blob: 7ba021950a4946aca5784262e8a8c0f623746b5f [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
102static bool check_vec_len(int in_len, int out_len, int p, int q)
103{
104 if (in_len % q) {
105 std::cerr << "Invalid input length " << in_len
106 << " is not multiple of " << q << std::endl;
107 return false;
108 }
109
110 if (out_len % p) {
111 std::cerr << "Invalid output length " << out_len
112 << " is not multiple of " << p << std::endl;
113 return false;
114 }
115
116 if ((in_len / q) != (out_len / p)) {
117 std::cerr << "Input/output block length mismatch" << std::endl;
118 std::cerr << "P = " << p << ", Q = " << q << std::endl;
119 std::cerr << "Input len: " << in_len << std::endl;
120 std::cerr << "Output len: " << out_len << std::endl;
121 return false;
122 }
123
124 if (out_len > MAX_OUTPUT_LEN) {
125 std::cerr << "Block length of " << out_len
126 << " exceeds max of " << MAX_OUTPUT_LEN << std::endl;
127 return false;
128 }
129
130 return true;
131}
132
Tom Tsou28670fb2015-08-21 19:32:58 -0700133int Resampler::rotate(const float *in, size_t in_len, float *out, size_t out_len)
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400134{
135 int n, path;
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400136
137 if (!check_vec_len(in_len, out_len, p, q))
Tom Tsoud3253432016-03-06 03:08:01 -0800138 return -1;
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400139
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400140 /* Generate output from precomputed input/output paths */
141 for (size_t i = 0; i < out_len; i++) {
Pau Espin Pedrolbdb970e2019-07-22 12:03:39 +0200142 n = in_index[i];
143 path = out_path[i];
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400144
145 convolve_real(in, in_len,
Tom Tsoud2e5c562017-06-19 15:23:59 -0700146 reinterpret_cast<float *>(partitions[path]),
147 filt_len, &out[2 * i], out_len - i,
Sylvain Munauta3934a12018-12-20 19:10:26 +0100148 n, 1);
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400149 }
150
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400151 return out_len;
152}
153
154bool Resampler::init(float bw)
155{
Tom Tsoud2e5c562017-06-19 15:23:59 -0700156 if (p == 0 || q == 0 || filt_len == 0) return false;
157
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400158 /* Filterbank filter internals */
Tom Tsoud2e5c562017-06-19 15:23:59 -0700159 initFilters(bw);
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400160
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400161 /* Precompute filterbank paths */
Tom Tsoud2e5c562017-06-19 15:23:59 -0700162 int i = 0;
163 for (auto &index : in_index)
164 index = (q * i++) / p;
165 i = 0;
166 for (auto &path : out_path)
167 path = (q * i++) % p;
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400168
169 return true;
170}
171
172size_t Resampler::len()
173{
174 return filt_len;
175}
176
177Resampler::Resampler(size_t p, size_t q, size_t filt_len)
Tom Tsoud2e5c562017-06-19 15:23:59 -0700178 : in_index(MAX_OUTPUT_LEN), out_path(MAX_OUTPUT_LEN), partitions(p)
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400179{
180 this->p = p;
181 this->q = q;
182 this->filt_len = filt_len;
183}
184
185Resampler::~Resampler()
186{
Tom Tsoud2e5c562017-06-19 15:23:59 -0700187 for (auto &part : partitions)
188 free(part);
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400189}