To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
The primary repository for this project is hosted at https://github.com/cannam/constant-q-cpp/ .
This repository is a read-only copy which is updated automatically every hour.
root / src / CQSpectrogram.cpp
History | View | Annotate | Download (7.37 KB)
| 1 |
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
|---|---|
| 2 |
/*
|
| 3 |
Constant-Q library
|
| 4 |
Copyright (c) 2013-2014 Queen Mary, University of London
|
| 5 |
|
| 6 |
Permission is hereby granted, free of charge, to any person
|
| 7 |
obtaining a copy of this software and associated documentation
|
| 8 |
files (the "Software"), to deal in the Software without
|
| 9 |
restriction, including without limitation the rights to use, copy,
|
| 10 |
modify, merge, publish, distribute, sublicense, and/or sell copies
|
| 11 |
of the Software, and to permit persons to whom the Software is
|
| 12 |
furnished to do so, subject to the following conditions:
|
| 13 |
|
| 14 |
The above copyright notice and this permission notice shall be
|
| 15 |
included in all copies or substantial portions of the Software.
|
| 16 |
|
| 17 |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 18 |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 19 |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 20 |
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
| 21 |
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
| 22 |
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 23 |
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 24 |
|
| 25 |
Except as contained in this notice, the names of the Centre for
|
| 26 |
Digital Music; Queen Mary, University of London; and Chris Cannam
|
| 27 |
shall not be used in advertising or otherwise to promote the sale,
|
| 28 |
use or other dealings in this Software without prior written
|
| 29 |
authorization.
|
| 30 |
*/
|
| 31 |
|
| 32 |
#include "CQSpectrogram.h" |
| 33 |
|
| 34 |
#include <iostream> |
| 35 |
#include <stdexcept> |
| 36 |
|
| 37 |
using std::cerr;
|
| 38 |
using std::endl;
|
| 39 |
|
| 40 |
//#define DEBUG_CQSPECTROGRAM 1
|
| 41 |
|
| 42 |
CQSpectrogram::CQSpectrogram(CQParameters params, |
| 43 |
Interpolation interpolation) : |
| 44 |
m_cq(params), |
| 45 |
m_interpolation(interpolation) |
| 46 |
{
|
| 47 |
} |
| 48 |
|
| 49 |
CQSpectrogram::~CQSpectrogram() |
| 50 |
{
|
| 51 |
} |
| 52 |
|
| 53 |
CQSpectrogram::RealBlock |
| 54 |
CQSpectrogram::process(const RealSequence &td)
|
| 55 |
{
|
| 56 |
return postProcess(m_cq.process(td), false); |
| 57 |
} |
| 58 |
|
| 59 |
CQSpectrogram::RealBlock |
| 60 |
CQSpectrogram::getRemainingOutput() |
| 61 |
{
|
| 62 |
return postProcess(m_cq.getRemainingOutput(), true); |
| 63 |
} |
| 64 |
|
| 65 |
CQSpectrogram::RealBlock |
| 66 |
CQSpectrogram::postProcess(const ComplexBlock &cq, bool insist) |
| 67 |
{
|
| 68 |
int width = cq.size();
|
| 69 |
|
| 70 |
// convert to magnitudes
|
| 71 |
RealBlock spec; |
| 72 |
for (int i = 0; i < width; ++i) { |
| 73 |
int height = cq[i].size();
|
| 74 |
RealColumn col(height, 0);
|
| 75 |
for (int j = 0; j < height; ++j) { |
| 76 |
#ifdef DEBUG_CQSPECTROGRAM
|
| 77 |
if (isnan(cq[i][j].real())) {
|
| 78 |
cerr << "WARNING: NaN in real at (" << i << "," << j << ")" << endl; |
| 79 |
} |
| 80 |
if (isnan(cq[i][j].imag())) {
|
| 81 |
cerr << "WARNING: NaN in imag at (" << i << "," << j << ")" << endl; |
| 82 |
} |
| 83 |
#endif
|
| 84 |
col[j] = abs(cq[i][j]); |
| 85 |
} |
| 86 |
spec.push_back(col); |
| 87 |
} |
| 88 |
|
| 89 |
if (m_interpolation == InterpolateZeros) {
|
| 90 |
for (int i = 0; i < width; ++i) { |
| 91 |
int sh = spec[i].size();
|
| 92 |
int fh = getTotalBins();
|
| 93 |
for (int j = sh; j < fh; ++j) { |
| 94 |
spec[i].push_back(0);
|
| 95 |
} |
| 96 |
} |
| 97 |
return spec;
|
| 98 |
} |
| 99 |
|
| 100 |
for (int i = 0; i < width; ++i) { |
| 101 |
m_buffer.push_back(spec[i]); |
| 102 |
} |
| 103 |
|
| 104 |
if (m_interpolation == InterpolateHold) {
|
| 105 |
return fetchHold(insist);
|
| 106 |
} else {
|
| 107 |
return fetchLinear(insist);
|
| 108 |
} |
| 109 |
} |
| 110 |
|
| 111 |
CQSpectrogram::RealBlock |
| 112 |
CQSpectrogram::fetchHold(bool)
|
| 113 |
{
|
| 114 |
RealBlock out; |
| 115 |
|
| 116 |
int width = m_buffer.size();
|
| 117 |
int height = getTotalBins();
|
| 118 |
|
| 119 |
for (int i = 0; i < width; ++i) { |
| 120 |
|
| 121 |
RealColumn col = m_buffer[i]; |
| 122 |
|
| 123 |
int thisHeight = col.size();
|
| 124 |
int prevHeight = m_prevColumn.size();
|
| 125 |
|
| 126 |
for (int j = thisHeight; j < height; ++j) { |
| 127 |
if (j < prevHeight) {
|
| 128 |
col.push_back(m_prevColumn[j]); |
| 129 |
} else {
|
| 130 |
col.push_back(0.0); |
| 131 |
} |
| 132 |
} |
| 133 |
|
| 134 |
m_prevColumn = col; |
| 135 |
out.push_back(col); |
| 136 |
} |
| 137 |
|
| 138 |
m_buffer.clear(); |
| 139 |
|
| 140 |
return out;
|
| 141 |
} |
| 142 |
|
| 143 |
CQSpectrogram::RealBlock |
| 144 |
CQSpectrogram::fetchLinear(bool insist)
|
| 145 |
{
|
| 146 |
RealBlock out; |
| 147 |
|
| 148 |
//!!! This is surprisingly messy. I must be missing something.
|
| 149 |
|
| 150 |
// We can only return any data when we have at least one column
|
| 151 |
// that has the full height in the buffer, that is not the first
|
| 152 |
// column.
|
| 153 |
//
|
| 154 |
// If the first col has full height, and there is another one
|
| 155 |
// later that also does, then we can interpolate between those, up
|
| 156 |
// to but not including the second full height column. Then we
|
| 157 |
// drop and return the columns we interpolated, leaving the second
|
| 158 |
// full-height col as the first col in the buffer. And repeat as
|
| 159 |
// long as enough columns are available.
|
| 160 |
//
|
| 161 |
// If the first col does not have full height, then (so long as
|
| 162 |
// we're following the logic above) we must simply have not yet
|
| 163 |
// reached the first full-height column in the CQ output, and we
|
| 164 |
// can interpolate nothing.
|
| 165 |
|
| 166 |
int width = m_buffer.size();
|
| 167 |
int height = getTotalBins();
|
| 168 |
|
| 169 |
if (width == 0) return out; |
| 170 |
|
| 171 |
int firstFullHeight = -1; |
| 172 |
int secondFullHeight = -1; |
| 173 |
|
| 174 |
for (int i = 0; i < width; ++i) { |
| 175 |
if ((int)m_buffer[i].size() == height) { |
| 176 |
if (firstFullHeight == -1) { |
| 177 |
firstFullHeight = i; |
| 178 |
} else if (secondFullHeight == -1) { |
| 179 |
secondFullHeight = i; |
| 180 |
break;
|
| 181 |
} |
| 182 |
} |
| 183 |
} |
| 184 |
|
| 185 |
// cerr << "fetchLinear: firstFullHeight = " << firstFullHeight << ", secondFullHeight = " << secondFullHeight << endl;
|
| 186 |
|
| 187 |
if (firstFullHeight < 0) { |
| 188 |
if (insist) {
|
| 189 |
return fetchHold(true); |
| 190 |
} else {
|
| 191 |
return out;
|
| 192 |
} |
| 193 |
} else if (firstFullHeight > 0) { |
| 194 |
// can interpolate nothing, stash up to first full height & recurse
|
| 195 |
out = RealBlock(m_buffer.begin(), m_buffer.begin() + firstFullHeight); |
| 196 |
m_buffer = RealBlock(m_buffer.begin() + firstFullHeight, m_buffer.end()); |
| 197 |
RealBlock more = fetchLinear(insist); |
| 198 |
out.insert(out.end(), more.begin(), more.end()); |
| 199 |
return out;
|
| 200 |
} else if (secondFullHeight < 0) { |
| 201 |
// firstFullHeight == 0, but there is no second full height --
|
| 202 |
// wait for it unless insist flag is set
|
| 203 |
if (insist) {
|
| 204 |
return fetchHold(true); |
| 205 |
} else {
|
| 206 |
return out;
|
| 207 |
} |
| 208 |
} else {
|
| 209 |
// firstFullHeight == 0 and secondFullHeight also valid. Can interpolate
|
| 210 |
out = linearInterpolated(m_buffer, 0, secondFullHeight);
|
| 211 |
m_buffer = RealBlock(m_buffer.begin() + secondFullHeight, m_buffer.end()); |
| 212 |
RealBlock more = fetchLinear(insist); |
| 213 |
out.insert(out.end(), more.begin(), more.end()); |
| 214 |
return out;
|
| 215 |
} |
| 216 |
} |
| 217 |
|
| 218 |
CQSpectrogram::RealBlock |
| 219 |
CQSpectrogram::linearInterpolated(const RealBlock &g, int x0, int x1) |
| 220 |
{
|
| 221 |
// g must be a grid with full-height columns at x0 and x1
|
| 222 |
|
| 223 |
if (x0 >= x1) {
|
| 224 |
throw std::logic_error("x0 >= x1"); |
| 225 |
} |
| 226 |
if (x1 >= (int)g.size()) { |
| 227 |
throw std::logic_error("x1 >= g.size()"); |
| 228 |
} |
| 229 |
if (g[x0].size() != g[x1].size()) {
|
| 230 |
throw std::logic_error("x0 and x1 are not the same height"); |
| 231 |
} |
| 232 |
|
| 233 |
int height = g[x0].size();
|
| 234 |
int width = x1 - x0;
|
| 235 |
|
| 236 |
RealBlock out(g.begin() + x0, g.begin() + x1); |
| 237 |
|
| 238 |
for (int y = 0; y < height; ++y) { |
| 239 |
|
| 240 |
int spacing = width;
|
| 241 |
for (int i = 1; i < width; ++i) { |
| 242 |
int thisHeight = g[x0 + i].size();
|
| 243 |
if (thisHeight > height) {
|
| 244 |
throw std::logic_error("First column not full-height"); |
| 245 |
} |
| 246 |
if (thisHeight > y) {
|
| 247 |
spacing = i; |
| 248 |
break;
|
| 249 |
} |
| 250 |
} |
| 251 |
|
| 252 |
if (spacing < 2) continue; |
| 253 |
|
| 254 |
for (int i = 0; i + spacing <= width; i += spacing) { |
| 255 |
for (int j = 1; j < spacing; ++j) { |
| 256 |
double proportion = double(j)/double(spacing); |
| 257 |
double interpolated =
|
| 258 |
g[x0 + i][y] * (1.0 - proportion) + |
| 259 |
g[x0 + i + spacing][y] * proportion; |
| 260 |
out[i + j].push_back(interpolated); |
| 261 |
} |
| 262 |
} |
| 263 |
} |
| 264 |
|
| 265 |
return out;
|
| 266 |
} |
| 267 |
|
| 268 |
|
| 269 |
|