blob: 2ca6406d4963c60db678435adbb2d49af3dc6ff3 [file] [log] [blame]
Thomas Tsou03e6ecf2013-08-20 20:54:54 -04001/*
2 * Rational Sample Rate Conversion
3 * Copyright (C) 2012, 2013 Thomas Tsou <tom@tsou.cc>
4 *
Pau Espin Pedrol21d03d32019-07-22 12:05:52 +02005 * SPDX-License-Identifier: LGPL-2.1+
6 *
Thomas Tsou03e6ecf2013-08-20 20:54:54 -04007 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#include <stdlib.h>
23#include <math.h>
24#include <string.h>
25#include <malloc.h>
26#include <iostream>
Tom Tsoud2e5c562017-06-19 15:23:59 -070027#include <algorithm>
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040028
29#include "Resampler.h"
30
31extern "C" {
32#include "convolve.h"
33}
34
35#ifndef M_PI
36#define M_PI 3.14159265358979323846264338327f
37#endif
38
39#define MAX_OUTPUT_LEN 4096
40
Tom Tsoud2e5c562017-06-19 15:23:59 -070041using namespace std;
42
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040043static float sinc(float x)
44{
45 if (x == 0.0)
46 return 0.9999999999;
47
48 return sin(M_PI * x) / (M_PI * x);
49}
50
Tom Tsoud2e5c562017-06-19 15:23:59 -070051void Resampler::initFilters(float bw)
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040052{
Tom Tsoud2e5c562017-06-19 15:23:59 -070053 float cutoff;
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040054 float sum = 0.0f, scale = 0.0f;
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040055
Pau Espin Pedrolbdb970e2019-07-22 12:03:39 +020056 /*
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040057 * Allocate partition filters and the temporary prototype filter
58 * according to numerator of the rational rate. Coefficients are
59 * real only and must be 16-byte memory aligned for SSE usage.
60 */
Tom Tsoud2e5c562017-06-19 15:23:59 -070061 auto proto = vector<float>(p * filt_len);
62 for (auto &part : partitions)
63 part = (complex<float> *) memalign(16, filt_len * sizeof(complex<float>));
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040064
Pau Espin Pedrolbdb970e2019-07-22 12:03:39 +020065 /*
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040066 * Generate the prototype filter with a Blackman-harris window.
67 * Scale coefficients with DC filter gain set to unity divided
Pau Espin Pedrolbdb970e2019-07-22 12:03:39 +020068 * by the number of filter partitions.
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040069 */
70 float a0 = 0.35875;
71 float a1 = 0.48829;
72 float a2 = 0.14128;
73 float a3 = 0.01168;
74
75 if (p > q)
76 cutoff = (float) p;
77 else
78 cutoff = (float) q;
79
Tom Tsoud2e5c562017-06-19 15:23:59 -070080 float midpt = (proto.size() - 1) / 2.0;
81 for (size_t i = 0; i < proto.size(); i++) {
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040082 proto[i] = sinc(((float) i - midpt) / cutoff * bw);
83 proto[i] *= a0 -
Tom Tsoud2e5c562017-06-19 15:23:59 -070084 a1 * cos(2 * M_PI * i / (proto.size() - 1)) +
85 a2 * cos(4 * M_PI * i / (proto.size() - 1)) -
86 a3 * cos(6 * M_PI * i / (proto.size() - 1));
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040087 sum += proto[i];
88 }
89 scale = p / sum;
90
91 /* Populate filter partitions from the prototype filter */
92 for (size_t i = 0; i < filt_len; i++) {
Tom Tsoud2e5c562017-06-19 15:23:59 -070093 for (size_t n = 0; n < p; n++)
94 partitions[n][i] = complex<float>(proto[i * p + n] * scale);
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040095 }
96
Tom Tsoud2e5c562017-06-19 15:23:59 -070097 /* Store filter taps in reverse */
98 for (auto &part : partitions)
99 reverse(&part[0], &part[filt_len]);
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400100}
101
Eric7a52e422020-08-14 03:11:22 +0200102#ifndef __OPTIMIZE__
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400103static bool check_vec_len(int in_len, int out_len, int p, int q)
104{
105 if (in_len % q) {
106 std::cerr << "Invalid input length " << in_len
107 << " is not multiple of " << q << std::endl;
108 return false;
109 }
110
111 if (out_len % p) {
112 std::cerr << "Invalid output length " << out_len
113 << " is not multiple of " << p << std::endl;
114 return false;
115 }
116
117 if ((in_len / q) != (out_len / p)) {
118 std::cerr << "Input/output block length mismatch" << std::endl;
119 std::cerr << "P = " << p << ", Q = " << q << std::endl;
120 std::cerr << "Input len: " << in_len << std::endl;
121 std::cerr << "Output len: " << out_len << std::endl;
122 return false;
123 }
124
125 if (out_len > MAX_OUTPUT_LEN) {
126 std::cerr << "Block length of " << out_len
127 << " exceeds max of " << MAX_OUTPUT_LEN << std::endl;
128 return false;
129 }
130
131 return true;
132}
Eric7a52e422020-08-14 03:11:22 +0200133#endif
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400134
Tom Tsou28670fb2015-08-21 19:32:58 -0700135int Resampler::rotate(const float *in, size_t in_len, float *out, size_t out_len)
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400136{
137 int n, path;
Eric7a52e422020-08-14 03:11:22 +0200138#ifndef __OPTIMIZE__
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400139 if (!check_vec_len(in_len, out_len, p, q))
Tom Tsoud3253432016-03-06 03:08:01 -0800140 return -1;
Eric7a52e422020-08-14 03:11:22 +0200141#endif
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400142 /* Generate output from precomputed input/output paths */
143 for (size_t i = 0; i < out_len; i++) {
Pau Espin Pedrolbdb970e2019-07-22 12:03:39 +0200144 n = in_index[i];
145 path = out_path[i];
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400146
147 convolve_real(in, in_len,
Tom Tsoud2e5c562017-06-19 15:23:59 -0700148 reinterpret_cast<float *>(partitions[path]),
149 filt_len, &out[2 * i], out_len - i,
Sylvain Munauta3934a12018-12-20 19:10:26 +0100150 n, 1);
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400151 }
152
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400153 return out_len;
154}
155
156bool Resampler::init(float bw)
157{
Tom Tsoud2e5c562017-06-19 15:23:59 -0700158 if (p == 0 || q == 0 || filt_len == 0) return false;
159
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400160 /* Filterbank filter internals */
Tom Tsoud2e5c562017-06-19 15:23:59 -0700161 initFilters(bw);
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400162
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400163 /* Precompute filterbank paths */
Tom Tsoud2e5c562017-06-19 15:23:59 -0700164 int i = 0;
165 for (auto &index : in_index)
166 index = (q * i++) / p;
167 i = 0;
168 for (auto &path : out_path)
169 path = (q * i++) % p;
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400170
171 return true;
172}
173
174size_t Resampler::len()
175{
176 return filt_len;
177}
178
179Resampler::Resampler(size_t p, size_t q, size_t filt_len)
Tom Tsoud2e5c562017-06-19 15:23:59 -0700180 : in_index(MAX_OUTPUT_LEN), out_path(MAX_OUTPUT_LEN), partitions(p)
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400181{
182 this->p = p;
183 this->q = q;
184 this->filt_len = filt_len;
185}
186
187Resampler::~Resampler()
188{
Tom Tsoud2e5c562017-06-19 15:23:59 -0700189 for (auto &part : partitions)
190 free(part);
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400191}