Chris@0: Chris@0: #include "TuningDifference.h" Chris@0: Chris@1: #include Chris@1: Chris@4: #include Chris@4: #include Chris@4: Chris@1: using std::cerr; Chris@1: using std::endl; Chris@1: Chris@5: static double targetFmin = 60.0; Chris@5: static double targetFmax = 1500.0; Chris@5: Chris@0: TuningDifference::TuningDifference(float inputSampleRate) : Chris@0: Plugin(inputSampleRate) Chris@0: { Chris@0: } Chris@0: Chris@0: TuningDifference::~TuningDifference() Chris@0: { Chris@0: } Chris@0: Chris@0: string Chris@0: TuningDifference::getIdentifier() const Chris@0: { Chris@1: return "tuning-difference"; Chris@0: } Chris@0: Chris@0: string Chris@0: TuningDifference::getName() const Chris@0: { Chris@1: return "Tuning Difference"; Chris@0: } Chris@0: Chris@0: string Chris@0: TuningDifference::getDescription() const Chris@0: { Chris@0: // Return something helpful here! Chris@0: return ""; Chris@0: } Chris@0: Chris@0: string Chris@0: TuningDifference::getMaker() const Chris@0: { Chris@0: // Your name here Chris@0: return ""; Chris@0: } Chris@0: Chris@0: int Chris@0: TuningDifference::getPluginVersion() const Chris@0: { Chris@0: // Increment this each time you release a version that behaves Chris@0: // differently from the previous one Chris@0: return 1; Chris@0: } Chris@0: Chris@0: string Chris@0: TuningDifference::getCopyright() const Chris@0: { Chris@0: // This function is not ideally named. It does not necessarily Chris@0: // need to say who made the plugin -- getMaker does that -- but it Chris@0: // should indicate the terms under which it is distributed. For Chris@0: // example, "Copyright (year). All Rights Reserved", or "GPL" Chris@0: return ""; Chris@0: } Chris@0: Chris@0: TuningDifference::InputDomain Chris@0: TuningDifference::getInputDomain() const Chris@0: { Chris@1: return FrequencyDomain; Chris@0: } Chris@0: Chris@0: size_t Chris@0: TuningDifference::getPreferredBlockSize() const Chris@0: { Chris@2: return 16384; Chris@0: } Chris@0: Chris@0: size_t Chris@0: TuningDifference::getPreferredStepSize() const Chris@0: { Chris@1: return 0; Chris@0: } Chris@0: Chris@0: size_t Chris@0: TuningDifference::getMinChannelCount() const Chris@0: { Chris@1: return 2; Chris@0: } Chris@0: Chris@0: size_t Chris@0: TuningDifference::getMaxChannelCount() const Chris@0: { Chris@1: return 2; Chris@0: } Chris@0: Chris@0: TuningDifference::ParameterList Chris@0: TuningDifference::getParameterDescriptors() const Chris@0: { Chris@0: ParameterList list; Chris@0: return list; Chris@0: } Chris@0: Chris@0: float Chris@1: TuningDifference::getParameter(string) const Chris@0: { Chris@0: return 0; Chris@0: } Chris@0: Chris@0: void Chris@1: TuningDifference::setParameter(string, float) Chris@0: { Chris@0: } Chris@0: Chris@0: TuningDifference::ProgramList Chris@0: TuningDifference::getPrograms() const Chris@0: { Chris@0: ProgramList list; Chris@0: return list; Chris@0: } Chris@0: Chris@0: string Chris@0: TuningDifference::getCurrentProgram() const Chris@0: { Chris@0: return ""; // no programs Chris@0: } Chris@0: Chris@0: void Chris@1: TuningDifference::selectProgram(string) Chris@0: { Chris@0: } Chris@0: Chris@0: TuningDifference::OutputList Chris@0: TuningDifference::getOutputDescriptors() const Chris@0: { Chris@0: OutputList list; Chris@0: Chris@1: OutputDescriptor d; Chris@1: d.identifier = "cents"; Chris@1: d.name = "Tuning Difference"; Chris@1: d.description = "Difference in averaged frequency profile between channels 1 and 2, in cents. A positive value means channel 2 is higher."; Chris@1: d.unit = "cents"; Chris@1: d.hasFixedBinCount = true; Chris@1: d.binCount = 1; Chris@1: d.hasKnownExtents = false; Chris@1: d.isQuantized = false; Chris@1: d.sampleType = OutputDescriptor::VariableSampleRate; Chris@1: d.hasDuration = false; Chris@1: list.push_back(d); Chris@0: Chris@1: d.identifier = "tuningfreq"; Chris@1: d.name = "Relative Tuning Frequency"; Chris@1: d.description = "Tuning frequency of channel 2, if channel 1 is assumed to contain the same music as it at a tuning frequency of A=440Hz."; Chris@4: d.unit = "hz"; Chris@1: d.hasFixedBinCount = true; Chris@1: d.binCount = 1; Chris@1: d.hasKnownExtents = false; Chris@1: d.isQuantized = false; Chris@1: d.sampleType = OutputDescriptor::VariableSampleRate; Chris@1: d.hasDuration = false; Chris@1: list.push_back(d); Chris@1: Chris@4: d.identifier = "curve"; Chris@4: d.name = "Shift Correlation Curve"; Chris@4: d.description = "Correlation between shifted and unshifted sources, for each probed shift in cents."; Chris@0: d.unit = ""; Chris@0: d.hasFixedBinCount = true; Chris@0: d.binCount = 1; Chris@0: d.hasKnownExtents = false; Chris@0: d.isQuantized = false; Chris@1: d.sampleType = OutputDescriptor::FixedSampleRate; Chris@1: d.sampleRate = 100; Chris@0: d.hasDuration = false; Chris@0: list.push_back(d); Chris@0: Chris@5: int targetBinMin = int(floor(targetFmin * m_blockSize / m_inputSampleRate)); Chris@5: int targetBinMax = int(ceil(targetFmax * m_blockSize / m_inputSampleRate)); Chris@5: cerr << "target bin range: " << targetBinMin << " -> " << targetBinMax << endl; Chris@5: Chris@4: d.identifier = "averages"; Chris@4: d.name = "Spectrum averages"; Chris@4: d.description = "Average magnitude spectrum for each channel."; Chris@4: d.unit = ""; Chris@4: d.hasFixedBinCount = true; Chris@5: d.binCount = (targetBinMax > targetBinMin ? targetBinMax - targetBinMin + 1 : 100); Chris@4: d.hasKnownExtents = false; Chris@4: d.isQuantized = false; Chris@4: d.sampleType = OutputDescriptor::FixedSampleRate; Chris@5: d.sampleRate = 1; Chris@4: d.hasDuration = false; Chris@4: list.push_back(d); Chris@4: Chris@0: return list; Chris@0: } Chris@0: Chris@0: bool Chris@0: TuningDifference::initialise(size_t channels, size_t stepSize, size_t blockSize) Chris@0: { Chris@0: if (channels < getMinChannelCount() || Chris@0: channels > getMaxChannelCount()) return false; Chris@0: Chris@1: if (blockSize != getPreferredBlockSize() || Chris@1: stepSize != blockSize/2) return false; Chris@0: Chris@1: m_blockSize = blockSize; Chris@1: Chris@1: reset(); Chris@1: Chris@0: return true; Chris@0: } Chris@0: Chris@0: void Chris@0: TuningDifference::reset() Chris@0: { Chris@1: m_sum[0].clear(); Chris@1: m_sum[1].clear(); Chris@1: m_frameCount = 0; Chris@0: } Chris@0: Chris@0: TuningDifference::FeatureSet Chris@0: TuningDifference::process(const float *const *inputBuffers, Vamp::RealTime timestamp) Chris@0: { Chris@1: for (int c = 0; c < 2; ++c) { Chris@4: if (m_sum[c].size() == 0) { Chris@4: m_sum[c].resize(m_blockSize/2 + 1, 0.0); Chris@4: } Chris@4: for (int i = 0; i <= m_blockSize/2; ++i) { Chris@1: double energy = Chris@1: inputBuffers[c][i*2 ] * inputBuffers[c][i*2 ] + Chris@1: inputBuffers[c][i*2+1] * inputBuffers[c][i*2+1]; Chris@5: double mag = sqrt(energy); Chris@5: m_sum[c][i] += mag; Chris@5: m_sum[c][i/2] += mag; Chris@1: } Chris@1: } Chris@1: Chris@1: ++m_frameCount; Chris@0: return FeatureSet(); Chris@0: } Chris@0: Chris@0: TuningDifference::FeatureSet Chris@0: TuningDifference::getRemainingFeatures() Chris@0: { Chris@1: int n = m_sum[0].size(); Chris@1: if (n == 0) return FeatureSet(); Chris@1: Chris@1: Feature f; Chris@1: FeatureSet fs; Chris@3: Chris@4: int maxshift = 2400; // cents Chris@4: Chris@4: int bestshift = -1; Chris@4: double bestdist = 0; Chris@4: Chris@4: for (int i = 0; i < n; ++i) { Chris@4: m_sum[0][i] /= m_frameCount; Chris@4: m_sum[1][i] /= m_frameCount; Chris@4: } Chris@4: Chris@4: for (int c = 0; c < 2; ++c) { Chris@4: double tot = 0.0; Chris@1: for (int i = 0; i < n; ++i) { Chris@4: tot += m_sum[c][i]; Chris@4: } Chris@4: if (tot != 0.0) { Chris@4: for (int i = 0; i < n; ++i) { Chris@4: m_sum[c][i] /= tot; Chris@1: } Chris@1: } Chris@4: } Chris@5: Chris@5: int targetBinMin = int(floor(targetFmin * m_blockSize / m_inputSampleRate)); Chris@5: int targetBinMax = int(ceil(targetFmax * m_blockSize / m_inputSampleRate)); Chris@5: cerr << "target bin range: " << targetBinMin << " -> " << targetBinMax << endl; Chris@4: Chris@5: f.values.clear(); Chris@5: for (int i = targetBinMin; i < targetBinMax; ++i) { Chris@4: f.values.push_back(m_sum[0][i]); Chris@5: } Chris@5: fs[3].push_back(f); Chris@5: f.values.clear(); Chris@5: for (int i = targetBinMin; i < targetBinMax; ++i) { Chris@4: f.values.push_back(m_sum[1][i]); Chris@1: } Chris@5: fs[3].push_back(f); Chris@1: Chris@4: f.values.clear(); Chris@5: Chris@4: for (int shift = -maxshift; shift <= maxshift; ++shift) { Chris@4: Chris@4: double multiplier = pow(2.0, double(shift) / 1200.0); Chris@4: double dist = 0.0; Chris@4: Chris@4: // cerr << "shift = " << shift << ", multiplier = " << multiplier << endl; Chris@5: Chris@5: int contributing = 0; Chris@4: Chris@5: for (int i = targetBinMin; i < targetBinMax; ++i) { Chris@4: Chris@4: double source = i / multiplier; Chris@4: int s0 = int(source), s1 = s0 + 1; Chris@4: double p1 = source - s0; Chris@4: double p0 = 1.0 - p1; Chris@4: Chris@4: double value = 0.0; Chris@4: if (s0 >= 0 && s0 < n) { Chris@4: value += p0 * m_sum[1][s0]; Chris@5: ++contributing; Chris@4: } Chris@4: if (s1 >= 0 && s1 < n) { Chris@4: value += p1 * m_sum[1][s1]; Chris@5: ++contributing; Chris@4: } Chris@4: Chris@4: // if (shift == -1) { Chris@4: // cerr << "for multiplier " << multiplier << ", target " << i << ", source " << source << ", value " << p0 << " * " << m_sum[1][s0] << " + " << p1 << " * " << m_sum[1][s1] << " = " << value << ", other " << m_sum[0][i] << endl; Chris@4: // } Chris@4: Chris@4: double diff = fabs(m_sum[0][i] - value); Chris@4: dist += diff; Chris@4: } Chris@4: Chris@5: dist /= contributing; Chris@5: Chris@4: f.values.clear(); Chris@4: f.values.push_back(dist); Chris@4: char label[100]; Chris@4: sprintf(label, "%f at shift %d freq mult %f", dist, shift, multiplier); Chris@4: f.label = label; Chris@4: fs[2].push_back(f); Chris@4: Chris@4: if (bestshift == -1 || dist < bestdist) { Chris@4: bestshift = shift; Chris@4: bestdist = dist; Chris@4: } Chris@4: } Chris@4: Chris@4: f.timestamp = Vamp::RealTime::zeroTime; Chris@4: f.hasTimestamp = true; Chris@5: f.label = ""; Chris@4: Chris@4: f.values.clear(); Chris@4: // cerr << "best dist = " << bestdist << " at shift " << bestshift << endl; Chris@4: f.values.push_back(-bestshift); Chris@4: fs[0].push_back(f); Chris@4: Chris@4: f.values.clear(); Chris@4: f.values.push_back(440.0 / pow(2.0, double(bestshift) / 1200.0)); Chris@4: fs[1].push_back(f); Chris@4: Chris@1: return fs; Chris@0: } Chris@0: