|
From: Lasse Kärkkäi. <tr...@us...> - 2011-02-03 19:47:13
|
Module: editor
Branch: master
Commit: f51ea5b2f3f870a5cfbe6e81a0d88ae9d231916f
Author: Lasse Karkkainen <tro...@tr...>
Date: Thu Feb 3 20:46:21 2011 +0100
Proper multichannel and samplerate handling for ffmpeg & pitchvis.
---
ffmpeg.cc | 1 +
ffmpeg.hh | 6 ++-
pitchvis.cc | 121 +++++++++++++++++++++++++++++++---------------------------
pitchvis.hh | 7 +++-
4 files changed, 77 insertions(+), 58 deletions(-)
diff --git a/ffmpeg.cc b/ffmpeg.cc
index c264b1d..84a14cf 100644
--- a/ffmpeg.cc
+++ b/ffmpeg.cc
@@ -74,6 +74,7 @@ void FFmpeg::open() {
if (audioStream == -1) throw std::runtime_error("No audio stream found");
AVCodecContext* cc = pFormatCtx->streams[audioStream]->codec;
pAudioCodec = avcodec_find_decoder(cc->codec_id);
+ audioQueue.setRateChannels(cc->sample_rate, cc->channels);
if (!pAudioCodec) throw std::runtime_error("Cannot find audio codec");
if (avcodec_open(cc, pAudioCodec) < 0) throw std::runtime_error("Cannot open audio codec");
pAudioCodecCtx = cc;
diff --git a/ffmpeg.hh b/ffmpeg.hh
index 8486db1..c904471 100644
--- a/ffmpeg.hh
+++ b/ffmpeg.hh
@@ -57,7 +57,10 @@ public:
m_needSpace.wakeOne();
return true;
}
- unsigned samplesPerSecond() const { return 44100; /* FIXME */ }
+ unsigned samplesPerSecond() const { return m_channels * m_rate; }
+ void setRateChannels(unsigned rate, unsigned channels) { m_rate = rate; m_channels = channels; }
+ unsigned getRate() { return m_rate; }
+ unsigned getChannels() { return m_channels; }
AudioQueue(unsigned capacity = (2 << 20)): m_ring(capacity), m_channels(), m_position(), m_size(), m_eof() {}
private:
@@ -65,6 +68,7 @@ private:
QWaitCondition m_needData, m_needSpace;
typedef std::vector<da::sample_t> Ring;
Ring m_ring;
+ unsigned m_rate;
unsigned m_channels;
unsigned m_position;
unsigned m_size;
diff --git a/pitchvis.cc b/pitchvis.cc
index 4220864..b275144 100644
--- a/pitchvis.cc
+++ b/pitchvis.cc
@@ -20,60 +20,67 @@ PitchVis::PitchVis(QString const& filename, QWidget *parent)
void PitchVis::run()
{
try {
- unsigned rate = 44100;
- Analyzer analyzer(rate, "");
- // Process the entire song
+ // Initialize FFmpeg decoding
+ FFmpeg mpeg(fileName.toStdString());
{
- // Initialize FFmpeg decoding
- FFmpeg mpeg(fileName.toStdString());
- {
- QMutexLocker locker(&mutex);
- paths.clear();
- position = 0.0;
- duration = mpeg.duration(); // Estimation
+ QMutexLocker locker(&mutex);
+ paths.clear();
+ position = 0.0;
+ duration = mpeg.duration(); // Estimation
+ }
+ unsigned channels = mpeg.audioQueue.getChannels();
+ if (channels == 0) throw std::runtime_error("No audio channels found");
+ std::vector<Analyzer> analyzers(channels, Analyzer(mpeg.audioQueue.getRate(), ""));
+ // Process the entire song
+ std::vector<float> data;
+ unsigned x = 0;
+ while (mpeg.audioQueue.output(data)) {
+ // Read until enough data is available
+ if (data.size() / channels - x < analyzers[0].processSize()) continue;
+ // Pitch detection
+ for (unsigned ch = 0; ch < channels; ++ch) {
+ analyzers[ch].process(da::step_iterator<float>(&data[x * channels + ch], channels));
}
- std::vector<float> data;
- unsigned x = 0;
- while (mpeg.audioQueue.output(data)) {
- // Read until enough data is available
- if (data.size() - x < analyzer.processSize()) continue;
- // Pitch detection
- analyzer.process(&data[x]);
- x += analyzer.processStep();
-
- QMutexLocker locker(&mutex);
- if (cancelled) return;
- Analyzer::Moments const& moments = analyzer.getMoments();
- if (!moments.empty()) {
- double t = moments.back().time();
- position = t;
- duration = std::max(duration, t + 0.01);
- }
+ x += analyzers[0].processStep();
+ // Update progress and check for quit flag
+ QMutexLocker locker(&mutex);
+ if (cancelled) return;
+ Analyzer::Moments const& moments = analyzers[0].getMoments();
+ if (!moments.empty()) {
+ double t = moments.back().time();
+ position = t;
+ duration = std::max(duration, t + 0.01);
}
}
// Filter the analyzer output data into QPainterPaths.
- Analyzer::Moments const& moments = analyzer.getMoments();
- if (moments.empty()) return;
- for (Analyzer::Moments::const_iterator it = moments.begin(), itend = moments.end(); it != itend; ++it) {
- Moment::Tones const& tones = it->m_tones;
- for (Moment::Tones::const_iterator it2 = tones.begin(), it2end = tones.end(); it2 != it2end; ++it2) {
- if (it2->prev) continue; // The tone doesn't begin at this moment, skip
- // Copy the linked list into vector for easier access and calculate max level
- std::vector<Tone const*> tones;
- for (Tone const* n = &*it2; n; n = n->next) { tones.push_back(n); }
- if (tones.size() < 3) continue; // Too short tone, ignored
- PitchPath path;
- Analyzer::Moments::const_iterator momit = it;
- // Render
- for (unsigned i = 0; i < tones.size(); ++i, ++momit) {
- float t = momit->m_time;
- float n = scale.getNote(tones[i]->freq);
- float level = level2dB(tones[i]->level);
- path.push_back(PitchFragment(t, n, level));
+ std::vector<Analyzer::Moments::const_iterator> mit(channels), mend(channels);
+ for (unsigned ch = 0; ch < channels; ++ch) {
+ Analyzer::Moments const& moments = analyzers[ch].getMoments();
+ mit[ch] = moments.begin();
+ mend[ch] = moments.end();
+ }
+ while (mit[0] != mend[0]) {
+ for (unsigned ch = 0; ch < channels; ++mit[ch++]) {
+ Moment::Tones const& tones = mit[ch]->m_tones; // Take tones then move forward the iterator
+ for (Moment::Tones::const_iterator it2 = tones.begin(), it2end = tones.end(); it2 != it2end; ++it2) {
+ if (it2->prev) continue; // The tone doesn't begin at this moment, skip
+ // Copy the linked list into vector for easier access and calculate max level
+ std::vector<Tone const*> tones;
+ for (Tone const* n = &*it2; n; n = n->next) { tones.push_back(n); }
+ if (tones.size() < 3) continue; // Too short tone, ignored
+ PitchPath path(ch);
+ Analyzer::Moments::const_iterator momit = mit[ch];
+ // Store path used for rendering
+ for (unsigned i = 0; i < tones.size(); ++i, ++momit) {
+ float t = momit->m_time;
+ float n = scale.getNote(tones[i]->freq);
+ float level = level2dB(tones[i]->level);
+ path.fragments.push_back(PitchFragment(t, n, level));
+ }
+ QMutexLocker locker(&mutex);
+ paths.push_back(path);
+ moreAvailable = true;
}
- QMutexLocker locker(&mutex);
- paths.push_back(path);
- moreAvailable = true;
}
}
@@ -97,17 +104,18 @@ void PitchVis::paint(NoteGraphWidget* widget, int x1, int x2) {
pen.setCapStyle(Qt::RoundCap);
PitchVis::Paths const& paths = getPaths();
for (PitchVis::Paths::const_iterator it = paths.begin(), itend = paths.end(); it != itend; ++it) {
+ PitchPath::Fragments const& fragments = it->fragments;
int oldx, oldy;
// Only render paths in view
- if (widget->s2px(it->back().time) < x1) continue;
- else if (widget->s2px(it->front().time) > x2) break;
+ if (widget->s2px(fragments.back().time) < x1) continue;
+ else if (widget->s2px(fragments.front().time) > x2) break;
// Iterate through the path points
- for (PitchPath::const_iterator it2 = it->begin(), it2end = it->end(); it2 != it2end; ++it2) {
+ for (PitchPath::Fragments::const_iterator it2 = fragments.begin(), it2end = fragments.end(); it2 != it2end; ++it2) {
int x = widget->s2px(it2->time);
int y = widget->n2px(it2->note);
- pen.setColor(QColor(32, clamp<int>(127 + it2->level, 32, 255), 32));
+ pen.setColor(QColor(32 + 64 * it->channel, clamp<int>(127 + it2->level, 32, 255), 32, 128));
painter.setPen(pen);
- if (it2 != it->begin()) painter.drawLine(oldx, oldy, x, y);
+ if (it2 != fragments.begin()) painter.drawLine(oldx, oldy, x, y);
oldx = x; oldy = y;
}
}
@@ -120,10 +128,11 @@ int PitchVis::guessNote(double begin, double end, int note) {
if (note >= 0 || note < 48) score[note] = 10.0; // Slightly prefer the current note
// Score against paths
for (PitchVis::Paths::const_iterator it = paths.begin(), itend = paths.end(); it != itend; ++it) {
+ PitchPath::Fragments const& fragments = it->fragments;
// Discard paths completely outside the window
- if (it->back().time < begin) continue;
- if (it->front().time > end) break;
- for (PitchPath::const_iterator it2 = it->begin(), it2end = it->end(); it2 != it2end; ++it2) {
+ if (fragments.back().time < begin) continue;
+ if (fragments.front().time > end) break;
+ for (PitchPath::Fragments::const_iterator it2 = fragments.begin(), it2end = fragments.end(); it2 != it2end; ++it2) {
// Discard path points outside the window
if (it2->time < begin) continue;
if (it2->time > end) break;
diff --git a/pitchvis.hh b/pitchvis.hh
index b62ffd7..3b0c993 100644
--- a/pitchvis.hh
+++ b/pitchvis.hh
@@ -15,7 +15,12 @@ struct PitchFragment {
PitchFragment(float time, float note, float level): time(time), note(note), level(level) {}
};
-typedef std::vector<PitchFragment> PitchPath;
+struct PitchPath {
+ typedef std::vector<PitchFragment> Fragments;
+ Fragments fragments;
+ unsigned channel;
+ PitchPath(unsigned channel): channel(channel) {}
+};
class NoteGraphWidget;
|