2011-06-23 01:25:08 +02:00
|
|
|
/* This file is part of Clementine.
|
2014-11-29 20:07:01 +01:00
|
|
|
Copyright 2011, Tyler Rhodes <tyler.s.rhodes@gmail.com>
|
|
|
|
Copyright 2011-2012, 2014, David Sansome <me@davidsansome.com>
|
2015-11-21 16:20:47 +01:00
|
|
|
Copyright 2014, Alibek Omarov <a1ba.omarov@gmail.com>
|
2014-11-29 20:07:01 +01:00
|
|
|
Copyright 2014, John Maguire <john.maguire@gmail.com>
|
|
|
|
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
2015-11-21 16:20:47 +01:00
|
|
|
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
|
|
|
Copyright 2015, Arun Narayanankutty <n.arun.lifescience@gmail.com>
|
2017-03-11 18:14:11 +01:00
|
|
|
|
2011-06-23 01:25:08 +02:00
|
|
|
Clementine is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
Clementine is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2015-11-21 16:20:47 +01:00
|
|
|
#include "rainbowanalyzer.h"
|
2011-06-23 01:25:08 +02:00
|
|
|
|
2017-03-11 18:14:11 +01:00
|
|
|
#include <QBrush>
|
2018-11-29 22:32:12 +01:00
|
|
|
#include <QPainter>
|
|
|
|
#include <QPen>
|
|
|
|
#include <QTimerEvent>
|
2020-09-18 16:15:19 +02:00
|
|
|
#include <cmath>
|
2011-06-23 01:25:08 +02:00
|
|
|
|
2014-04-29 14:11:52 +02:00
|
|
|
#include "core/arraysize.h"
|
|
|
|
#include "core/logging.h"
|
|
|
|
|
2014-05-13 14:46:22 +02:00
|
|
|
using Analyzer::Scope;
|
|
|
|
|
2017-03-11 18:14:11 +01:00
|
|
|
const int Rainbow::RainbowAnalyzer::kHeight[] = {21, 33};
|
|
|
|
const int Rainbow::RainbowAnalyzer::kWidth[] = {34, 53};
|
|
|
|
const int Rainbow::RainbowAnalyzer::kFrameCount[] = {6, 16};
|
|
|
|
const int Rainbow::RainbowAnalyzer::kRainbowHeight[] = {21, 16};
|
|
|
|
const int Rainbow::RainbowAnalyzer::kRainbowOverlap[] = {13, 15};
|
|
|
|
const int Rainbow::RainbowAnalyzer::kSleepingHeight[] = {24, 33};
|
2015-11-21 16:20:47 +01:00
|
|
|
|
|
|
|
const char* Rainbow::NyanCatAnalyzer::kName = "Nyanalyzer Cat";
|
|
|
|
const char* Rainbow::RainbowDashAnalyzer::kName = "Rainbow Dash";
|
|
|
|
const float Rainbow::RainbowAnalyzer::kPixelScale = 0.02f;
|
2011-06-23 01:25:08 +02:00
|
|
|
|
2015-11-21 16:20:47 +01:00
|
|
|
Rainbow::RainbowAnalyzer::RainbowType Rainbow::RainbowAnalyzer::rainbowtype;
|
|
|
|
|
2017-03-11 18:14:11 +01:00
|
|
|
Rainbow::RainbowAnalyzer::RainbowAnalyzer(const RainbowType& rbtype,
|
|
|
|
QWidget* parent)
|
2014-02-07 16:34:20 +01:00
|
|
|
: Analyzer::Base(parent, 9),
|
|
|
|
timer_id_(startTimer(kFrameIntervalMs)),
|
|
|
|
frame_(0),
|
|
|
|
current_buffer_(0),
|
|
|
|
available_rainbow_width_(0),
|
|
|
|
px_per_frame_(0),
|
|
|
|
x_offset_(0),
|
|
|
|
background_brush_(QColor(0x0f, 0x43, 0x73)) {
|
2015-11-21 16:20:47 +01:00
|
|
|
rainbowtype = rbtype;
|
|
|
|
cat_dash_[0] = QPixmap(":/nyancat.png");
|
|
|
|
cat_dash_[1] = QPixmap(":/rainbowdash.png");
|
2014-05-23 13:31:50 +02:00
|
|
|
memset(history_, 0, sizeof(history_));
|
2011-06-23 01:25:08 +02:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int i = 0; i < kRainbowBands; ++i) {
|
2011-06-23 22:36:38 +02:00
|
|
|
colors_[i] = QPen(QColor::fromHsv(i * 255 / kRainbowBands, 255, 255),
|
2017-03-11 18:14:11 +01:00
|
|
|
kRainbowHeight[rainbowtype] / kRainbowBands,
|
2015-11-21 16:20:47 +01:00
|
|
|
Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin);
|
2011-06-23 22:36:30 +02:00
|
|
|
|
|
|
|
// pow constants computed so that
|
|
|
|
// | band_scale(0) | ~= .5 and | band_scale(5) | ~= 32
|
2014-02-07 16:34:20 +01:00
|
|
|
band_scale_[i] =
|
|
|
|
-std::cos(M_PI * i / (kRainbowBands - 1)) * 0.5 * std::pow(2.3, i);
|
2011-06-23 01:25:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-11 18:14:11 +01:00
|
|
|
void Rainbow::RainbowAnalyzer::transform(Scope& s) { fht_->spectrum(s.data()); }
|
2011-06-23 01:25:08 +02:00
|
|
|
|
2015-11-21 16:20:47 +01:00
|
|
|
void Rainbow::RainbowAnalyzer::timerEvent(QTimerEvent* e) {
|
2011-06-23 01:25:08 +02:00
|
|
|
if (e->timerId() == timer_id_) {
|
2015-11-21 16:20:47 +01:00
|
|
|
frame_ = (frame_ + 1) % kFrameCount[rainbowtype];
|
2011-06-23 01:25:08 +02:00
|
|
|
} else {
|
|
|
|
Analyzer::Base::timerEvent(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-21 16:20:47 +01:00
|
|
|
void Rainbow::RainbowAnalyzer::resizeEvent(QResizeEvent* e) {
|
2011-06-23 22:36:38 +02:00
|
|
|
// Invalidate the buffer so it's recreated from scratch in the next paint
|
|
|
|
// event.
|
2011-12-01 18:29:27 +01:00
|
|
|
buffer_[0] = QPixmap();
|
|
|
|
buffer_[1] = QPixmap();
|
2011-06-24 00:49:33 +02:00
|
|
|
|
2017-03-11 18:14:11 +01:00
|
|
|
available_rainbow_width_ =
|
|
|
|
width() - kWidth[rainbowtype] + kRainbowOverlap[rainbowtype];
|
Add "Psychedelic Colour" mode to all analyzers
(Well, except Nyanalyzer and Rainbow dash because they are already colourful enough.)
I have added functionality for any 2D analyzer to change any part of its colour palatte with the frequency content of the music, in the same way that Moodbars do.
I find this gives the analyzer a sort of "third dimention".
This is built into Analyzer::Base, so all analyzers can use it and override it as they please. I have thus added support for Block, Boom, Turbine, Sonogram, and Bar, however Boom and Block seem to look the best in my opinion.
This is of course all optional and is toggled by a checkbox in the context menu for the analyzer, disabled by default.
I have not been able to measure any increase in CPU activity with this enabled, even at 60fps.
2015-07-01 17:48:03 +02:00
|
|
|
px_per_frame_ =
|
|
|
|
static_cast<float>(available_rainbow_width_) / (kHistorySize - 1) + 1;
|
2014-02-07 16:34:20 +01:00
|
|
|
x_offset_ = px_per_frame_ * (kHistorySize - 1) - available_rainbow_width_;
|
2011-06-23 22:36:38 +02:00
|
|
|
}
|
|
|
|
|
2015-11-21 16:20:47 +01:00
|
|
|
void Rainbow::RainbowAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
|
2017-03-11 18:14:11 +01:00
|
|
|
bool new_frame) {
|
2011-06-23 01:25:08 +02:00
|
|
|
// Discard the second half of the transform
|
|
|
|
const int scope_size = s.size() / 2;
|
|
|
|
|
2012-10-16 12:20:56 +02:00
|
|
|
if ((new_frame && is_playing_) ||
|
|
|
|
(buffer_[0].isNull() && buffer_[1].isNull())) {
|
2011-06-23 22:36:14 +02:00
|
|
|
// Transform the music into rainbows!
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int band = 0; band < kRainbowBands; ++band) {
|
2011-06-23 22:36:14 +02:00
|
|
|
float* band_start = history_ + band * kHistorySize;
|
2011-06-23 01:25:08 +02:00
|
|
|
|
2011-06-23 22:36:14 +02:00
|
|
|
// Move the history of each band across by 1 frame.
|
|
|
|
memmove(band_start, band_start + 1, (kHistorySize - 1) * sizeof(float));
|
|
|
|
}
|
2011-06-23 01:25:08 +02:00
|
|
|
|
2011-06-23 22:36:14 +02:00
|
|
|
// Now accumulate the scope data into each band. Should maybe use a series
|
|
|
|
// of band pass filters for this, so bands can leak into neighbouring bands,
|
|
|
|
// but for now it's a series of separate square filters.
|
|
|
|
const int samples_per_band = scope_size / kRainbowBands;
|
|
|
|
int sample = 0;
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int band = 0; band < kRainbowBands; ++band) {
|
2011-06-23 22:36:30 +02:00
|
|
|
float accumulator = 0.0;
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int i = 0; i < samples_per_band; ++i) {
|
2011-06-23 22:36:30 +02:00
|
|
|
accumulator += s[sample++];
|
2011-06-23 22:36:14 +02:00
|
|
|
}
|
2011-06-23 22:36:30 +02:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
history_[(band + 1) * kHistorySize - 1] = accumulator * band_scale_[band];
|
2011-06-23 01:25:08 +02:00
|
|
|
}
|
|
|
|
|
2011-06-24 00:49:33 +02:00
|
|
|
// Create polylines for the rainbows.
|
|
|
|
QPointF polyline[kRainbowBands * kHistorySize];
|
|
|
|
QPointF* dest = polyline;
|
|
|
|
float* source = history_;
|
|
|
|
|
2017-03-11 18:14:11 +01:00
|
|
|
const float top_of = static_cast<float>(height()) / 2 -
|
|
|
|
static_cast<float>(kRainbowHeight[rainbowtype]) / 2;
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int band = 0; band < kRainbowBands; ++band) {
|
2011-06-24 00:49:33 +02:00
|
|
|
// Calculate the Y position of this band.
|
2017-03-11 18:14:11 +01:00
|
|
|
const float y = static_cast<float>(kRainbowHeight[rainbowtype]) /
|
|
|
|
(kRainbowBands + 1) * (band + 0.5) +
|
|
|
|
top_of;
|
2011-06-24 00:49:33 +02:00
|
|
|
|
|
|
|
// Add each point in the line.
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int x = 0; x < kHistorySize; ++x) {
|
|
|
|
*dest = QPointF(px_per_frame_ * x, y + *source * kPixelScale);
|
|
|
|
++dest;
|
|
|
|
++source;
|
2011-06-24 00:49:33 +02:00
|
|
|
}
|
2011-06-23 01:25:08 +02:00
|
|
|
}
|
|
|
|
|
2011-06-24 00:49:33 +02:00
|
|
|
// Do we have to draw the whole rainbow into the buffer?
|
2011-12-01 18:29:27 +01:00
|
|
|
if (buffer_[0].isNull()) {
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int i = 0; i < 2; ++i) {
|
2011-12-01 18:29:27 +01:00
|
|
|
buffer_[i] = QPixmap(QSize(width() + x_offset_, height()));
|
|
|
|
buffer_[i].fill(background_brush_.color());
|
|
|
|
}
|
|
|
|
current_buffer_ = 0;
|
2011-06-23 15:21:08 +02:00
|
|
|
|
2011-12-01 18:29:27 +01:00
|
|
|
QPainter buffer_painter(&buffer_[0]);
|
2011-06-24 00:49:33 +02:00
|
|
|
buffer_painter.setRenderHint(QPainter::Antialiasing);
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int band = kRainbowBands - 1; band >= 0; --band) {
|
2011-06-24 00:49:33 +02:00
|
|
|
buffer_painter.setPen(colors_[band]);
|
2014-02-07 16:34:20 +01:00
|
|
|
buffer_painter.drawPolyline(&polyline[band * kHistorySize],
|
|
|
|
kHistorySize);
|
|
|
|
buffer_painter.drawPolyline(&polyline[band * kHistorySize],
|
|
|
|
kHistorySize);
|
2011-06-24 00:49:33 +02:00
|
|
|
}
|
|
|
|
} else {
|
2011-12-01 18:29:27 +01:00
|
|
|
const int last_buffer = current_buffer_;
|
|
|
|
current_buffer_ = (current_buffer_ + 1) % 2;
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
// We can just shuffle the buffer along a bit and draw the new frame's
|
|
|
|
// data.
|
2011-12-01 18:29:27 +01:00
|
|
|
QPainter buffer_painter(&buffer_[current_buffer_]);
|
2011-06-24 00:49:33 +02:00
|
|
|
buffer_painter.setRenderHint(QPainter::Antialiasing);
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
buffer_painter.drawPixmap(
|
|
|
|
0, 0, buffer_[last_buffer], px_per_frame_, 0,
|
|
|
|
x_offset_ + available_rainbow_width_ - px_per_frame_, 0);
|
|
|
|
buffer_painter.fillRect(
|
|
|
|
x_offset_ + available_rainbow_width_ - px_per_frame_, 0,
|
2017-03-11 18:14:11 +01:00
|
|
|
kWidth[rainbowtype] - kRainbowOverlap[rainbowtype] + px_per_frame_,
|
2015-11-21 16:20:47 +01:00
|
|
|
height(), background_brush_);
|
2011-06-24 00:49:33 +02:00
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
for (int band = kRainbowBands - 1; band >= 0; --band) {
|
2011-06-24 00:49:33 +02:00
|
|
|
buffer_painter.setPen(colors_[band]);
|
2014-02-07 16:34:20 +01:00
|
|
|
buffer_painter.drawPolyline(&polyline[(band + 1) * kHistorySize - 3],
|
|
|
|
3);
|
2011-06-24 00:49:33 +02:00
|
|
|
}
|
2011-06-23 22:36:38 +02:00
|
|
|
}
|
2011-06-23 01:25:08 +02:00
|
|
|
}
|
|
|
|
|
2011-06-23 22:36:38 +02:00
|
|
|
// Draw the buffer on to the widget
|
2011-12-01 18:29:27 +01:00
|
|
|
p.drawPixmap(0, 0, buffer_[current_buffer_], x_offset_, 0, 0, 0);
|
2011-06-23 22:36:38 +02:00
|
|
|
|
2015-11-21 16:20:47 +01:00
|
|
|
// Draw rainbow analyzer (nyan cat or rainbowdash)
|
|
|
|
// Nyan nyan nyan nyan dash dash dash dash.
|
2012-10-16 12:20:56 +02:00
|
|
|
if (!is_playing_) {
|
|
|
|
// Ssshhh!
|
2017-03-11 18:14:11 +01:00
|
|
|
p.drawPixmap(SleepingDestRect(rainbowtype), cat_dash_[rainbowtype],
|
2015-11-21 16:20:47 +01:00
|
|
|
SleepingSourceRect(rainbowtype));
|
2012-10-16 12:20:56 +02:00
|
|
|
} else {
|
2017-03-11 18:14:11 +01:00
|
|
|
p.drawPixmap(DestRect(rainbowtype), cat_dash_[rainbowtype],
|
2015-11-21 16:20:47 +01:00
|
|
|
SourceRect(rainbowtype));
|
2012-10-16 12:20:56 +02:00
|
|
|
}
|
2011-06-23 01:25:08 +02:00
|
|
|
}
|
2015-11-21 16:20:47 +01:00
|
|
|
|
|
|
|
Rainbow::NyanCatAnalyzer::NyanCatAnalyzer(QWidget* parent)
|
2017-03-11 18:14:11 +01:00
|
|
|
: RainbowAnalyzer(Rainbow::RainbowAnalyzer::Nyancat, parent) {}
|
2015-11-21 16:20:47 +01:00
|
|
|
|
|
|
|
Rainbow::RainbowDashAnalyzer::RainbowDashAnalyzer(QWidget* parent)
|
2017-03-11 18:14:11 +01:00
|
|
|
: RainbowAnalyzer(Rainbow::RainbowAnalyzer::Dash, parent) {}
|