blob: f545db8647b5cb7672755447198a1e33e48165a7 [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 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include <stdlib.h>
21#include <math.h>
22#include <string.h>
23#include <malloc.h>
24#include <iostream>
Tom Tsoud2e5c562017-06-19 15:23:59 -070025#include <algorithm>
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040026
27#include "Resampler.h"
28
29extern "C" {
30#include "convolve.h"
31}
32
33#ifndef M_PI
34#define M_PI 3.14159265358979323846264338327f
35#endif
36
37#define MAX_OUTPUT_LEN 4096
38
Tom Tsoud2e5c562017-06-19 15:23:59 -070039using namespace std;
40
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040041static float sinc(float x)
42{
43 if (x == 0.0)
44 return 0.9999999999;
45
46 return sin(M_PI * x) / (M_PI * x);
47}
48
Tom Tsoud2e5c562017-06-19 15:23:59 -070049void Resampler::initFilters(float bw)
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040050{
Tom Tsoud2e5c562017-06-19 15:23:59 -070051 float cutoff;
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040052 float sum = 0.0f, scale = 0.0f;
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040053
Pau Espin Pedrolbdb970e2019-07-22 12:03:39 +020054 /*
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040055 * Allocate partition filters and the temporary prototype filter
56 * according to numerator of the rational rate. Coefficients are
57 * real only and must be 16-byte memory aligned for SSE usage.
58 */
Tom Tsoud2e5c562017-06-19 15:23:59 -070059 auto proto = vector<float>(p * filt_len);
60 for (auto &part : partitions)
61 part = (complex<float> *) memalign(16, filt_len * sizeof(complex<float>));
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040062
Pau Espin Pedrolbdb970e2019-07-22 12:03:39 +020063 /*
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040064 * Generate the prototype filter with a Blackman-harris window.
65 * Scale coefficients with DC filter gain set to unity divided
Pau Espin Pedrolbdb970e2019-07-22 12:03:39 +020066 * by the number of filter partitions.
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040067 */
68 float a0 = 0.35875;
69 float a1 = 0.48829;
70 float a2 = 0.14128;
71 float a3 = 0.01168;
72
73 if (p > q)
74 cutoff = (float) p;
75 else
76 cutoff = (float) q;
77
Tom Tsoud2e5c562017-06-19 15:23:59 -070078 float midpt = (proto.size() - 1) / 2.0;
79 for (size_t i = 0; i < proto.size(); i++) {
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040080 proto[i] = sinc(((float) i - midpt) / cutoff * bw);
81 proto[i] *= a0 -
Tom Tsoud2e5c562017-06-19 15:23:59 -070082 a1 * cos(2 * M_PI * i / (proto.size() - 1)) +
83 a2 * cos(4 * M_PI * i / (proto.size() - 1)) -
84 a3 * cos(6 * M_PI * i / (proto.size() - 1));
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040085 sum += proto[i];
86 }
87 scale = p / sum;
88
89 /* Populate filter partitions from the prototype filter */
90 for (size_t i = 0; i < filt_len; i++) {
Tom Tsoud2e5c562017-06-19 15:23:59 -070091 for (size_t n = 0; n < p; n++)
92 partitions[n][i] = complex<float>(proto[i * p + n] * scale);
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040093 }
94
Tom Tsoud2e5c562017-06-19 15:23:59 -070095 /* Store filter taps in reverse */
96 for (auto &part : partitions)
97 reverse(&part[0], &part[filt_len]);
Thomas Tsou03e6ecf2013-08-20 20:54:54 -040098}
99
100static bool check_vec_len(int in_len, int out_len, int p, int q)
101{
102 if (in_len % q) {
103 std::cerr << "Invalid input length " << in_len
104 << " is not multiple of " << q << std::endl;
105 return false;
106 }
107
108 if (out_len % p) {
109 std::cerr << "Invalid output length " << out_len
110 << " is not multiple of " << p << std::endl;
111 return false;
112 }
113
114 if ((in_len / q) != (out_len / p)) {
115 std::cerr << "Input/output block length mismatch" << std::endl;
116 std::cerr << "P = " << p << ", Q = " << q << std::endl;
117 std::cerr << "Input len: " << in_len << std::endl;
118 std::cerr << "Output len: " << out_len << std::endl;
119 return false;
120 }
121
122 if (out_len > MAX_OUTPUT_LEN) {
123 std::cerr << "Block length of " << out_len
124 << " exceeds max of " << MAX_OUTPUT_LEN << std::endl;
125 return false;
126 }
127
128 return true;
129}
130
Tom Tsou28670fb2015-08-21 19:32:58 -0700131int Resampler::rotate(const float *in, size_t in_len, float *out, size_t out_len)
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400132{
133 int n, path;
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400134
135 if (!check_vec_len(in_len, out_len, p, q))
Tom Tsoud3253432016-03-06 03:08:01 -0800136 return -1;
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400137
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400138 /* Generate output from precomputed input/output paths */
139 for (size_t i = 0; i < out_len; i++) {
Pau Espin Pedrolbdb970e2019-07-22 12:03:39 +0200140 n = in_index[i];
141 path = out_path[i];
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400142
143 convolve_real(in, in_len,
Tom Tsoud2e5c562017-06-19 15:23:59 -0700144 reinterpret_cast<float *>(partitions[path]),
145 filt_len, &out[2 * i], out_len - i,
Sylvain Munauta3934a12018-12-20 19:10:26 +0100146 n, 1);
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400147 }
148
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400149 return out_len;
150}
151
152bool Resampler::init(float bw)
153{
Tom Tsoud2e5c562017-06-19 15:23:59 -0700154 if (p == 0 || q == 0 || filt_len == 0) return false;
155
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400156 /* Filterbank filter internals */
Tom Tsoud2e5c562017-06-19 15:23:59 -0700157 initFilters(bw);
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400158
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400159 /* Precompute filterbank paths */
Tom Tsoud2e5c562017-06-19 15:23:59 -0700160 int i = 0;
161 for (auto &index : in_index)
162 index = (q * i++) / p;
163 i = 0;
164 for (auto &path : out_path)
165 path = (q * i++) % p;
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400166
167 return true;
168}
169
170size_t Resampler::len()
171{
172 return filt_len;
173}
174
175Resampler::Resampler(size_t p, size_t q, size_t filt_len)
Tom Tsoud2e5c562017-06-19 15:23:59 -0700176 : in_index(MAX_OUTPUT_LEN), out_path(MAX_OUTPUT_LEN), partitions(p)
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400177{
178 this->p = p;
179 this->q = q;
180 this->filt_len = filt_len;
181}
182
183Resampler::~Resampler()
184{
Tom Tsoud2e5c562017-06-19 15:23:59 -0700185 for (auto &part : partitions)
186 free(part);
Thomas Tsou03e6ecf2013-08-20 20:54:54 -0400187}