Merge branch 'master' into qt5
This commit is contained in:
commit
631a6cdabd
52
Changelog
52
Changelog
|
@ -2,9 +2,11 @@ Next release:
|
|||
Major features:
|
||||
* Vk.com support
|
||||
* Seafile support
|
||||
* Amazon Cloud Drive support
|
||||
* Add Ampache compatibility (through Subsonic service)
|
||||
* Add new analyzer "Rainbow Dash"
|
||||
* Answer to the ultimate question of life, the universe and everything
|
||||
* Add "Psychedelic Colour" mode to all analyzers
|
||||
|
||||
Other features:
|
||||
* Add left click to fullsize cover on playing widget
|
||||
|
@ -24,9 +26,8 @@ Next release:
|
|||
* Add a source icon for CD tracks
|
||||
* Allow user to remove directories
|
||||
* Add ability to remove unavailable items from playlist
|
||||
* Adds an import button to the transcode UI, allowing the user to pull in
|
||||
all files in a folder heirarchy to be transcoded
|
||||
* Allow user to pull in all files in a folder heirarchy to be transcoded
|
||||
* Add an import button to the transcode UI, allowing the user to pull in
|
||||
all files in a folder hierarchy to be transcoded
|
||||
* Make it impossible to collapse either side of the MainWindow splitter
|
||||
* Add menu items for updating and doing a full rescan of Google Drive
|
||||
* Increase Soundcloud cover image size
|
||||
|
@ -52,6 +53,22 @@ Next release:
|
|||
* Ability to add tracks to Spotify starred playlist by drag and drop
|
||||
* Add HipHop and Kuduro equalizers
|
||||
* Add AZLyrics lyric provider
|
||||
* Remember current playlist between restarts
|
||||
* (OSX) Use Alt+Tab to switch between playlist tabs
|
||||
* IDv3 tag lyrics support
|
||||
* Improve handling of Spotify Top Tracks and compilations
|
||||
* Scroll to last played track when switching playlists
|
||||
* Add stop after each song repeat mode
|
||||
* Sort discs numerically when using Group by disc
|
||||
* Add ability for sort by group and performer in the library view
|
||||
* Parse the year of a disc from musicbrainz
|
||||
* Add track intro mode
|
||||
* Add ability to add a search term with tab and space in the smart
|
||||
playlist window
|
||||
* Add love/ban (lastfm) global shortcuts
|
||||
* Add support for "original year" tags
|
||||
* Send album artist to Last.fm with liblastfm >= 1.0.0
|
||||
* Add sample rate selection
|
||||
|
||||
Bugfixes:
|
||||
* Fix crash when click on a SoundCloud entry in internet tab
|
||||
|
@ -90,8 +107,6 @@ Next release:
|
|||
* Fix moodbars not generating correctly
|
||||
* Fix socket leak in moodbar
|
||||
* Fix memory leak in tagreader
|
||||
* Remove Ubuntu One support
|
||||
* Remove Discogs support
|
||||
* Fix crash when trying to fingerprint but missing a plugin
|
||||
* Fix infinite scan with Subsonic when the library is empty
|
||||
* Fix shortcut/media keys issues on Mac
|
||||
|
@ -101,6 +116,26 @@ Next release:
|
|||
* Fix laggy interface on Mac
|
||||
* Fix crash in GrooveShark
|
||||
* Fix playback breaks in Spotify
|
||||
* Fix memory leaks
|
||||
* Fix crash when stopping song that is fading after pausing
|
||||
* Fix crash when trying to download a track but there is no current one playing
|
||||
* (OSX) Fix Soundcloud API Search which simply doesn't work
|
||||
* Fix default spinner gif image which shows white pixels around the image
|
||||
* Fix setting album artist tag for FLAC files if it already exists
|
||||
* Fix crash when Clementine lists the albums on Ampache
|
||||
* Fix Last.fm scrobbling after seek
|
||||
* Fix metadata not processed properly for some streams (Akamai)
|
||||
* Fix save state when the song was paused
|
||||
* (Windows) Fix context menu for the "now playing widget"
|
||||
* Fix some issues in Boom and Turbine analyzers
|
||||
* Fix song continuously rewinding when seeking using keyboard arrow keys
|
||||
* Fix OSD re-posistioning which doesn't work on multiple monitors
|
||||
* Fix Sonogram state while paused
|
||||
|
||||
Removed features:
|
||||
* Remove Ubuntu One support
|
||||
* Remove Discogs support
|
||||
* Remove GrooveShark support
|
||||
|
||||
Build system changes:
|
||||
* Update to gstreamer 1.0
|
||||
|
@ -113,6 +148,13 @@ Next release:
|
|||
dependency
|
||||
* (Debian/Ubuntu) Add libmygpo-qt-dev (=> 1.0.7)
|
||||
* Remove internal copy of libechonest and add it as dependency
|
||||
* (Windows) Uninstall a previous install of Clementine when installing a new
|
||||
version
|
||||
* (Windows) Remember the last installation path
|
||||
* (GNU/Linux) Follow freedesktop.org specifications for icons
|
||||
* (GNU/Linux) Add a 128x128 version of the Clementine icon
|
||||
* (OSX) Use a GTlsDatabase for gstreamer SSL
|
||||
* Use libcrypto++ instead of QCA
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
|
||||
UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
|
||||
dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
|
||||
MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
|
||||
dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
|
||||
AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
|
||||
BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
|
||||
cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
|
||||
AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
|
||||
MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
|
||||
aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
|
||||
ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
|
||||
IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
|
||||
MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
|
||||
A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
|
||||
7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
|
||||
1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
|
||||
-----END CERTIFICATE-----
|
|
@ -1,5 +1,6 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>Equifax_Secure_Certificate_Authority.pem</file>
|
||||
<file>blank.ttf</file>
|
||||
<file>clementine_remote_qr.png</file>
|
||||
<file>clementine-spotify-public.pem</file>
|
||||
|
@ -330,7 +331,6 @@
|
|||
<file>providers/myspace.png</file>
|
||||
<file>providers/podcast16.png</file>
|
||||
<file>providers/podcast32.png</file>
|
||||
<file>providers/radiogfm.png</file>
|
||||
<file>providers/rockradio.png</file>
|
||||
<file>providers/skydrive.png</file>
|
||||
<file>providers/somafm.png</file>
|
||||
|
@ -388,6 +388,7 @@
|
|||
<file>schema/schema-49.sql</file>
|
||||
<file>schema/schema-4.sql</file>
|
||||
<file>schema/schema-5.sql</file>
|
||||
<file>schema/schema-50.sql</file>
|
||||
<file>schema/schema-6.sql</file>
|
||||
<file>schema/schema-7.sql</file>
|
||||
<file>schema/schema-8.sql</file>
|
||||
|
@ -406,7 +407,6 @@
|
|||
<file>volumeslider-gradient.png</file>
|
||||
<file>volumeslider-handle_glow.png</file>
|
||||
<file>volumeslider-handle.png</file>
|
||||
<file>volumeslider-inset.png</file>
|
||||
<file>vk/add.png</file>
|
||||
<file>vk/bookmarks.png</file>
|
||||
<file>vk/delete.png</file>
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
<invalidIndicator value=""iTotalRecords": 0"/>
|
||||
<invalidIndicator value="lyrics not available"/>
|
||||
</provider>
|
||||
<provider name="letras.mus.br" title="" charset="iso-8859-1" url="http://letras.terra.com.br/winamp.php?musica={title}&artista={artist}">
|
||||
<provider name="letras.mus.br" title="" charset="utf-8" url="http://letras.terra.com.br/winamp.php?musica={title}&artista={artist}">
|
||||
<urlFormat replace="_@,;&\/"" with="_"/>
|
||||
<urlFormat replace=" " with="+"/>
|
||||
<extract>
|
||||
|
@ -207,7 +207,7 @@
|
|||
<invalidIndicator value="Sorry, we have no"/>
|
||||
<invalidIndicator value="This is an upcoming album and we do not have the"/>
|
||||
</provider>
|
||||
<provider name="tekstowo.pl (Polish translations)" title="{artist} - {title} - tekst" charset="utf-8" url="http://www.tekstowo.pl/piosenka,{artist},{title}.html">
|
||||
<provider name="tekstowo.pl (Original lyric language)" title="{artist} - {title} - tekst" charset="utf-8" url="http://www.tekstowo.pl/piosenka,{artist},{title}.html">
|
||||
<urlFormat replace=" _@,;&\/'"." with="_"/>
|
||||
<extract>
|
||||
<item begin="<div class="song-text">" end="<a href="javascript:;""/>
|
||||
|
@ -219,6 +219,15 @@
|
|||
<item begin="<h2>" end="</h2><br />"/>
|
||||
</exclude>
|
||||
</provider>
|
||||
<provider name="tekstowo.pl (Translated to Polish)" title="{artist} - {title} - tekst" charset="utf-8" url="http://www.tekstowo.pl/piosenka,{artist},{title}.html">
|
||||
<urlFormat replace=" _@,;&\/'"." with="_"/>
|
||||
<extract>
|
||||
<item begin="<div id="translation" class=" end="<a href="/>
|
||||
</extract>
|
||||
<exclude>
|
||||
<item begin=""id-" end="">"/>
|
||||
</exclude>
|
||||
</provider>
|
||||
<provider name="teksty.org" title="{artist} - {title} - tekst" charset="utf-8" url="http://teksty.org/{artist},{title},tekst-piosenki">
|
||||
<urlFormat replace=" _@,;&\/"'" with="-"/>
|
||||
<urlFormat replace="." with=""/>
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 676 B |
|
@ -59,7 +59,10 @@ CREATE TABLE device_%deviceid_songs (
|
|||
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
lyrics TEXT
|
||||
lyrics TEXT,
|
||||
|
||||
originalyear INTEGER,
|
||||
effective_originalyear INTEGER
|
||||
);
|
||||
|
||||
CREATE INDEX idx_device_%deviceid_songs_album ON device_%deviceid_songs (album);
|
||||
|
|
|
@ -46,7 +46,10 @@ CREATE TABLE jamendo.songs (
|
|||
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
lyrics TEXT
|
||||
lyrics TEXT,
|
||||
|
||||
originalyear INTEGER,
|
||||
effective_originalyear INTEGER
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE jamendo.songs_fts USING fts3(
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
ALTER TABLE %allsongstables ADD COLUMN originalyear INTEGER;
|
||||
|
||||
ALTER TABLE %allsongstables ADD COLUMN effective_originalyear INTEGER;
|
||||
|
||||
UPDATE songs SET originalyear = -1;
|
||||
|
||||
UPDATE songs SET effective_originalyear = -1;
|
||||
|
||||
UPDATE schema_version SET version=50;
|
Binary file not shown.
Before Width: | Height: | Size: 203 B After Width: | Height: | Size: 384 B |
Binary file not shown.
Before Width: | Height: | Size: 707 B |
|
@ -281,6 +281,7 @@ Section "Clementine" Clementine
|
|||
File "libgnutls-28.dll"
|
||||
File "libgobject-2.0-0.dll"
|
||||
File "libgpg-error-0.dll"
|
||||
File "libgpod.dll"
|
||||
File "libgstapp-1.0-0.dll"
|
||||
File "libgstaudio-1.0-0.dll"
|
||||
File "libgstbase-1.0-0.dll"
|
||||
|
@ -314,6 +315,7 @@ Section "Clementine" Clementine
|
|||
File "libqjson.dll"
|
||||
File "libspeex-1.dll"
|
||||
File "libspotify.dll"
|
||||
File "libsqlite3-0.dll"
|
||||
File "libstdc++-6.dll"
|
||||
File "libtag.dll"
|
||||
File "libtasn1-6.dll"
|
||||
|
@ -1091,6 +1093,7 @@ Section "Uninstall"
|
|||
Delete "$INSTDIR\libgnutls-28.dll"
|
||||
Delete "$INSTDIR\libgobject-2.0-0.dll"
|
||||
Delete "$INSTDIR\libgpg-error-0.dll"
|
||||
Delete "$INSTDIR\libgpod.dll"
|
||||
Delete "$INSTDIR\libgstapp-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgstaudio-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgstbase-1.0-0.dll"
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
|
||||
UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
|
||||
dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
|
||||
MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
|
||||
dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
|
||||
AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
|
||||
BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
|
||||
cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
|
||||
AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
|
||||
MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
|
||||
aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
|
||||
ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
|
||||
IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
|
||||
MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
|
||||
A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
|
||||
7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
|
||||
1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
|
||||
-----END CERTIFICATE-----
|
|
@ -1,5 +1,6 @@
|
|||
<RCC>
|
||||
<qresource prefix="/certs">
|
||||
<file>Equifax_Secure_Certificate_Authority.pem</file>
|
||||
<file>godaddy-root.pem</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -57,6 +57,8 @@ int main(int argc, char** argv) {
|
|||
|
||||
QSslSocket::addDefaultCaCertificates(
|
||||
QSslCertificate::fromPath(":/certs/godaddy-root.pem", QSsl::Pem));
|
||||
QSslSocket::addDefaultCaCertificates(QSslCertificate::fromPath(
|
||||
":/certs/Equifax_Secure_Certificate_Authority.pem", QSsl::Pem));
|
||||
|
||||
TagReaderWorker worker(&socket);
|
||||
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
#include "closure.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/timeconstants.h"
|
||||
|
||||
namespace _detail {
|
||||
|
@ -65,11 +63,3 @@ void DoInAMinuteOrSo(QObject* receiver, const char* slot) {
|
|||
int msec = (60 + (qrand() % 60)) * kMsecPerSec;
|
||||
DoAfter(receiver, slot, msec);
|
||||
}
|
||||
|
||||
void DoAfter(std::function<void()> callback, int msec) {
|
||||
QTimer* timer = new QTimer;
|
||||
timer->setSingleShot(true);
|
||||
NewClosure(timer, SIGNAL(timeout()), callback);
|
||||
QObject::connect(timer, SIGNAL(timeout()), timer, SLOT(deleteLater()));
|
||||
timer->start(msec);
|
||||
}
|
||||
|
|
|
@ -18,12 +18,14 @@
|
|||
#ifndef CLOSURE_H
|
||||
#define CLOSURE_H
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <QMetaMethod>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QTimer>
|
||||
|
||||
namespace _detail {
|
||||
|
||||
|
@ -188,7 +190,19 @@ _detail::ClosureBase* NewClosure(QObject* sender, const char* signal,
|
|||
}
|
||||
|
||||
void DoAfter(QObject* receiver, const char* slot, int msec);
|
||||
void DoAfter(std::function<void()> callback, int msec);
|
||||
void DoAfter(std::function<void()> callback, std::chrono::milliseconds msec);
|
||||
void DoInAMinuteOrSo(QObject* receiver, const char* slot);
|
||||
|
||||
template <typename R, typename P>
|
||||
void DoAfter(
|
||||
std::function<void()> callback, std::chrono::duration<R, P> duration) {
|
||||
QTimer* timer = new QTimer;
|
||||
timer->setSingleShot(true);
|
||||
NewClosure(timer, SIGNAL(timeout()), callback);
|
||||
QObject::connect(timer, SIGNAL(timeout()), timer, SLOT(deleteLater()));
|
||||
std::chrono::milliseconds msec =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(duration);
|
||||
timer->start(msec.count());
|
||||
}
|
||||
|
||||
#endif // CLOSURE_H
|
||||
|
|
|
@ -285,3 +285,17 @@ QDebug CreateLoggerDebug(int line, const char *class_name) { return qCreateLogge
|
|||
#endif // QT_NO_DEBUG_OUTPUT
|
||||
|
||||
} // namespace logging
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename T>
|
||||
QString print_duration(T duration, const std::string& unit) {
|
||||
return QString("%1%2").arg(duration.count()).arg(unit.c_str());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QDebug operator<<(QDebug dbg, std::chrono::seconds secs) {
|
||||
dbg.nospace() << print_duration(secs, "s");
|
||||
return dbg.space();
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
#ifndef LOGGING_H
|
||||
#define LOGGING_H
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#ifdef QT_NO_DEBUG_STREAM
|
||||
|
@ -84,4 +87,6 @@ void GLog(const char* domain, int level, const char* message, void* user_data);
|
|||
extern const char* kDefaultLogLevels;
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, std::chrono::seconds secs);
|
||||
|
||||
#endif // LOGGING_H
|
||||
|
|
|
@ -69,6 +69,24 @@ enum EngineState {
|
|||
|
||||
// Song Metadata
|
||||
message SongMetadata {
|
||||
enum Type {
|
||||
UNKNOWN = 0;
|
||||
ASF = 1;
|
||||
FLAC = 2;
|
||||
MP4 = 3;
|
||||
MPC = 4;
|
||||
MPEG = 5;
|
||||
OGGFLAC = 6;
|
||||
OGGSPEEX = 7;
|
||||
OGGVORBIS = 8;
|
||||
AIFF = 9;
|
||||
WAV = 10;
|
||||
TRUEAUDIO = 11;
|
||||
CDDA = 12;
|
||||
OGGOPUS = 13;
|
||||
STREAM = 99;
|
||||
}
|
||||
|
||||
optional int32 id = 1; // unique id of the song
|
||||
optional int32 index = 2; // Index of the current row of the active playlist
|
||||
optional string title = 3;
|
||||
|
@ -88,6 +106,9 @@ message SongMetadata {
|
|||
optional int32 file_size = 17;
|
||||
optional float rating = 18; // 0 (0 stars) to 1 (5 stars)
|
||||
optional string url = 19;
|
||||
optional string art_automatic = 20;
|
||||
optional string art_manual = 21;
|
||||
optional Type type = 22;
|
||||
}
|
||||
|
||||
// Playlist informations
|
||||
|
@ -106,6 +127,7 @@ enum RepeatMode {
|
|||
Repeat_Album = 2;
|
||||
Repeat_Playlist = 3;
|
||||
Repeat_OneByOne = 4;
|
||||
Repeat_Intro = 5;
|
||||
}
|
||||
|
||||
// Valid Shuffle modes
|
||||
|
@ -217,6 +239,7 @@ message RequestInsertUrls {
|
|||
optional int32 position = 3 [default=-1];
|
||||
optional bool play_now = 4 [default=false];
|
||||
optional bool enqueue = 5 [default=false];
|
||||
repeated SongMetadata songs = 6;
|
||||
}
|
||||
|
||||
// Client want to change track
|
||||
|
@ -320,7 +343,7 @@ message ResponseGlobalSearchStatus {
|
|||
|
||||
// The message itself
|
||||
message Message {
|
||||
optional int32 version = 1 [default=20];
|
||||
optional int32 version = 1 [default=21];
|
||||
optional MsgType type = 2 [default=UNKNOWN]; // What data is in the message?
|
||||
|
||||
optional RequestConnect request_connect = 21;
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <QNetworkAccessManager>
|
||||
#include <QTextCodec>
|
||||
#include <QUrl>
|
||||
#include <QVector>
|
||||
|
||||
#include <aifffile.h>
|
||||
#include <asffile.h>
|
||||
|
@ -48,6 +49,7 @@
|
|||
#include <textidentificationframe.h>
|
||||
#include <trueaudiofile.h>
|
||||
#include <tstring.h>
|
||||
#include <unsynchronizedlyricsframe.h>
|
||||
#include <vorbisfile.h>
|
||||
#include <wavfile.h>
|
||||
|
||||
|
@ -106,6 +108,14 @@ const char* TagReader::kMP4_FMPS_Playcount_ID =
|
|||
const char* TagReader::kMP4_FMPS_Score_ID =
|
||||
"----:com.apple.iTunes:FMPS_Rating_Amarok_Score";
|
||||
|
||||
namespace {
|
||||
// Tags containing the year the album was originally released (in contrast to
|
||||
// other tags that contain the release year of the current edition)
|
||||
const char* kMP4_OriginalYear_ID = "----:com.apple.iTunes:ORIGINAL YEAR";
|
||||
const char* kASF_OriginalDate_ID = "WM/OriginalReleaseTime";
|
||||
const char* kASF_OriginalYear_ID = "WM/OriginalReleaseYear";
|
||||
}
|
||||
|
||||
TagReader::TagReader()
|
||||
: factory_(new TagLibFileRefFactory),
|
||||
network_(new QNetworkAccessManager),
|
||||
|
@ -189,11 +199,21 @@ void TagReader::ReadFile(const QString& filename,
|
|||
compilation =
|
||||
TStringToQString(map["TCMP"].front()->toString()).trimmed();
|
||||
|
||||
if (!map["TDOR"].isEmpty()) {
|
||||
song->set_originalyear(
|
||||
map["TDOR"].front()->toString().substr(0, 4).toInt());
|
||||
} else if (!map["TORY"].isEmpty()) {
|
||||
song->set_originalyear(
|
||||
map["TORY"].front()->toString().substr(0, 4).toInt());
|
||||
}
|
||||
|
||||
if (!map["USLT"].isEmpty()) {
|
||||
lyrics = TStringToQString((map["USLT"].front())->toString()).trimmed();
|
||||
qLog(Debug) << "Read ULST lyrics " << lyrics;
|
||||
} else if (!map["SYLT"].isEmpty())
|
||||
lyrics = TStringToQString((map["SYLT"].front())->toString()).trimmed();
|
||||
Decode(map["USLT"].front()->toString(), nullptr,
|
||||
song->mutable_lyrics());
|
||||
} else if (!map["SYLT"].isEmpty()) {
|
||||
Decode(map["SYLT"].front()->toString(), nullptr,
|
||||
song->mutable_lyrics());
|
||||
}
|
||||
|
||||
if (!map["APIC"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
|
||||
|
||||
|
@ -310,6 +330,15 @@ void TagReader::ReadFile(const QString& filename,
|
|||
Decode(items["\251grp"].toStringList().toString(" "), nullptr,
|
||||
song->mutable_grouping());
|
||||
}
|
||||
|
||||
if (items.contains(kMP4_OriginalYear_ID)) {
|
||||
song->set_originalyear(
|
||||
TStringToQString(
|
||||
items[kMP4_OriginalYear_ID].toStringList().toString('\n'))
|
||||
.left(4)
|
||||
.toInt());
|
||||
}
|
||||
|
||||
Decode(mp4_tag->comment(), nullptr, song->mutable_comment());
|
||||
}
|
||||
}
|
||||
|
@ -350,6 +379,22 @@ void TagReader::ReadFile(const QString& filename,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attributes_map.contains(kASF_OriginalDate_ID)) {
|
||||
const TagLib::ASF::AttributeList& attributes =
|
||||
attributes_map[kASF_OriginalDate_ID];
|
||||
if (!attributes.isEmpty()) {
|
||||
song->set_originalyear(
|
||||
TStringToQString(attributes.front().toString()).left(4).toInt());
|
||||
}
|
||||
} else if (attributes_map.contains(kASF_OriginalYear_ID)) {
|
||||
const TagLib::ASF::AttributeList& attributes =
|
||||
attributes_map[kASF_OriginalYear_ID];
|
||||
if (!attributes.isEmpty()) {
|
||||
song->set_originalyear(
|
||||
TStringToQString(attributes.front().toString()).left(4).toInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (tag) {
|
||||
|
@ -486,6 +531,13 @@ void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap& map,
|
|||
Decode(map["ALBUM ARTIST"].front(), codec, song->mutable_albumartist());
|
||||
}
|
||||
|
||||
if (!map["ORIGINALDATE"].isEmpty())
|
||||
song->set_originalyear(
|
||||
TStringToQString(map["ORIGINALDATE"].front()).left(4).toInt());
|
||||
else if (!map["ORIGINALYEAR"].isEmpty())
|
||||
song->set_originalyear(
|
||||
TStringToQString(map["ORIGINALYEAR"].front()).toInt());
|
||||
|
||||
if (!map["BPM"].isEmpty())
|
||||
song->set_bpm(TStringToQString(map["BPM"].front()).trimmed().toFloat());
|
||||
|
||||
|
@ -628,7 +680,7 @@ bool TagReader::SaveFile(const QString& filename,
|
|||
SetTextFrame("TCOM", song.composer(), tag);
|
||||
SetTextFrame("TIT1", song.grouping(), tag);
|
||||
SetTextFrame("TOPE", song.performer(), tag);
|
||||
SetTextFrame("USLT", song.lyrics(), tag);
|
||||
SetUnsyncLyricsFrame(song.lyrics(), tag);
|
||||
// Skip TPE1 (which is the artist) here because we already set it
|
||||
SetTextFrame("TPE2", song.albumartist(), tag);
|
||||
SetTextFrame("TCMP", std::string(song.compilation() ? "1" : "0"), tag);
|
||||
|
@ -842,19 +894,67 @@ void TagReader::SetTextFrame(const char* id, const QString& value,
|
|||
void TagReader::SetTextFrame(const char* id, const std::string& value,
|
||||
TagLib::ID3v2::Tag* tag) const {
|
||||
TagLib::ByteVector id_vector(id);
|
||||
QVector<TagLib::ByteVector> frames_buffer;
|
||||
|
||||
// Remove the frame if it already exists
|
||||
// Store and clear existing frames
|
||||
while (tag->frameListMap().contains(id_vector) &&
|
||||
tag->frameListMap()[id_vector].size() != 0) {
|
||||
frames_buffer.push_back(tag->frameListMap()[id_vector].front()->render());
|
||||
tag->removeFrame(tag->frameListMap()[id_vector].front());
|
||||
}
|
||||
|
||||
// Create and add a new frame
|
||||
TagLib::ID3v2::TextIdentificationFrame* frame =
|
||||
new TagLib::ID3v2::TextIdentificationFrame(id_vector,
|
||||
// If no frames stored create empty frame
|
||||
if (frames_buffer.isEmpty()) {
|
||||
TagLib::ID3v2::TextIdentificationFrame frame(id_vector,
|
||||
TagLib::String::UTF8);
|
||||
frames_buffer.push_back(frame.render());
|
||||
}
|
||||
|
||||
// Update and add the frames
|
||||
for (int lyrics_index = 0; lyrics_index < frames_buffer.size();
|
||||
lyrics_index++) {
|
||||
TagLib::ID3v2::TextIdentificationFrame* frame =
|
||||
new TagLib::ID3v2::TextIdentificationFrame(
|
||||
frames_buffer.at(lyrics_index));
|
||||
if (lyrics_index == 0) {
|
||||
frame->setText(StdStringToTaglibString(value));
|
||||
}
|
||||
// add frame takes ownership and clears the memory
|
||||
tag->addFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
void TagReader::SetUnsyncLyricsFrame(const std::string& value,
|
||||
TagLib::ID3v2::Tag* tag) const {
|
||||
TagLib::ByteVector id_vector("USLT");
|
||||
QVector<TagLib::ByteVector> frames_buffer;
|
||||
|
||||
// Store and clear existing frames
|
||||
while (tag->frameListMap().contains(id_vector) &&
|
||||
tag->frameListMap()[id_vector].size() != 0) {
|
||||
frames_buffer.push_back(tag->frameListMap()[id_vector].front()->render());
|
||||
tag->removeFrame(tag->frameListMap()[id_vector].front());
|
||||
}
|
||||
|
||||
// If no frames stored create empty frame
|
||||
if (frames_buffer.isEmpty()) {
|
||||
TagLib::ID3v2::UnsynchronizedLyricsFrame frame(TagLib::String::UTF8);
|
||||
frame.setDescription("Clementine editor");
|
||||
frames_buffer.push_back(frame.render());
|
||||
}
|
||||
|
||||
// Update and add the frames
|
||||
for (int lyrics_index = 0; lyrics_index < frames_buffer.size();
|
||||
lyrics_index++) {
|
||||
TagLib::ID3v2::UnsynchronizedLyricsFrame* frame =
|
||||
new TagLib::ID3v2::UnsynchronizedLyricsFrame(
|
||||
frames_buffer.at(lyrics_index));
|
||||
if (lyrics_index == 0) {
|
||||
frame->setText(StdStringToTaglibString(value));
|
||||
}
|
||||
// add frame takes ownership and clears the memory
|
||||
tag->addFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
bool TagReader::IsMediaFile(const QString& filename) const {
|
||||
|
|
|
@ -87,12 +87,12 @@ class TagReader {
|
|||
void SetFMPSStatisticsVorbisComments(
|
||||
TagLib::Ogg::XiphComment* vorbis_comments,
|
||||
const pb::tagreader::SongMetadata& song) const;
|
||||
void SetFMPSRatingVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
|
||||
const pb::tagreader::SongMetadata& song)
|
||||
const;
|
||||
void SetFMPSRatingVorbisComments(
|
||||
TagLib::Ogg::XiphComment* vorbis_comments,
|
||||
const pb::tagreader::SongMetadata& song) const;
|
||||
|
||||
pb::tagreader::SongMetadata_Type GuessFileType(TagLib::FileRef* fileref)
|
||||
const;
|
||||
pb::tagreader::SongMetadata_Type GuessFileType(
|
||||
TagLib::FileRef* fileref) const;
|
||||
|
||||
void SetUserTextFrame(const QString& description, const QString& value,
|
||||
TagLib::ID3v2::Tag* tag) const;
|
||||
|
@ -104,6 +104,8 @@ class TagReader {
|
|||
TagLib::ID3v2::Tag* tag) const;
|
||||
void SetTextFrame(const char* id, const std::string& value,
|
||||
TagLib::ID3v2::Tag* tag) const;
|
||||
void SetUnsyncLyricsFrame(const std::string& value,
|
||||
TagLib::ID3v2::Tag* tag) const;
|
||||
|
||||
private:
|
||||
static const char* kMP4_FMPS_Rating_ID;
|
||||
|
|
|
@ -52,6 +52,7 @@ message SongMetadata {
|
|||
optional string performer = 31;
|
||||
optional string grouping = 32;
|
||||
optional string lyrics = 33;
|
||||
optional int32 originalyear = 34;
|
||||
}
|
||||
|
||||
message ReadFileRequest {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Copyright 2003, Max Howell <max.howell@methylblue.com>
|
||||
Copyright 2009, 2011-2012, David Sansome <me@davidsansome.com>
|
||||
Copyright 2010, 2012, 2014, John Maguire <john.maguire@gmail.com>
|
||||
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
||||
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
|
@ -33,6 +33,7 @@
|
|||
#include <QtDebug>
|
||||
|
||||
#include "engines/enginebase.h"
|
||||
#include "core/arraysize.h"
|
||||
|
||||
// INSTRUCTIONS Base2D
|
||||
// 1. do anything that depends on height() in init(), Base2D will call it before
|
||||
|
@ -45,7 +46,8 @@
|
|||
// TODO(David Sansome): make an INSTRUCTIONS file
|
||||
// can't mod scope in analyze you have to use transform
|
||||
|
||||
// TODO(John Maguire): for 2D use setErasePixmap Qt function insetead of m_background
|
||||
// TODO(John Maguire): for 2D use setErasePixmap Qt function insetead of
|
||||
// m_background
|
||||
|
||||
// make the linker happy only for gcc < 4.0
|
||||
#if !(__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 0)) && \
|
||||
|
@ -53,20 +55,29 @@
|
|||
template class Analyzer::Base<QWidget>;
|
||||
#endif
|
||||
|
||||
static const int sBarkBands[] = {
|
||||
100, 200, 300, 400, 510, 630, 770, 920, 1080, 1270, 1480, 1720,
|
||||
2000, 2320, 2700, 3150, 3700, 4400, 5300, 6400, 7700, 9500, 12000, 15500};
|
||||
|
||||
static const int sBarkBandCount = arraysize(sBarkBands);
|
||||
|
||||
Analyzer::Base::Base(QWidget* parent, uint scopeSize)
|
||||
: QWidget(parent),
|
||||
m_timeout(40) // msec
|
||||
timeout_(40) // msec
|
||||
,
|
||||
m_fht(new FHT(scopeSize)),
|
||||
m_engine(nullptr),
|
||||
m_lastScope(512),
|
||||
current_chunk_(0),
|
||||
fht_(new FHT(scopeSize)),
|
||||
engine_(nullptr),
|
||||
lastScope_(512),
|
||||
new_frame_(false),
|
||||
is_playing_(false) {}
|
||||
is_playing_(false),
|
||||
barkband_table_(QList<uint>()),
|
||||
prev_color_index_(0),
|
||||
bands_(0),
|
||||
psychedelic_enabled_(false) {}
|
||||
|
||||
void Analyzer::Base::hideEvent(QHideEvent*) { m_timer.stop(); }
|
||||
void Analyzer::Base::hideEvent(QHideEvent*) { timer_.stop(); }
|
||||
|
||||
void Analyzer::Base::showEvent(QShowEvent*) { m_timer.start(timeout(), this); }
|
||||
void Analyzer::Base::showEvent(QShowEvent*) { timer_.start(timeout(), this); }
|
||||
|
||||
void Analyzer::Base::transform(Scope& scope) {
|
||||
// this is a standard transformation that should give
|
||||
|
@ -74,16 +85,16 @@ void Analyzer::Base::transform(Scope& scope) {
|
|||
|
||||
// NOTE resizing here is redundant as FHT routines only calculate FHT::size()
|
||||
// values
|
||||
// scope.resize( m_fht->size() );
|
||||
// scope.resize( fht_->size() );
|
||||
|
||||
float* front = static_cast<float*>(&scope.front());
|
||||
|
||||
float* f = new float[m_fht->size()];
|
||||
m_fht->copy(&f[0], front);
|
||||
m_fht->logSpectrum(front, &f[0]);
|
||||
m_fht->scale(front, 1.0 / 20);
|
||||
float* f = new float[fht_->size()];
|
||||
fht_->copy(&f[0], front);
|
||||
fht_->logSpectrum(front, &f[0]);
|
||||
fht_->scale(front, 1.0 / 20);
|
||||
|
||||
scope.resize(m_fht->size() / 2); // second half of values are rubbish
|
||||
scope.resize(fht_->size() / 2); // second half of values are rubbish
|
||||
delete[] f;
|
||||
}
|
||||
|
||||
|
@ -91,30 +102,30 @@ void Analyzer::Base::paintEvent(QPaintEvent* e) {
|
|||
QPainter p(this);
|
||||
p.fillRect(e->rect(), palette().color(QPalette::Window));
|
||||
|
||||
switch (m_engine->state()) {
|
||||
switch (engine_->state()) {
|
||||
case Engine::Playing: {
|
||||
const Engine::Scope& thescope = m_engine->scope(m_timeout);
|
||||
const Engine::Scope& thescope = engine_->scope(timeout_);
|
||||
int i = 0;
|
||||
|
||||
// convert to mono here - our built in analyzers need mono, but the
|
||||
// engines provide interleaved pcm
|
||||
for (uint x = 0; static_cast<int>(x) < m_fht->size(); ++x) {
|
||||
m_lastScope[x] =
|
||||
static_cast<double>(thescope[i] + thescope[i + 1]) / (2 * (1 << 15));
|
||||
for (uint x = 0; static_cast<int>(x) < fht_->size(); ++x) {
|
||||
lastScope_[x] = static_cast<double>(thescope[i] + thescope[i + 1]) /
|
||||
(2 * (1 << 15));
|
||||
i += 2;
|
||||
}
|
||||
|
||||
is_playing_ = true;
|
||||
transform(m_lastScope);
|
||||
analyze(p, m_lastScope, new_frame_);
|
||||
transform(lastScope_);
|
||||
analyze(p, lastScope_, new_frame_);
|
||||
|
||||
// scope.resize( m_fht->size() );
|
||||
// scope.resize( fht_->size() );
|
||||
|
||||
break;
|
||||
}
|
||||
case Engine::Paused:
|
||||
is_playing_ = false;
|
||||
analyze(p, m_lastScope, new_frame_);
|
||||
analyze(p, lastScope_, new_frame_);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -131,9 +142,9 @@ int Analyzer::Base::resizeExponent(int exp) {
|
|||
else if (exp > 9)
|
||||
exp = 9;
|
||||
|
||||
if (exp != m_fht->sizeExp()) {
|
||||
delete m_fht;
|
||||
m_fht = new FHT(exp);
|
||||
if (exp != fht_->sizeExp()) {
|
||||
delete fht_;
|
||||
fht_ = new FHT(exp);
|
||||
}
|
||||
return exp;
|
||||
}
|
||||
|
@ -154,7 +165,7 @@ int Analyzer::Base::resizeForBands(int bands) {
|
|||
exp = 9;
|
||||
|
||||
resizeExponent(exp);
|
||||
return m_fht->size() / 2;
|
||||
return fht_->size() / 2;
|
||||
}
|
||||
|
||||
void Analyzer::Base::demo(QPainter& p) {
|
||||
|
@ -175,6 +186,67 @@ void Analyzer::Base::demo(QPainter& p) {
|
|||
++t;
|
||||
}
|
||||
|
||||
void Analyzer::Base::psychedelicModeChanged(bool enabled) {
|
||||
psychedelic_enabled_ = enabled;
|
||||
}
|
||||
|
||||
int Analyzer::Base::BandFrequency(int band) const {
|
||||
return ((kSampleRate / 2) * band + kSampleRate / 4) / bands_;
|
||||
}
|
||||
|
||||
void Analyzer::Base::updateBandSize(const int scopeSize) {
|
||||
// prevent possible dbz in BandFrequency
|
||||
if (scopeSize == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bands_ = scopeSize;
|
||||
|
||||
barkband_table_.clear();
|
||||
barkband_table_.reserve(bands_ + 1);
|
||||
|
||||
int barkband = 0;
|
||||
for (int i = 0; i < bands_ + 1; ++i) {
|
||||
if (barkband < sBarkBandCount - 1 &&
|
||||
BandFrequency(i) >= sBarkBands[barkband]) {
|
||||
barkband++;
|
||||
}
|
||||
|
||||
barkband_table_.append(barkband);
|
||||
}
|
||||
}
|
||||
|
||||
QColor Analyzer::Base::getPsychedelicColor(const Scope& scope,
|
||||
const int ampFactor,
|
||||
const int bias) {
|
||||
if (scope.size() > barkband_table_.length()) {
|
||||
return palette().color(QPalette::Highlight);
|
||||
}
|
||||
|
||||
// Calculate total magnitudes for different bark bands.
|
||||
double bands[sBarkBandCount]{};
|
||||
|
||||
for (int i = 0; i < barkband_table_.size(); ++i) {
|
||||
bands[barkband_table_[i]] += scope[i];
|
||||
}
|
||||
|
||||
// Now divide the bark bands into thirds and compute their total amplitudes.
|
||||
double rgb[3]{};
|
||||
for (int i = 0; i < sBarkBandCount - 1; ++i) {
|
||||
rgb[(i * 3) / sBarkBandCount] += bands[i] * bands[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
// bias colours for a threshold around normally amplified audio
|
||||
rgb[i] = (int)((sqrt(rgb[i]) * ampFactor) + bias);
|
||||
if (rgb[i] > 255) {
|
||||
rgb[i] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
return QColor::fromRgb(rgb[0], rgb[1], rgb[2]);
|
||||
}
|
||||
|
||||
void Analyzer::Base::polishEvent() {
|
||||
init(); // virtual
|
||||
}
|
||||
|
@ -211,7 +283,7 @@ void Analyzer::initSin(Scope& v, const uint size) {
|
|||
|
||||
void Analyzer::Base::timerEvent(QTimerEvent* e) {
|
||||
QWidget::timerEvent(e);
|
||||
if (e->timerId() != m_timer.timerId()) return;
|
||||
if (e->timerId() != timer_.timerId()) return;
|
||||
|
||||
new_frame_ = true;
|
||||
update();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
|
||||
Copyright 2011, Arnaud Bienner <arnaud.bienner@gmail.com>
|
||||
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
||||
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
|
@ -58,21 +58,22 @@ class Base : public QWidget {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
~Base() { delete m_fht; }
|
||||
~Base() { delete fht_; }
|
||||
|
||||
uint timeout() const { return m_timeout; }
|
||||
uint timeout() const { return timeout_; }
|
||||
|
||||
void set_engine(EngineBase* engine) { m_engine = engine; }
|
||||
void set_engine(EngineBase* engine) { engine_ = engine; }
|
||||
|
||||
void changeTimeout(uint newTimeout) {
|
||||
m_timeout = newTimeout;
|
||||
if (m_timer.isActive()) {
|
||||
m_timer.stop();
|
||||
m_timer.start(m_timeout, this);
|
||||
timeout_ = newTimeout;
|
||||
if (timer_.isActive()) {
|
||||
timer_.stop();
|
||||
timer_.start(timeout_, this);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void framerateChanged() {}
|
||||
virtual void psychedelicModeChanged(bool);
|
||||
|
||||
protected:
|
||||
explicit Base(QWidget*, uint scopeSize = 7);
|
||||
|
@ -86,21 +87,32 @@ class Base : public QWidget {
|
|||
|
||||
int resizeExponent(int);
|
||||
int resizeForBands(int);
|
||||
int BandFrequency(int) const;
|
||||
void updateBandSize(const int);
|
||||
QColor getPsychedelicColor(const Scope&, const int, const int);
|
||||
virtual void init() {}
|
||||
virtual void transform(Scope&);
|
||||
virtual void analyze(QPainter& p, const Scope&, bool new_frame) = 0;
|
||||
virtual void demo(QPainter& p);
|
||||
|
||||
protected:
|
||||
QBasicTimer m_timer;
|
||||
uint m_timeout;
|
||||
FHT* m_fht;
|
||||
EngineBase* m_engine;
|
||||
Scope m_lastScope;
|
||||
int current_chunk_;
|
||||
static const int kSampleRate =
|
||||
44100; // we shouldn't need to care about ultrasonics
|
||||
|
||||
QBasicTimer timer_;
|
||||
uint timeout_;
|
||||
FHT* fht_;
|
||||
EngineBase* engine_;
|
||||
Scope lastScope_;
|
||||
|
||||
bool new_frame_;
|
||||
bool is_playing_;
|
||||
|
||||
QList<uint> barkband_table_;
|
||||
double prev_colors_[10][3];
|
||||
int prev_color_index_;
|
||||
int bands_;
|
||||
bool psychedelic_enabled_;
|
||||
};
|
||||
|
||||
void interpolate(const Scope&, Scope&);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
|
||||
Copyright 2011-2012, Arnaud Bienner <arnaud.bienner@gmail.com>
|
||||
Copyright 2013, Vasily Fomin <vasili.fomin@gmail.com>
|
||||
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
||||
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
|
@ -57,6 +57,7 @@ AnalyzerContainer::AnalyzerContainer(QWidget* parent)
|
|||
visualisation_action_(nullptr),
|
||||
double_click_timer_(new QTimer(this)),
|
||||
ignore_next_click_(false),
|
||||
psychedelic_colors_on_(false),
|
||||
current_analyzer_(nullptr),
|
||||
engine_(nullptr) {
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
|
@ -88,6 +89,11 @@ AnalyzerContainer::AnalyzerContainer(QWidget* parent)
|
|||
disable_action_->setCheckable(true);
|
||||
group_->addAction(disable_action_);
|
||||
|
||||
context_menu_->addSeparator();
|
||||
psychedelic_enable_ = context_menu_->addAction(
|
||||
tr("Use Psychedelic Colors"), this, SLOT(TogglePsychedelicColors()));
|
||||
psychedelic_enable_->setCheckable(true);
|
||||
|
||||
context_menu_->addSeparator();
|
||||
// Visualisation action gets added in SetActions
|
||||
|
||||
|
@ -145,6 +151,14 @@ void AnalyzerContainer::DisableAnalyzer() {
|
|||
Save();
|
||||
}
|
||||
|
||||
void AnalyzerContainer::TogglePsychedelicColors() {
|
||||
psychedelic_colors_on_ = !psychedelic_colors_on_;
|
||||
if (current_analyzer_) {
|
||||
current_analyzer_->psychedelicModeChanged(psychedelic_colors_on_);
|
||||
}
|
||||
SavePsychedelic();
|
||||
}
|
||||
|
||||
void AnalyzerContainer::ChangeAnalyzer(int id) {
|
||||
QObject* instance = analyzer_types_[id]->newInstance(Q_ARG(QWidget*, this));
|
||||
|
||||
|
@ -161,6 +175,7 @@ void AnalyzerContainer::ChangeAnalyzer(int id) {
|
|||
current_framerate_ =
|
||||
current_framerate_ == 0 ? kMediumFramerate : current_framerate_;
|
||||
current_analyzer_->changeTimeout(1000 / current_framerate_);
|
||||
current_analyzer_->psychedelicModeChanged(psychedelic_colors_on_);
|
||||
|
||||
layout()->addWidget(current_analyzer_);
|
||||
|
||||
|
@ -183,6 +198,10 @@ void AnalyzerContainer::Load() {
|
|||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
// Colours
|
||||
psychedelic_colors_on_ = s.value("psychedelic", false).toBool();
|
||||
psychedelic_enable_->setChecked(psychedelic_colors_on_);
|
||||
|
||||
// Analyzer
|
||||
QString type = s.value("type", "BlockAnalyzer").toString();
|
||||
if (type.isEmpty()) {
|
||||
|
@ -227,6 +246,13 @@ void AnalyzerContainer::Save() {
|
|||
: QVariant());
|
||||
}
|
||||
|
||||
void AnalyzerContainer::SavePsychedelic() {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
s.setValue("psychedelic", psychedelic_colors_on_);
|
||||
}
|
||||
|
||||
void AnalyzerContainer::AddFramerate(const QString& name, int framerate) {
|
||||
QAction* action =
|
||||
context_menu_framerate_->addAction(name, mapper_framerate_, SLOT(map()));
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
Copyright 2011-2012, Arnaud Bienner <arnaud.bienner@gmail.com>
|
||||
Copyright 2013, Vasily Fomin <vasili.fomin@gmail.com>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
Copyright 2015, Mark Furneaux <mark@furneaux.ca>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -40,7 +41,7 @@ class AnalyzerContainer : public QWidget {
|
|||
static const char* kSettingsGroup;
|
||||
static const char* kSettingsFramerate;
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void WheelEvent(int delta);
|
||||
|
||||
protected:
|
||||
|
@ -53,6 +54,7 @@ class AnalyzerContainer : public QWidget {
|
|||
void ChangeFramerate(int new_framerate);
|
||||
void DisableAnalyzer();
|
||||
void ShowPopupMenu();
|
||||
void TogglePsychedelicColors();
|
||||
|
||||
private:
|
||||
static const int kLowFramerate;
|
||||
|
@ -63,6 +65,7 @@ class AnalyzerContainer : public QWidget {
|
|||
void Load();
|
||||
void Save();
|
||||
void SaveFramerate(int framerate);
|
||||
void SavePsychedelic();
|
||||
template <typename T>
|
||||
void AddAnalyzerType();
|
||||
void AddFramerate(const QString& name, int framerate);
|
||||
|
@ -80,11 +83,13 @@ class AnalyzerContainer : public QWidget {
|
|||
QList<int> framerate_list_;
|
||||
QList<QAction*> actions_;
|
||||
QAction* disable_action_;
|
||||
QAction* psychedelic_enable_;
|
||||
|
||||
QAction* visualisation_action_;
|
||||
QTimer* double_click_timer_;
|
||||
QPoint last_click_pos_;
|
||||
bool ignore_next_click_;
|
||||
bool psychedelic_colors_on_;
|
||||
|
||||
Analyzer::Base* current_analyzer_;
|
||||
EngineBase* engine_;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Copyright 2003, Mark Kretschmann <markey@web.de>
|
||||
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||
Copyright 2014, Alibek Omarov <a1ba.omarov@gmail.com>
|
||||
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
||||
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
Copyright 2014, John Maguire <john.maguire@gmail.com>
|
||||
|
||||
|
@ -23,7 +23,6 @@
|
|||
/* Original Author: Mark Kretschmann <markey@web.de> 2003
|
||||
*/
|
||||
|
||||
|
||||
#include "baranalyzer.h"
|
||||
#include <cmath>
|
||||
#include <QtDebug>
|
||||
|
@ -36,18 +35,19 @@ const char* BarAnalyzer::kName =
|
|||
|
||||
BarAnalyzer::BarAnalyzer(QWidget* parent) : Analyzer::Base(parent, 8) {
|
||||
// roof pixmaps don't depend on size() so we do in the ctor
|
||||
m_bg = parent->palette().color(QPalette::Background);
|
||||
bg_ = parent->palette().color(QPalette::Background);
|
||||
|
||||
QColor fg(parent->palette().color(QPalette::Highlight).lighter(150));
|
||||
|
||||
double dr = static_cast<double>(m_bg.red() - fg.red()) /
|
||||
(NUM_ROOFS - 1); // -1 because we start loop below at 0
|
||||
double dg = static_cast<double>(m_bg.green() - fg.green()) / (NUM_ROOFS - 1);
|
||||
double db = static_cast<double>(m_bg.blue() - fg.blue()) / (NUM_ROOFS - 1);
|
||||
double dr = static_cast<double>(bg_.red() - fg.red()) /
|
||||
(kNumRoofs - 1); // -1 because we start loop below at 0
|
||||
double dg = static_cast<double>(bg_.green() - fg.green()) / (kNumRoofs - 1);
|
||||
double db = static_cast<double>(bg_.blue() - fg.blue()) / (kNumRoofs - 1);
|
||||
|
||||
for (uint i = 0; i < NUM_ROOFS; ++i) {
|
||||
m_pixRoof[i] = QPixmap(COLUMN_WIDTH, 1);
|
||||
m_pixRoof[i].fill(QColor(fg.red() + static_cast<int>(dr * i), fg.green() + static_cast<int>(dg * i),
|
||||
for (uint i = 0; i < kNumRoofs; ++i) {
|
||||
pixRoof_[i] = QPixmap(kColumnWidth, 1);
|
||||
pixRoof_[i].fill(QColor(fg.red() + static_cast<int>(dr * i),
|
||||
fg.green() + static_cast<int>(dg * i),
|
||||
fg.blue() + static_cast<int>(db * i)));
|
||||
}
|
||||
}
|
||||
|
@ -58,46 +58,66 @@ void BarAnalyzer::resizeEvent(QResizeEvent* e) { init(); }
|
|||
|
||||
void BarAnalyzer::init() {
|
||||
const double MAX_AMPLITUDE = 1.0;
|
||||
const double F = static_cast<double>(height() - 2) / (log10(255) * MAX_AMPLITUDE);
|
||||
const double F =
|
||||
static_cast<double>(height() - 2) / (log10(255) * MAX_AMPLITUDE);
|
||||
|
||||
BAND_COUNT = width() / 5;
|
||||
MAX_DOWN = static_cast<int>(0 - (qMax(1, height() / 50)));
|
||||
MAX_UP = static_cast<int>(qMax(1, height() / 25));
|
||||
band_count_ = width() / 5;
|
||||
max_down_ = static_cast<int>(0 - (qMax(1, height() / 50)));
|
||||
max_up_ = static_cast<int>(qMax(1, height() / 25));
|
||||
|
||||
barVector.resize(BAND_COUNT, 0);
|
||||
roofVector.resize(BAND_COUNT, height() - 5);
|
||||
roofVelocityVector.resize(BAND_COUNT, ROOF_VELOCITY_REDUCTION_FACTOR);
|
||||
m_roofMem.resize(BAND_COUNT);
|
||||
m_scope.resize(BAND_COUNT);
|
||||
barVector_.resize(band_count_, 0);
|
||||
roofVector_.resize(band_count_, height() - 5);
|
||||
roofVelocityVector_.resize(band_count_, kRoofVelocityReductionFactor);
|
||||
roofMem_.resize(band_count_);
|
||||
scope_.resize(band_count_);
|
||||
|
||||
// generate a list of values that express amplitudes in range 0-MAX_AMP as
|
||||
// ints from 0-height() on log scale
|
||||
for (uint x = 0; x < 256; ++x) {
|
||||
m_lvlMapper[x] = static_cast<uint>(F * log10(x + 1));
|
||||
lvlMapper_[x] = static_cast<uint>(F * log10(x + 1));
|
||||
}
|
||||
|
||||
m_pixBarGradient = QPixmap(height() * COLUMN_WIDTH, height());
|
||||
m_pixCompose = QPixmap(size());
|
||||
pixBarGradient_ = QPixmap(height() * kColumnWidth, height());
|
||||
pixCompose_ = QPixmap(size());
|
||||
canvas_ = QPixmap(size());
|
||||
canvas_.fill(palette().color(QPalette::Background));
|
||||
|
||||
QPainter p(&m_pixBarGradient);
|
||||
QColor rgb(palette().color(QPalette::Highlight));
|
||||
updateBandSize(band_count_);
|
||||
colorChanged();
|
||||
setMinimumSize(QSize(band_count_ * kColumnWidth, 10));
|
||||
}
|
||||
|
||||
for (int x = 0, r = rgb.red(), g = rgb.green(), b = rgb.blue(), r2 = 255 - r; x < height();
|
||||
++x) {
|
||||
void BarAnalyzer::colorChanged() {
|
||||
if (pixBarGradient_.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QPainter p(&pixBarGradient_);
|
||||
QColor rgb;
|
||||
if (psychedelic_enabled_) {
|
||||
rgb = getPsychedelicColor(scope_, 50, 100);
|
||||
} else {
|
||||
rgb = palette().color(QPalette::Highlight);
|
||||
}
|
||||
|
||||
for (int x = 0, r = rgb.red(), g = rgb.green(), b = rgb.blue(), r2 = 255 - r;
|
||||
x < height(); ++x) {
|
||||
for (int y = x; y > 0; --y) {
|
||||
const double fraction = static_cast<double>(y) / height();
|
||||
|
||||
// p.setPen( QColor( r + (int)(r2 * fraction), g, b - (int)(255 *
|
||||
// fraction) ) );
|
||||
p.setPen(QColor(r + static_cast<int>(r2 * fraction), g, b));
|
||||
p.drawLine(x * COLUMN_WIDTH, height() - y, (x + 1) * COLUMN_WIDTH,
|
||||
p.drawLine(x * kColumnWidth, height() - y, (x + 1) * kColumnWidth,
|
||||
height() - y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setMinimumSize(QSize(BAND_COUNT * COLUMN_WIDTH, 10));
|
||||
void BarAnalyzer::psychedelicModeChanged(bool enabled) {
|
||||
psychedelic_enabled_ = enabled;
|
||||
// reset colours back to normal
|
||||
colorChanged();
|
||||
}
|
||||
|
||||
void BarAnalyzer::analyze(QPainter& p, const Scope& s, bool new_frame) {
|
||||
|
@ -107,20 +127,25 @@ void BarAnalyzer::analyze(QPainter& p, const Scope& s, bool new_frame) {
|
|||
}
|
||||
// Analyzer::interpolate( s, m_bands );
|
||||
|
||||
Scope& v = m_scope;
|
||||
Analyzer::interpolate(s, v);
|
||||
Analyzer::interpolate(s, scope_);
|
||||
QPainter canvas_painter(&canvas_);
|
||||
|
||||
// update the graphics with the new colour
|
||||
if (psychedelic_enabled_) {
|
||||
colorChanged();
|
||||
}
|
||||
|
||||
canvas_.fill(palette().color(QPalette::Background));
|
||||
|
||||
for (uint i = 0, x = 0, y2; i < v.size(); ++i, x += COLUMN_WIDTH + 1) {
|
||||
for (uint i = 0, x = 0, y2; i < scope_.size(); ++i, x += kColumnWidth + 1) {
|
||||
// assign pre[log10]'d value
|
||||
y2 = static_cast<uint>(v[i] *
|
||||
y2 = static_cast<uint>(
|
||||
scope_[i] *
|
||||
256); // 256 will be optimised to a bitshift //no, it's a float
|
||||
y2 = m_lvlMapper[(y2 > 255) ? 255 : y2]; // lvlMapper is array of ints with
|
||||
y2 = lvlMapper_[(y2 > 255) ? 255 : y2]; // lvlMapper is array of ints with
|
||||
// values 0 to height()
|
||||
|
||||
int change = y2 - barVector[i];
|
||||
int change = y2 - barVector_[i];
|
||||
|
||||
// using the best of Markey's, piggz and Max's ideas on the way to shift the
|
||||
// bars
|
||||
|
@ -129,54 +154,52 @@ void BarAnalyzer::analyze(QPainter& p, const Scope& s, bool new_frame) {
|
|||
// 2. shift large upwards with a bias towards last value
|
||||
// 3. fall downwards at a constant pace
|
||||
|
||||
/*if ( change > MAX_UP ) //anything too much greater than 2 gives "jitter"
|
||||
/*if ( change > max_up_ ) //anything too much greater than 2 gives "jitter"
|
||||
//add some dynamics - makes the value slightly closer to what it was last time
|
||||
y2 = ( barVector[i] + MAX_UP );
|
||||
//y2 = ( barVector[i] * 2 + y2 ) / 3;
|
||||
y2 = ( barVector_[i] + max_up_ );
|
||||
//y2 = ( barVector_[i] * 2 + y2 ) / 3;
|
||||
else*/ if (change <
|
||||
MAX_DOWN)
|
||||
y2 = barVector[i] + MAX_DOWN;
|
||||
max_down_)
|
||||
y2 = barVector_[i] + max_down_;
|
||||
|
||||
if (static_cast<int>(y2) > roofVector[i]) {
|
||||
roofVector[i] = static_cast<int>(y2);
|
||||
roofVelocityVector[i] = 1;
|
||||
if (static_cast<int>(y2) > roofVector_[i]) {
|
||||
roofVector_[i] = static_cast<int>(y2);
|
||||
roofVelocityVector_[i] = 1;
|
||||
}
|
||||
|
||||
// remember where we are
|
||||
barVector[i] = y2;
|
||||
barVector_[i] = y2;
|
||||
|
||||
if (m_roofMem[i].size() > NUM_ROOFS)
|
||||
m_roofMem[i].erase(m_roofMem[i].begin());
|
||||
if (roofMem_[i].size() > kNumRoofs) roofMem_[i].erase(roofMem_[i].begin());
|
||||
|
||||
// blt last n roofs, a.k.a motion blur
|
||||
for (uint c = 0; c < m_roofMem[i].size(); ++c)
|
||||
// bitBlt( m_pComposePixmap, x, m_roofMem[i]->at( c ), m_roofPixmaps[ c ]
|
||||
for (uint c = 0; c < roofMem_[i].size(); ++c)
|
||||
// bitBlt( m_pComposePixmap, x, roofMem_[i]->at( c ), m_roofPixmaps[ c ]
|
||||
// );
|
||||
// bitBlt( canvas(), x, m_roofMem[i][c], &m_pixRoof[ NUM_ROOFS - 1 - c ]
|
||||
// bitBlt( canvas(), x, roofMem_[i][c], &pixRoof_[ kNumRoofs - 1 - c ]
|
||||
// );
|
||||
canvas_painter.drawPixmap(x, m_roofMem[i][c],
|
||||
m_pixRoof[NUM_ROOFS - 1 - c]);
|
||||
canvas_painter.drawPixmap(x, roofMem_[i][c], pixRoof_[kNumRoofs - 1 - c]);
|
||||
|
||||
// blt the bar
|
||||
canvas_painter.drawPixmap(x, height() - y2, *gradient(), y2 * COLUMN_WIDTH,
|
||||
height() - y2, COLUMN_WIDTH, y2);
|
||||
canvas_painter.drawPixmap(x, height() - y2, *gradient(), y2 * kColumnWidth,
|
||||
height() - y2, kColumnWidth, y2);
|
||||
/*bitBlt( canvas(), x, height() - y2,
|
||||
gradient(), y2 * COLUMN_WIDTH, height() - y2, COLUMN_WIDTH, y2,
|
||||
gradient(), y2 * kColumnWidth, height() - y2, kColumnWidth, y2,
|
||||
Qt::CopyROP );*/
|
||||
|
||||
m_roofMem[i].push_back(height() - roofVector[i] - 2);
|
||||
roofMem_[i].push_back(height() - roofVector_[i] - 2);
|
||||
|
||||
// set roof parameters for the NEXT draw
|
||||
if (roofVelocityVector[i] != 0) {
|
||||
if (roofVelocityVector[i] > 32) // no reason to do == 32
|
||||
roofVector[i] -=
|
||||
(roofVelocityVector[i] - 32) / 20; // trivial calculation
|
||||
if (roofVelocityVector_[i] != 0) {
|
||||
if (roofVelocityVector_[i] > 32) // no reason to do == 32
|
||||
roofVector_[i] -=
|
||||
(roofVelocityVector_[i] - 32) / 20; // trivial calculation
|
||||
|
||||
if (roofVector[i] < 0) {
|
||||
roofVector[i] = 0; // not strictly necessary
|
||||
roofVelocityVector[i] = 0;
|
||||
if (roofVector_[i] < 0) {
|
||||
roofVector_[i] = 0; // not strictly necessary
|
||||
roofVelocityVector_[i] = 0;
|
||||
} else {
|
||||
++roofVelocityVector[i];
|
||||
++roofVelocityVector_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Copyright 2003-2005, Max Howell <max.howell@methylblue.com>
|
||||
Copyright 2005, Mark Kretschmann <markey@web.de>
|
||||
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
||||
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||
Copyright 2014, Krzysztof A. Sobiecki <sobkas@gmail.com>
|
||||
Copyright 2014, John Maguire <john.maguire@gmail.com>
|
||||
|
||||
|
@ -39,44 +39,45 @@ class BarAnalyzer : public Analyzer::Base {
|
|||
|
||||
void init();
|
||||
virtual void analyze(QPainter& p, const Analyzer::Scope&, bool new_frame);
|
||||
// virtual void transform( Scope& );
|
||||
virtual void psychedelicModeChanged(bool);
|
||||
|
||||
/**
|
||||
* Resizes the widget to a new geometry according to @p e
|
||||
* @param e The resize-event
|
||||
*/
|
||||
void resizeEvent(QResizeEvent* e);
|
||||
void colorChanged();
|
||||
|
||||
uint BAND_COUNT;
|
||||
int MAX_DOWN;
|
||||
int MAX_UP;
|
||||
static const uint ROOF_HOLD_TIME = 48;
|
||||
static const int ROOF_VELOCITY_REDUCTION_FACTOR = 32;
|
||||
static const uint NUM_ROOFS = 16;
|
||||
static const uint COLUMN_WIDTH = 4;
|
||||
uint band_count_;
|
||||
int max_down_;
|
||||
int max_up_;
|
||||
static const uint kRoofHoldTime = 48;
|
||||
static const int kRoofVelocityReductionFactor = 32;
|
||||
static const uint kNumRoofs = 16;
|
||||
static const uint kColumnWidth = 4;
|
||||
|
||||
static const char* kName;
|
||||
|
||||
protected:
|
||||
QPixmap m_pixRoof[NUM_ROOFS];
|
||||
// vector<uint> m_roofMem[BAND_COUNT];
|
||||
QPixmap pixRoof_[kNumRoofs];
|
||||
// vector<uint> roofMem_[band_count_];
|
||||
|
||||
// Scope m_bands; //copy of the Scope to prevent creating/destroying a Scope
|
||||
// every iteration
|
||||
uint m_lvlMapper[256];
|
||||
std::vector<aroofMemVec> m_roofMem;
|
||||
std::vector<uint> barVector; // positions of bars
|
||||
std::vector<int> roofVector; // positions of roofs
|
||||
std::vector<uint> roofVelocityVector; // speed that roofs falls
|
||||
uint lvlMapper_[256];
|
||||
std::vector<aroofMemVec> roofMem_;
|
||||
std::vector<uint> barVector_; // positions of bars
|
||||
std::vector<int> roofVector_; // positions of roofs
|
||||
std::vector<uint> roofVelocityVector_; // speed that roofs falls
|
||||
|
||||
const QPixmap* gradient() const { return &m_pixBarGradient; }
|
||||
const QPixmap* gradient() const { return &pixBarGradient_; }
|
||||
|
||||
private:
|
||||
QPixmap m_pixBarGradient;
|
||||
QPixmap m_pixCompose;
|
||||
QPixmap pixBarGradient_;
|
||||
QPixmap pixCompose_;
|
||||
QPixmap canvas_;
|
||||
Analyzer::Scope m_scope; // so we don't create a vector every frame
|
||||
QColor m_bg;
|
||||
Analyzer::Scope scope_; // so we don't create a vector every frame
|
||||
QColor bg_;
|
||||
};
|
||||
|
||||
#endif // ANALYZERS_BARANALYZER_H_
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright 2005, Mark Kretschmann <markey@web.de>
|
||||
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
|
||||
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
||||
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
|
@ -33,35 +33,34 @@
|
|||
#include <cstdlib>
|
||||
#include <QPainter>
|
||||
|
||||
const uint BlockAnalyzer::HEIGHT = 2;
|
||||
const uint BlockAnalyzer::WIDTH = 4;
|
||||
const uint BlockAnalyzer::MIN_ROWS = 3; // arbituary
|
||||
const uint BlockAnalyzer::MIN_COLUMNS = 32; // arbituary
|
||||
const uint BlockAnalyzer::MAX_COLUMNS = 256; // must be 2**n
|
||||
const uint BlockAnalyzer::FADE_SIZE = 90;
|
||||
const uint BlockAnalyzer::kHeight = 2;
|
||||
const uint BlockAnalyzer::kWidth = 4;
|
||||
const uint BlockAnalyzer::kMinRows = 3; // arbituary
|
||||
const uint BlockAnalyzer::kMinColumns = 32; // arbituary
|
||||
const uint BlockAnalyzer::kMaxColumns = 256; // must be 2**n
|
||||
const uint BlockAnalyzer::kFadeSize = 90;
|
||||
|
||||
const char* BlockAnalyzer::kName =
|
||||
QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer");
|
||||
|
||||
BlockAnalyzer::BlockAnalyzer(QWidget* parent)
|
||||
: Analyzer::Base(parent, 9),
|
||||
m_columns(0),
|
||||
m_rows(0),
|
||||
m_y(0),
|
||||
m_barPixmap(1, 1),
|
||||
m_topBarPixmap(WIDTH, HEIGHT),
|
||||
m_scope(MIN_COLUMNS),
|
||||
m_store(1 << 8, 0),
|
||||
m_fade_bars(FADE_SIZE),
|
||||
m_fade_pos(1 << 8, 50),
|
||||
m_fade_intensity(1 << 8, 32) {
|
||||
setMinimumSize(MIN_COLUMNS * (WIDTH + 1) - 1,
|
||||
MIN_ROWS * (HEIGHT + 1) - 1);
|
||||
columns_(0),
|
||||
rows_(0),
|
||||
y_(0),
|
||||
barPixmap_(1, 1),
|
||||
topBarPixmap_(kWidth, kHeight),
|
||||
scope_(kMinColumns),
|
||||
store_(1 << 8, 0),
|
||||
fade_bars_(kFadeSize),
|
||||
fade_pos_(1 << 8, 50),
|
||||
fade_intensity_(1 << 8, 32) {
|
||||
setMinimumSize(kMinColumns * (kWidth + 1) - 1, kMinRows * (kHeight + 1) - 1);
|
||||
// -1 is padding, no drawing takes place there
|
||||
setMaximumWidth(MAX_COLUMNS * (WIDTH + 1) - 1);
|
||||
setMaximumWidth(kMaxColumns * (kWidth + 1) - 1);
|
||||
|
||||
// mxcl says null pixmaps cause crashes, so let's play it safe
|
||||
for (uint i = 0; i < FADE_SIZE; ++i) m_fade_bars[i] = QPixmap(1, 1);
|
||||
for (uint i = 0; i < kFadeSize; ++i) fade_bars_[i] = QPixmap(1, 1);
|
||||
}
|
||||
|
||||
BlockAnalyzer::~BlockAnalyzer() {}
|
||||
|
@ -69,41 +68,44 @@ BlockAnalyzer::~BlockAnalyzer() {}
|
|||
void BlockAnalyzer::resizeEvent(QResizeEvent* e) {
|
||||
QWidget::resizeEvent(e);
|
||||
|
||||
m_background = QPixmap(size());
|
||||
background_ = QPixmap(size());
|
||||
canvas_ = QPixmap(size());
|
||||
|
||||
const uint oldRows = m_rows;
|
||||
const uint oldRows = rows_;
|
||||
|
||||
// all is explained in analyze()..
|
||||
// +1 to counter -1 in maxSizes, trust me we need this!
|
||||
m_columns = qMax(static_cast<uint>(static_cast<double>(width() + 1) / (WIDTH + 1)), MAX_COLUMNS);
|
||||
m_rows = static_cast<uint>(static_cast<double>(height() + 1) / (HEIGHT + 1));
|
||||
columns_ = qMin(
|
||||
static_cast<uint>(static_cast<double>(width() + 1) / (kWidth + 1)) + 1,
|
||||
kMaxColumns);
|
||||
rows_ = static_cast<uint>(static_cast<double>(height() + 1) / (kHeight + 1));
|
||||
|
||||
// this is the y-offset for drawing from the top of the widget
|
||||
m_y = (height() - (m_rows * (HEIGHT + 1)) + 2) / 2;
|
||||
y_ = (height() - (rows_ * (kHeight + 1)) + 2) / 2;
|
||||
|
||||
m_scope.resize(m_columns);
|
||||
scope_.resize(columns_);
|
||||
|
||||
if (m_rows != oldRows) {
|
||||
m_barPixmap = QPixmap(WIDTH, m_rows * (HEIGHT + 1));
|
||||
if (rows_ != oldRows) {
|
||||
barPixmap_ = QPixmap(kWidth, rows_ * (kHeight + 1));
|
||||
|
||||
for (uint i = 0; i < FADE_SIZE; ++i)
|
||||
m_fade_bars[i] = QPixmap(WIDTH, m_rows * (HEIGHT + 1));
|
||||
for (uint i = 0; i < kFadeSize; ++i)
|
||||
fade_bars_[i] = QPixmap(kWidth, rows_ * (kHeight + 1));
|
||||
|
||||
m_yscale.resize(m_rows + 1);
|
||||
yscale_.resize(rows_ + 1);
|
||||
|
||||
const uint PRE = 1,
|
||||
PRO = 1; // PRE and PRO allow us to restrict the range somewhat
|
||||
|
||||
for (uint z = 0; z < m_rows; ++z)
|
||||
m_yscale[z] = 1 - (log10(PRE + z) / log10(PRE + m_rows + PRO));
|
||||
for (uint z = 0; z < rows_; ++z)
|
||||
yscale_[z] = 1 - (log10(PRE + z) / log10(PRE + rows_ + PRO));
|
||||
|
||||
m_yscale[m_rows] = 0;
|
||||
yscale_[rows_] = 0;
|
||||
|
||||
determineStep();
|
||||
paletteChange(palette());
|
||||
}
|
||||
|
||||
updateBandSize(columns_);
|
||||
drawBackground();
|
||||
}
|
||||
|
||||
|
@ -113,9 +115,9 @@ void BlockAnalyzer::determineStep() {
|
|||
// I calculated the value 30 based on some trial and error
|
||||
|
||||
// the fall time of 30 is too slow on framerates above 50fps
|
||||
const double fallTime = timeout() < 20 ? 20 * m_rows : 30 * m_rows;
|
||||
const double fallTime = timeout() < 20 ? 20 * rows_ : 30 * rows_;
|
||||
|
||||
m_step = static_cast<double>(m_rows * timeout()) / fallTime;
|
||||
step_ = static_cast<double>(rows_ * timeout()) / fallTime;
|
||||
}
|
||||
|
||||
void BlockAnalyzer::framerateChanged() { // virtual
|
||||
|
@ -127,15 +129,14 @@ void BlockAnalyzer::transform(Analyzer::Scope& s) {
|
|||
|
||||
float* front = static_cast<float*>(&s.front());
|
||||
|
||||
m_fht->spectrum(front);
|
||||
m_fht->scale(front, 1.0 / 20);
|
||||
fht_->spectrum(front);
|
||||
fht_->scale(front, 1.0 / 20);
|
||||
|
||||
// the second half is pretty dull, so only show it if the user has a large
|
||||
// analyzer
|
||||
// by setting to m_scope.size() if large we prevent interpolation of large
|
||||
// by setting to scope_.size() if large we prevent interpolation of large
|
||||
// analyzers, this is good!
|
||||
s.resize(m_scope.size() <= MAX_COLUMNS / 2 ? MAX_COLUMNS / 2
|
||||
: m_scope.size());
|
||||
s.resize(scope_.size() <= kMaxColumns / 2 ? kMaxColumns / 2 : scope_.size());
|
||||
}
|
||||
|
||||
void BlockAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
|
||||
|
@ -150,7 +151,7 @@ void BlockAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
|
|||
// y represents the number of blanks
|
||||
// y starts from the top and increases in units of blocks
|
||||
|
||||
// m_yscale looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 }
|
||||
// yscale_ looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 }
|
||||
// if it contains 6 elements there are 5 rows in the analyzer
|
||||
|
||||
if (!new_frame) {
|
||||
|
@ -160,51 +161,55 @@ void BlockAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
|
|||
|
||||
QPainter canvas_painter(&canvas_);
|
||||
|
||||
Analyzer::interpolate(s, m_scope);
|
||||
Analyzer::interpolate(s, scope_);
|
||||
|
||||
// update the graphics with the new colour
|
||||
if (psychedelic_enabled_) {
|
||||
paletteChange(QPalette());
|
||||
}
|
||||
|
||||
// Paint the background
|
||||
canvas_painter.drawPixmap(0, 0, m_background);
|
||||
canvas_painter.drawPixmap(0, 0, background_);
|
||||
|
||||
for (uint y, x = 0; x < m_scope.size(); ++x) {
|
||||
for (uint y, x = 0; x < scope_.size(); ++x) {
|
||||
// determine y
|
||||
for (y = 0; m_scope[x] < m_yscale[y]; ++y)
|
||||
continue;
|
||||
for (y = 0; scope_[x] < yscale_[y]; ++y) continue;
|
||||
|
||||
// this is opposite to what you'd think, higher than y
|
||||
// means the bar is lower than y (physically)
|
||||
if (static_cast<float>(y) > m_store[x])
|
||||
y = static_cast<int>(m_store[x] += m_step);
|
||||
if (static_cast<float>(y) > store_[x])
|
||||
y = static_cast<int>(store_[x] += step_);
|
||||
else
|
||||
m_store[x] = y;
|
||||
store_[x] = y;
|
||||
|
||||
// if y is lower than m_fade_pos, then the bar has exceeded the height of
|
||||
// if y is lower than fade_pos_, then the bar has exceeded the kHeight of
|
||||
// the fadeout
|
||||
// if the fadeout is quite faded now, then display the new one
|
||||
if (y <= m_fade_pos[x] /*|| m_fade_intensity[x] < FADE_SIZE / 3*/) {
|
||||
m_fade_pos[x] = y;
|
||||
m_fade_intensity[x] = FADE_SIZE;
|
||||
if (y <= fade_pos_[x] /*|| fade_intensity_[x] < kFadeSize / 3*/) {
|
||||
fade_pos_[x] = y;
|
||||
fade_intensity_[x] = kFadeSize;
|
||||
}
|
||||
|
||||
if (m_fade_intensity[x] > 0) {
|
||||
const uint offset = --m_fade_intensity[x];
|
||||
const uint y = m_y + (m_fade_pos[x] * (HEIGHT + 1));
|
||||
canvas_painter.drawPixmap(x * (WIDTH + 1), y, m_fade_bars[offset], 0, 0,
|
||||
WIDTH, height() - y);
|
||||
if (fade_intensity_[x] > 0) {
|
||||
const uint offset = --fade_intensity_[x];
|
||||
const uint y = y_ + (fade_pos_[x] * (kHeight + 1));
|
||||
canvas_painter.drawPixmap(x * (kWidth + 1), y, fade_bars_[offset], 0, 0,
|
||||
kWidth, height() - y);
|
||||
}
|
||||
|
||||
if (m_fade_intensity[x] == 0) m_fade_pos[x] = m_rows;
|
||||
if (fade_intensity_[x] == 0) fade_pos_[x] = rows_;
|
||||
|
||||
// REMEMBER: y is a number from 0 to m_rows, 0 means all blocks are glowing,
|
||||
// m_rows means none are
|
||||
canvas_painter.drawPixmap(x * (WIDTH + 1), y * (HEIGHT + 1) + m_y, *bar(),
|
||||
0, y * (HEIGHT + 1), bar()->width(),
|
||||
// REMEMBER: y is a number from 0 to rows_, 0 means all blocks are glowing,
|
||||
// rows_ means none are
|
||||
canvas_painter.drawPixmap(x * (kWidth + 1), y * (kHeight + 1) + y_, *bar(),
|
||||
0, y * (kHeight + 1), bar()->width(),
|
||||
bar()->height());
|
||||
}
|
||||
|
||||
for (uint x = 0; x < m_store.size(); ++x)
|
||||
canvas_painter.drawPixmap(x * (WIDTH + 1),
|
||||
static_cast<int>(m_store[x]) * (HEIGHT + 1) + m_y,
|
||||
m_topBarPixmap);
|
||||
for (uint x = 0; x < store_.size(); ++x)
|
||||
canvas_painter.drawPixmap(x * (kWidth + 1),
|
||||
static_cast<int>(store_[x]) * (kHeight + 1) + y_,
|
||||
topBarPixmap_);
|
||||
|
||||
p.drawPixmap(0, 0, canvas_);
|
||||
}
|
||||
|
@ -232,6 +237,12 @@ static inline void adjustToLimits(int& b, int& f, uint& amount) {
|
|||
}
|
||||
}
|
||||
|
||||
void BlockAnalyzer::psychedelicModeChanged(bool enabled) {
|
||||
psychedelic_enabled_ = enabled;
|
||||
// reset colours back to normal
|
||||
paletteChange(QPalette());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clever contrast function
|
||||
*
|
||||
|
@ -305,7 +316,8 @@ QColor ensureContrast(const QColor& bg, const QColor& fg, uint _amount = 150) {
|
|||
if (static_cast<int>(_amount) > 0) adjustToLimits(bs, fs, _amount);
|
||||
|
||||
// see if we need to adjust the hue
|
||||
if (static_cast<int>(_amount) > 0) fh += static_cast<int>(_amount); // cycles around;
|
||||
if (static_cast<int>(_amount) > 0)
|
||||
fh += static_cast<int>(_amount); // cycles around;
|
||||
|
||||
return QColor::fromHsv(fh, fs, fv);
|
||||
}
|
||||
|
@ -327,21 +339,32 @@ QColor ensureContrast(const QColor& bg, const QColor& fg, uint _amount = 150) {
|
|||
|
||||
void BlockAnalyzer::paletteChange(const QPalette&) {
|
||||
const QColor bg = palette().color(QPalette::Background);
|
||||
const QColor fg = ensureContrast(bg, palette().color(QPalette::Highlight));
|
||||
QColor fg;
|
||||
|
||||
m_topBarPixmap.fill(fg);
|
||||
if (psychedelic_enabled_) {
|
||||
fg = getPsychedelicColor(scope_, 10, 75);
|
||||
} else {
|
||||
fg = ensureContrast(bg, palette().color(QPalette::Highlight));
|
||||
}
|
||||
|
||||
const double dr = 15 * static_cast<double>(bg.red() - fg.red()) / (m_rows * 16);
|
||||
const double dg = 15 * static_cast<double>(bg.green() - fg.green()) / (m_rows * 16);
|
||||
const double db = 15 * static_cast<double>(bg.blue() - fg.blue()) / (m_rows * 16);
|
||||
topBarPixmap_.fill(fg);
|
||||
|
||||
const double dr =
|
||||
15 * static_cast<double>(bg.red() - fg.red()) / (rows_ * 16);
|
||||
const double dg =
|
||||
15 * static_cast<double>(bg.green() - fg.green()) / (rows_ * 16);
|
||||
const double db =
|
||||
15 * static_cast<double>(bg.blue() - fg.blue()) / (rows_ * 16);
|
||||
const int r = fg.red(), g = fg.green(), b = fg.blue();
|
||||
|
||||
bar()->fill(bg);
|
||||
|
||||
QPainter p(bar());
|
||||
for (int y = 0; static_cast<uint>(y) < m_rows; ++y)
|
||||
|
||||
for (int y = 0; static_cast<uint>(y) < rows_; ++y)
|
||||
// graduate the fg color
|
||||
p.fillRect(0, y * (HEIGHT + 1), WIDTH, HEIGHT,
|
||||
p.fillRect(
|
||||
0, y * (kHeight + 1), kWidth, kHeight,
|
||||
QColor(r + static_cast<int>(dr * y), g + static_cast<int>(dg * y),
|
||||
b + static_cast<int>(db * y)));
|
||||
|
||||
|
@ -360,13 +383,15 @@ void BlockAnalyzer::paletteChange(const QPalette&) {
|
|||
const int r = bg.red(), g = bg.green(), b = bg.blue();
|
||||
|
||||
// Precalculate all fade-bar pixmaps
|
||||
for (uint y = 0; y < FADE_SIZE; ++y) {
|
||||
m_fade_bars[y].fill(palette().color(QPalette::Background));
|
||||
QPainter f(&m_fade_bars[y]);
|
||||
for (int z = 0; static_cast<uint>(z) < m_rows; ++z) {
|
||||
const double Y = 1.0 - (log10(FADE_SIZE - y) / log10(FADE_SIZE));
|
||||
f.fillRect(0, z * (HEIGHT + 1), WIDTH, HEIGHT,
|
||||
QColor(r + static_cast<int>(dr * Y), g + static_cast<int>(dg * Y), b + static_cast<int>(db * Y)));
|
||||
for (uint y = 0; y < kFadeSize; ++y) {
|
||||
fade_bars_[y].fill(palette().color(QPalette::Background));
|
||||
QPainter f(&fade_bars_[y]);
|
||||
for (int z = 0; static_cast<uint>(z) < rows_; ++z) {
|
||||
const double Y = 1.0 - (log10(kFadeSize - y) / log10(kFadeSize));
|
||||
f.fillRect(
|
||||
0, z * (kHeight + 1), kWidth, kHeight,
|
||||
QColor(r + static_cast<int>(dr * Y), g + static_cast<int>(dg * Y),
|
||||
b + static_cast<int>(db * Y)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -378,11 +403,16 @@ void BlockAnalyzer::drawBackground() {
|
|||
const QColor bg = palette().color(QPalette::Background);
|
||||
const QColor bgdark = bg.dark(112);
|
||||
|
||||
m_background.fill(bg);
|
||||
background_.fill(bg);
|
||||
|
||||
QPainter p(&m_background);
|
||||
for (int x = 0; (uint)x < m_columns; ++x)
|
||||
for (int y = 0; (uint)y < m_rows; ++y)
|
||||
p.fillRect(x * (WIDTH + 1), y * (HEIGHT + 1) + m_y, WIDTH, HEIGHT,
|
||||
QPainter p(&background_);
|
||||
|
||||
if (p.paintEngine() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int x = 0; (uint)x < columns_; ++x)
|
||||
for (int y = 0; (uint)y < rows_; ++y)
|
||||
p.fillRect(x * (kWidth + 1), y * (kHeight + 1) + y_, kWidth, kHeight,
|
||||
bgdark);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Copyright 2003-2005, Max Howell <max.howell@methylblue.com>
|
||||
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
|
||||
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
||||
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||
Copyright 2014, Krzysztof A. Sobiecki <sobkas@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
|
@ -39,12 +39,12 @@ class BlockAnalyzer : public Analyzer::Base {
|
|||
Q_INVOKABLE BlockAnalyzer(QWidget*);
|
||||
~BlockAnalyzer();
|
||||
|
||||
static const uint HEIGHT;
|
||||
static const uint WIDTH;
|
||||
static const uint MIN_ROWS;
|
||||
static const uint MIN_COLUMNS;
|
||||
static const uint MAX_COLUMNS;
|
||||
static const uint FADE_SIZE;
|
||||
static const uint kHeight;
|
||||
static const uint kWidth;
|
||||
static const uint kMinRows;
|
||||
static const uint kMinColumns;
|
||||
static const uint kMaxColumns;
|
||||
static const uint kFadeSize;
|
||||
|
||||
static const char* kName;
|
||||
|
||||
|
@ -54,29 +54,30 @@ class BlockAnalyzer : public Analyzer::Base {
|
|||
virtual void resizeEvent(QResizeEvent*);
|
||||
virtual void paletteChange(const QPalette&);
|
||||
virtual void framerateChanged();
|
||||
virtual void psychedelicModeChanged(bool);
|
||||
|
||||
void drawBackground();
|
||||
void determineStep();
|
||||
|
||||
private:
|
||||
QPixmap* bar() { return &m_barPixmap; }
|
||||
QPixmap* bar() { return &barPixmap_; }
|
||||
|
||||
uint m_columns, m_rows; // number of rows and columns of blocks
|
||||
uint m_y; // y-offset from top of widget
|
||||
QPixmap m_barPixmap;
|
||||
QPixmap m_topBarPixmap;
|
||||
QPixmap m_background;
|
||||
uint columns_, rows_; // number of rows and columns of blocks
|
||||
uint y_; // y-offset from top of widget
|
||||
QPixmap barPixmap_;
|
||||
QPixmap topBarPixmap_;
|
||||
QPixmap background_;
|
||||
QPixmap canvas_;
|
||||
Analyzer::Scope m_scope; // so we don't create a vector every frame
|
||||
std::vector<float> m_store; // current bar heights
|
||||
std::vector<float> m_yscale;
|
||||
Analyzer::Scope scope_; // so we don't create a vector every frame
|
||||
std::vector<float> store_; // current bar kHeights
|
||||
std::vector<float> yscale_;
|
||||
|
||||
// FIXME why can't I namespace these? c++ issue?
|
||||
std::vector<QPixmap> m_fade_bars;
|
||||
std::vector<uint> m_fade_pos;
|
||||
std::vector<int> m_fade_intensity;
|
||||
std::vector<QPixmap> fade_bars_;
|
||||
std::vector<uint> fade_pos_;
|
||||
std::vector<int> fade_intensity_;
|
||||
|
||||
float m_step; // rows to fall per frame
|
||||
float step_; // rows to fall per frame
|
||||
};
|
||||
|
||||
#endif // ANALYZERS_BLOCKANALYZER_H_
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Copyright 2004, Max Howell <max.howell@methylblue.com>
|
||||
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
|
||||
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
||||
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
|
@ -28,69 +28,78 @@
|
|||
|
||||
using Analyzer::Scope;
|
||||
|
||||
const uint BoomAnalyzer::kColumnWidth = 4;
|
||||
const uint BoomAnalyzer::kMaxBandCount = 256;
|
||||
const uint BoomAnalyzer::kMinBandCount = 32;
|
||||
|
||||
const char* BoomAnalyzer::kName =
|
||||
QT_TRANSLATE_NOOP("AnalyzerContainer", "Boom analyzer");
|
||||
|
||||
BoomAnalyzer::BoomAnalyzer(QWidget* parent)
|
||||
: Analyzer::Base(parent, 9),
|
||||
K_barHeight(1.271) // 1.471
|
||||
bands_(0),
|
||||
scope_(kMinBandCount),
|
||||
fg_(palette().color(QPalette::Highlight)),
|
||||
K_barHeight_(1.271) // 1.471
|
||||
,
|
||||
F_peakSpeed(1.103) // 1.122
|
||||
F_peakSpeed_(1.103) // 1.122
|
||||
,
|
||||
F(1.0),
|
||||
bar_height(BAND_COUNT, 0),
|
||||
peak_height(BAND_COUNT, 0),
|
||||
peak_speed(BAND_COUNT, 0.01),
|
||||
barPixmap(COLUMN_WIDTH, 50) {}
|
||||
F_(1.0),
|
||||
bar_height_(kMaxBandCount, 0),
|
||||
peak_height_(kMaxBandCount, 0),
|
||||
peak_speed_(kMaxBandCount, 0.01),
|
||||
barPixmap_(kColumnWidth, 50) {
|
||||
setMinimumWidth(kMinBandCount * (kColumnWidth + 1) - 1);
|
||||
setMaximumWidth(kMaxBandCount * (kColumnWidth + 1) - 1);
|
||||
}
|
||||
|
||||
void BoomAnalyzer::changeK_barHeight(int newValue) {
|
||||
K_barHeight = static_cast<double>(newValue) / 1000;
|
||||
K_barHeight_ = static_cast<double>(newValue) / 1000;
|
||||
}
|
||||
|
||||
void BoomAnalyzer::changeF_peakSpeed(int newValue) {
|
||||
F_peakSpeed = static_cast<double>(newValue) / 1000;
|
||||
F_peakSpeed_ = static_cast<double>(newValue) / 1000;
|
||||
}
|
||||
|
||||
void BoomAnalyzer::resizeEvent(QResizeEvent*) { init(); }
|
||||
void BoomAnalyzer::resizeEvent(QResizeEvent* e) {
|
||||
QWidget::resizeEvent(e);
|
||||
|
||||
void BoomAnalyzer::init() {
|
||||
const uint HEIGHT = height() - 2;
|
||||
const double h = 1.2 / HEIGHT;
|
||||
|
||||
F = static_cast<double>(HEIGHT) / (log10(256) * 1.1 /*<- max. amplitude*/);
|
||||
bands_ = qMin(
|
||||
static_cast<uint>(static_cast<double>(width() + 1) / (kColumnWidth + 1)) +
|
||||
1,
|
||||
kMaxBandCount);
|
||||
scope_.resize(bands_);
|
||||
|
||||
barPixmap = QPixmap(COLUMN_WIDTH - 2, HEIGHT);
|
||||
F_ = static_cast<double>(HEIGHT) / (log10(256) * 1.1 /*<- max. amplitude*/);
|
||||
|
||||
barPixmap_ = QPixmap(kColumnWidth - 2, HEIGHT);
|
||||
canvas_ = QPixmap(size());
|
||||
canvas_.fill(palette().color(QPalette::Background));
|
||||
|
||||
QPainter p(&barPixmap);
|
||||
QPainter p(&barPixmap_);
|
||||
for (uint y = 0; y < HEIGHT; ++y) {
|
||||
const double F = static_cast<double>(y) * h;
|
||||
|
||||
p.setPen(QColor(qMax(0, 255 - static_cast<int>(229.0 * F)),
|
||||
qMax(0, 255 - static_cast<int>(229.0 * F)),
|
||||
qMax(0, 255 - static_cast<int>(191.0 * F))));
|
||||
p.drawLine(0, y, COLUMN_WIDTH - 2, y);
|
||||
p.drawLine(0, y, kColumnWidth - 2, y);
|
||||
}
|
||||
|
||||
updateBandSize(bands_);
|
||||
}
|
||||
|
||||
void BoomAnalyzer::transform(Scope& s) {
|
||||
float* front = static_cast<float*>(&s.front());
|
||||
|
||||
m_fht->spectrum(front);
|
||||
m_fht->scale(front, 1.0 / 60);
|
||||
fht_->spectrum(front);
|
||||
fht_->scale(front, 1.0 / 50);
|
||||
|
||||
Scope scope(32, 0);
|
||||
|
||||
const uint xscale[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
|
||||
11, 12, 13, 14, 15, 16, 17, 19, 24, 29, 36,
|
||||
43, 52, 63, 76, 91, 108, 129, 153, 182, 216, 255};
|
||||
|
||||
for (uint j, i = 0; i < 32; i++)
|
||||
for (j = xscale[i]; j < xscale[i + 1]; j++)
|
||||
if (s[j] > scope[i]) scope[i] = s[j];
|
||||
|
||||
s = scope;
|
||||
s.resize(scope_.size() <= kMaxBandCount / 2 ? kMaxBandCount / 2
|
||||
: scope_.size());
|
||||
}
|
||||
|
||||
void BoomAnalyzer::analyze(QPainter& p, const Scope& scope, bool new_frame) {
|
||||
|
@ -104,47 +113,70 @@ void BoomAnalyzer::analyze(QPainter& p, const Scope& scope, bool new_frame) {
|
|||
QPainter canvas_painter(&canvas_);
|
||||
canvas_.fill(palette().color(QPalette::Background));
|
||||
|
||||
for (uint i = 0, x = 0, y; i < BAND_COUNT; ++i, x += COLUMN_WIDTH + 1) {
|
||||
h = log10(scope[i] * 256.0) * F;
|
||||
Analyzer::interpolate(scope, scope_);
|
||||
|
||||
// update the graphics with the new colour
|
||||
if (psychedelic_enabled_) {
|
||||
paletteChange(QPalette());
|
||||
}
|
||||
|
||||
for (uint i = 0, x = 0, y; i < bands_; ++i, x += kColumnWidth + 1) {
|
||||
h = log10(scope_[i] * 256.0) * F_;
|
||||
|
||||
if (h > MAX_HEIGHT) h = MAX_HEIGHT;
|
||||
|
||||
if (h > bar_height[i]) {
|
||||
bar_height[i] = h;
|
||||
if (h > bar_height_[i]) {
|
||||
bar_height_[i] = h;
|
||||
|
||||
if (h > peak_height[i]) {
|
||||
peak_height[i] = h;
|
||||
peak_speed[i] = 0.01;
|
||||
if (h > peak_height_[i]) {
|
||||
peak_height_[i] = h;
|
||||
peak_speed_[i] = 0.01;
|
||||
} else {
|
||||
goto peak_handling;
|
||||
}
|
||||
} else {
|
||||
if (bar_height[i] > 0.0) {
|
||||
bar_height[i] -= K_barHeight; // 1.4
|
||||
if (bar_height[i] < 0.0) bar_height[i] = 0.0;
|
||||
if (bar_height_[i] > 0.0) {
|
||||
bar_height_[i] -= K_barHeight_; // 1.4
|
||||
if (bar_height_[i] < 0.0) bar_height_[i] = 0.0;
|
||||
}
|
||||
|
||||
peak_handling:
|
||||
|
||||
if (peak_height[i] > 0.0) {
|
||||
peak_height[i] -= peak_speed[i];
|
||||
peak_speed[i] *= F_peakSpeed; // 1.12
|
||||
if (peak_height_[i] > 0.0) {
|
||||
peak_height_[i] -= peak_speed_[i];
|
||||
peak_speed_[i] *= F_peakSpeed_; // 1.12
|
||||
|
||||
if (peak_height[i] < bar_height[i]) peak_height[i] = bar_height[i];
|
||||
if (peak_height[i] < 0.0) peak_height[i] = 0.0;
|
||||
if (peak_height_[i] < bar_height_[i]) peak_height_[i] = bar_height_[i];
|
||||
if (peak_height_[i] < 0.0) peak_height_[i] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
y = height() - uint(bar_height[i]);
|
||||
canvas_painter.drawPixmap(x + 1, y, barPixmap, 0, y, -1, -1);
|
||||
canvas_painter.setPen(palette().color(QPalette::Highlight));
|
||||
if (bar_height[i] > 0)
|
||||
canvas_painter.drawRect(x, y, COLUMN_WIDTH - 1, height() - y - 1);
|
||||
y = height() - uint(bar_height_[i]);
|
||||
canvas_painter.drawPixmap(x + 1, y, barPixmap_, 0, y, -1, -1);
|
||||
canvas_painter.setPen(fg_);
|
||||
if (bar_height_[i] > 0)
|
||||
canvas_painter.drawRect(x, y, kColumnWidth - 1, height() - y - 1);
|
||||
|
||||
y = height() - uint(peak_height[i]);
|
||||
canvas_painter.setPen(palette().color(QPalette::Base));
|
||||
canvas_painter.drawLine(x, y, x + COLUMN_WIDTH - 1, y);
|
||||
y = height() - uint(peak_height_[i]);
|
||||
canvas_painter.setPen(palette().color(QPalette::Midlight));
|
||||
canvas_painter.drawLine(x, y, x + kColumnWidth - 1, y);
|
||||
}
|
||||
|
||||
p.drawPixmap(0, 0, canvas_);
|
||||
}
|
||||
|
||||
void BoomAnalyzer::psychedelicModeChanged(bool enabled) {
|
||||
psychedelic_enabled_ = enabled;
|
||||
// reset colours back to normal
|
||||
paletteChange(QPalette());
|
||||
}
|
||||
|
||||
void BoomAnalyzer::paletteChange(const QPalette&) {
|
||||
if (psychedelic_enabled_) {
|
||||
fg_ = getPsychedelicColor(scope_, 50, 100);
|
||||
} else {
|
||||
// the highlight colour changes when the main window loses focus,
|
||||
// so we use save and use the focused colour
|
||||
fg_ = palette().color(QPalette::Highlight);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2004, Max Howell <max.howell@methylblue.com>
|
||||
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
||||
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
Copyright 2014, John Maguire <john.maguire@gmail.com>
|
||||
|
||||
|
@ -27,10 +27,6 @@
|
|||
|
||||
#include "analyzerbase.h"
|
||||
|
||||
/**
|
||||
@author Max Howell
|
||||
*/
|
||||
|
||||
class BoomAnalyzer : public Analyzer::Base {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -39,9 +35,9 @@ class BoomAnalyzer : public Analyzer::Base {
|
|||
|
||||
static const char* kName;
|
||||
|
||||
virtual void init();
|
||||
virtual void transform(Analyzer::Scope& s);
|
||||
virtual void analyze(QPainter& p, const Analyzer::Scope&, bool new_frame);
|
||||
virtual void psychedelicModeChanged(bool);
|
||||
|
||||
public slots:
|
||||
void changeK_barHeight(int);
|
||||
|
@ -49,17 +45,23 @@ class BoomAnalyzer : public Analyzer::Base {
|
|||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* e);
|
||||
void paletteChange(const QPalette&);
|
||||
|
||||
static const uint COLUMN_WIDTH = 4;
|
||||
static const uint BAND_COUNT = 32;
|
||||
static const uint kColumnWidth;
|
||||
static const uint kMaxBandCount;
|
||||
static const uint kMinBandCount;
|
||||
|
||||
double K_barHeight, F_peakSpeed, F;
|
||||
uint bands_;
|
||||
Analyzer::Scope scope_;
|
||||
QColor fg_;
|
||||
|
||||
std::vector<float> bar_height;
|
||||
std::vector<float> peak_height;
|
||||
std::vector<float> peak_speed;
|
||||
double K_barHeight_, F_peakSpeed_, F_;
|
||||
|
||||
QPixmap barPixmap;
|
||||
std::vector<float> bar_height_;
|
||||
std::vector<float> peak_height_;
|
||||
std::vector<float> peak_speed_;
|
||||
|
||||
QPixmap barPixmap_;
|
||||
QPixmap canvas_;
|
||||
};
|
||||
|
||||
|
|
|
@ -24,70 +24,69 @@
|
|||
#include <string.h>
|
||||
#include "fht.h"
|
||||
|
||||
FHT::FHT(int n) : m_buf(0), m_tab(0), m_log(0) {
|
||||
FHT::FHT(int n) : buf_(0), tab_(0), log_(0) {
|
||||
if (n < 3) {
|
||||
m_num = 0;
|
||||
m_exp2 = -1;
|
||||
num_ = 0;
|
||||
exp2_ = -1;
|
||||
return;
|
||||
}
|
||||
m_exp2 = n;
|
||||
m_num = 1 << n;
|
||||
exp2_ = n;
|
||||
num_ = 1 << n;
|
||||
if (n > 3) {
|
||||
m_buf = new float[m_num];
|
||||
m_tab = new float[m_num * 2];
|
||||
buf_ = new float[num_];
|
||||
tab_ = new float[num_ * 2];
|
||||
makeCasTable();
|
||||
}
|
||||
}
|
||||
|
||||
FHT::~FHT() {
|
||||
delete[] m_buf;
|
||||
delete[] m_tab;
|
||||
delete[] m_log;
|
||||
delete[] buf_;
|
||||
delete[] tab_;
|
||||
delete[] log_;
|
||||
}
|
||||
|
||||
void FHT::makeCasTable(void) {
|
||||
float d, *costab, *sintab;
|
||||
int ul, ndiv2 = m_num / 2;
|
||||
int ul, ndiv2 = num_ / 2;
|
||||
|
||||
for (costab = m_tab, sintab = m_tab + m_num / 2 + 1, ul = 0; ul < m_num;
|
||||
ul++) {
|
||||
for (costab = tab_, sintab = tab_ + num_ / 2 + 1, ul = 0; ul < num_; ul++) {
|
||||
d = M_PI * ul / ndiv2;
|
||||
*costab = *sintab = cos(d);
|
||||
|
||||
costab += 2, sintab += 2;
|
||||
if (sintab > m_tab + m_num * 2) sintab = m_tab + 1;
|
||||
if (sintab > tab_ + num_ * 2) sintab = tab_ + 1;
|
||||
}
|
||||
}
|
||||
|
||||
float* FHT::copy(float* d, float* s) {
|
||||
return static_cast<float*>(memcpy(d, s, m_num * sizeof(float)));
|
||||
return static_cast<float*>(memcpy(d, s, num_ * sizeof(float)));
|
||||
}
|
||||
|
||||
float* FHT::clear(float* d) {
|
||||
return static_cast<float*>(memset(d, 0, m_num * sizeof(float)));
|
||||
return static_cast<float*>(memset(d, 0, num_ * sizeof(float)));
|
||||
}
|
||||
|
||||
void FHT::scale(float* p, float d) {
|
||||
for (int i = 0; i < (m_num / 2); i++) *p++ *= d;
|
||||
for (int i = 0; i < (num_ / 2); i++) *p++ *= d;
|
||||
}
|
||||
|
||||
void FHT::ewma(float* d, float* s, float w) {
|
||||
for (int i = 0; i < (m_num / 2); i++, d++, s++) *d = *d * w + *s * (1 - w);
|
||||
for (int i = 0; i < (num_ / 2); i++, d++, s++) *d = *d * w + *s * (1 - w);
|
||||
}
|
||||
|
||||
void FHT::logSpectrum(float* out, float* p) {
|
||||
int n = m_num / 2, i, j, k, *r;
|
||||
if (!m_log) {
|
||||
m_log = new int[n];
|
||||
int n = num_ / 2, i, j, k, *r;
|
||||
if (!log_) {
|
||||
log_ = new int[n];
|
||||
float f = n / log10(static_cast<double>(n));
|
||||
for (i = 0, r = m_log; i < n; i++, r++) {
|
||||
for (i = 0, r = log_; i < n; i++, r++) {
|
||||
j = static_cast<int>(rint(log10(i + 1.0) * f));
|
||||
*r = j >= n ? n - 1 : j;
|
||||
}
|
||||
}
|
||||
semiLogSpectrum(p);
|
||||
*out++ = *p = *p / 100;
|
||||
for (k = i = 1, r = m_log; i < n; i++) {
|
||||
for (k = i = 1, r = log_; i < n; i++) {
|
||||
j = *r++;
|
||||
if (i == j) {
|
||||
*out++ = p[i];
|
||||
|
@ -102,7 +101,7 @@ void FHT::logSpectrum(float* out, float* p) {
|
|||
void FHT::semiLogSpectrum(float* p) {
|
||||
float e;
|
||||
power2(p);
|
||||
for (int i = 0; i < (m_num / 2); i++, p++) {
|
||||
for (int i = 0; i < (num_ / 2); i++, p++) {
|
||||
e = 10.0 * log10(sqrt(*p * .5));
|
||||
*p = e < 0 ? 0 : e;
|
||||
}
|
||||
|
@ -110,31 +109,31 @@ void FHT::semiLogSpectrum(float* p) {
|
|||
|
||||
void FHT::spectrum(float* p) {
|
||||
power2(p);
|
||||
for (int i = 0; i < (m_num / 2); i++, p++)
|
||||
for (int i = 0; i < (num_ / 2); i++, p++)
|
||||
*p = static_cast<float>(sqrt(*p * .5));
|
||||
}
|
||||
|
||||
void FHT::power(float* p) {
|
||||
power2(p);
|
||||
for (int i = 0; i < (m_num / 2); i++) *p++ *= .5;
|
||||
for (int i = 0; i < (num_ / 2); i++) *p++ *= .5;
|
||||
}
|
||||
|
||||
void FHT::power2(float* p) {
|
||||
int i;
|
||||
float* q;
|
||||
_transform(p, m_num, 0);
|
||||
_transform(p, num_, 0);
|
||||
|
||||
*p = (*p * *p), *p += *p, p++;
|
||||
|
||||
for (i = 1, q = p + m_num - 2; i < (m_num / 2); i++, --q)
|
||||
for (i = 1, q = p + num_ - 2; i < (num_ / 2); i++, --q)
|
||||
*p = (*p * *p) + (*q * *q), p++;
|
||||
}
|
||||
|
||||
void FHT::transform(float* p) {
|
||||
if (m_num == 8)
|
||||
if (num_ == 8)
|
||||
transform8(p);
|
||||
else
|
||||
_transform(p, m_num, 0);
|
||||
_transform(p, num_, 0);
|
||||
}
|
||||
|
||||
void FHT::transform8(float* p) {
|
||||
|
@ -173,19 +172,19 @@ void FHT::_transform(float* p, int n, int k) {
|
|||
int i, j, ndiv2 = n / 2;
|
||||
float a, *t1, *t2, *t3, *t4, *ptab, *pp;
|
||||
|
||||
for (i = 0, t1 = m_buf, t2 = m_buf + ndiv2, pp = &p[k]; i < ndiv2; i++)
|
||||
for (i = 0, t1 = buf_, t2 = buf_ + ndiv2, pp = &p[k]; i < ndiv2; i++)
|
||||
*t1++ = *pp++, *t2++ = *pp++;
|
||||
|
||||
memcpy(p + k, m_buf, sizeof(float) * n);
|
||||
memcpy(p + k, buf_, sizeof(float) * n);
|
||||
|
||||
_transform(p, ndiv2, k);
|
||||
_transform(p, ndiv2, k + ndiv2);
|
||||
|
||||
j = m_num / ndiv2 - 1;
|
||||
t1 = m_buf;
|
||||
j = num_ / ndiv2 - 1;
|
||||
t1 = buf_;
|
||||
t2 = t1 + ndiv2;
|
||||
t3 = p + k + ndiv2;
|
||||
ptab = m_tab;
|
||||
ptab = tab_;
|
||||
pp = p + k;
|
||||
|
||||
a = *ptab++ * *t3++;
|
||||
|
@ -202,5 +201,5 @@ void FHT::_transform(float* p, int n, int k) {
|
|||
*t1++ = *pp + a;
|
||||
*t2++ = *pp++ - a;
|
||||
}
|
||||
memcpy(p + k, m_buf, sizeof(float) * n);
|
||||
memcpy(p + k, buf_, sizeof(float) * n);
|
||||
}
|
||||
|
|
|
@ -32,11 +32,11 @@
|
|||
* [1] Computer in Physics, Vol. 9, No. 4, Jul/Aug 1995 pp 373-379
|
||||
*/
|
||||
class FHT {
|
||||
int m_exp2;
|
||||
int m_num;
|
||||
float* m_buf;
|
||||
float* m_tab;
|
||||
int* m_log;
|
||||
int exp2_;
|
||||
int num_;
|
||||
float* buf_;
|
||||
float* tab_;
|
||||
int* log_;
|
||||
|
||||
/**
|
||||
* Create a table of "cas" (cosine and sine) values.
|
||||
|
@ -59,8 +59,8 @@ class FHT {
|
|||
explicit FHT(int);
|
||||
|
||||
~FHT();
|
||||
inline int sizeExp() const { return m_exp2; }
|
||||
inline int size() const { return m_num; }
|
||||
inline int sizeExp() const { return exp2_; }
|
||||
inline int size() const { return num_; }
|
||||
float* copy(float*, float*);
|
||||
float* clear(float*);
|
||||
void scale(float*, float);
|
||||
|
|
|
@ -57,7 +57,7 @@ NyanCatAnalyzer::NyanCatAnalyzer(QWidget* parent)
|
|||
}
|
||||
}
|
||||
|
||||
void NyanCatAnalyzer::transform(Scope& s) { m_fht->spectrum(&s.front()); }
|
||||
void NyanCatAnalyzer::transform(Scope& s) { fht_->spectrum(&s.front()); }
|
||||
|
||||
void NyanCatAnalyzer::timerEvent(QTimerEvent* e) {
|
||||
if (e->timerId() == timer_id_) {
|
||||
|
@ -74,7 +74,8 @@ void NyanCatAnalyzer::resizeEvent(QResizeEvent* e) {
|
|||
buffer_[1] = QPixmap();
|
||||
|
||||
available_rainbow_width_ = width() - kCatWidth + kRainbowOverlap;
|
||||
px_per_frame_ = static_cast<float>(available_rainbow_width_) / (kHistorySize - 1) + 1;
|
||||
px_per_frame_ =
|
||||
static_cast<float>(available_rainbow_width_) / (kHistorySize - 1) + 1;
|
||||
x_offset_ = px_per_frame_ * (kHistorySize - 1) - available_rainbow_width_;
|
||||
}
|
||||
|
||||
|
@ -112,11 +113,13 @@ void NyanCatAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
|
|||
QPointF* dest = polyline;
|
||||
float* source = history_;
|
||||
|
||||
const float top_of_cat = static_cast<float>(height()) / 2 - static_cast<float>(kCatHeight) / 2;
|
||||
const float top_of_cat =
|
||||
static_cast<float>(height()) / 2 - static_cast<float>(kCatHeight) / 2;
|
||||
for (int band = 0; band < kRainbowBands; ++band) {
|
||||
// Calculate the Y position of this band.
|
||||
const float y =
|
||||
static_cast<float>(kCatHeight) / (kRainbowBands + 1) * (band + 0.5) + top_of_cat;
|
||||
static_cast<float>(kCatHeight) / (kRainbowBands + 1) * (band + 0.5) +
|
||||
top_of_cat;
|
||||
|
||||
// Add each point in the line.
|
||||
for (int x = 0; x < kHistorySize; ++x) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2014, Alibek Omarov <a1ba.omarov@gmail.com>
|
||||
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
||||
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
Copyright 2014, David Sansome <me@davidsansome.com>
|
||||
|
||||
|
@ -57,7 +57,7 @@ RainbowDashAnalyzer::RainbowDashAnalyzer(QWidget* parent)
|
|||
}
|
||||
}
|
||||
|
||||
void RainbowDashAnalyzer::transform(Scope& s) { m_fht->spectrum(&s.front()); }
|
||||
void RainbowDashAnalyzer::transform(Scope& s) { fht_->spectrum(&s.front()); }
|
||||
|
||||
void RainbowDashAnalyzer::timerEvent(QTimerEvent* e) {
|
||||
if (e->timerId() == timer_id_) {
|
||||
|
@ -74,7 +74,8 @@ void RainbowDashAnalyzer::resizeEvent(QResizeEvent* e) {
|
|||
buffer_[1] = QPixmap();
|
||||
|
||||
available_rainbow_width_ = width() - kDashWidth + kRainbowOverlap;
|
||||
px_per_frame_ = static_cast<float>(available_rainbow_width_) / (kHistorySize - 1) + 1;
|
||||
px_per_frame_ =
|
||||
static_cast<float>(available_rainbow_width_) / (kHistorySize - 1) + 1;
|
||||
x_offset_ = px_per_frame_ * (kHistorySize - 1) - available_rainbow_width_;
|
||||
}
|
||||
|
||||
|
@ -111,11 +112,12 @@ void RainbowDashAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
|
|||
QPointF* dest = polyline;
|
||||
float* source = history_;
|
||||
|
||||
const float top_of_Dash = static_cast<float>(height()) / 2 - static_cast<float>(kRainbowHeight) / 2;
|
||||
const float top_of_Dash = static_cast<float>(height()) / 2 -
|
||||
static_cast<float>(kRainbowHeight) / 2;
|
||||
for (int band = 0; band < kRainbowBands; ++band) {
|
||||
// Calculate the Y position of this band.
|
||||
const float y =
|
||||
static_cast<float>(kRainbowHeight) / (kRainbowBands + 1) * (band + 0.5) +
|
||||
const float y = static_cast<float>(kRainbowHeight) / (kRainbowBands + 1) *
|
||||
(band + 0.5) +
|
||||
top_of_Dash;
|
||||
|
||||
// Add each point in the line.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2014, Alibek Omarov <a1ba.omarov@gmail.com>
|
||||
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
||||
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Copyright 2004, Melchior FRANZ <mfranz@kde.org>
|
||||
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
|
||||
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
||||
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
|
@ -31,7 +31,8 @@ using Analyzer::Scope;
|
|||
const char* Sonogram::kName =
|
||||
QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram");
|
||||
|
||||
Sonogram::Sonogram(QWidget* parent) : Analyzer::Base(parent, 9) {}
|
||||
Sonogram::Sonogram(QWidget* parent)
|
||||
: Analyzer::Base(parent, 9), scope_size_(128) {}
|
||||
|
||||
Sonogram::~Sonogram() {}
|
||||
|
||||
|
@ -45,10 +46,16 @@ void Sonogram::resizeEvent(QResizeEvent* e) {
|
|||
|
||||
canvas_ = QPixmap(size());
|
||||
canvas_.fill(palette().color(QPalette::Background));
|
||||
updateBandSize(scope_size_);
|
||||
}
|
||||
|
||||
void Sonogram::psychedelicModeChanged(bool enabled) {
|
||||
psychedelic_enabled_ = enabled;
|
||||
updateBandSize(scope_size_);
|
||||
}
|
||||
|
||||
void Sonogram::analyze(QPainter& p, const Scope& s, bool new_frame) {
|
||||
if (!new_frame) {
|
||||
if (!new_frame || engine_->state() == Engine::Paused) {
|
||||
p.drawPixmap(0, 0, canvas_);
|
||||
return;
|
||||
}
|
||||
|
@ -60,6 +67,30 @@ void Sonogram::analyze(QPainter& p, const Scope& s, bool new_frame) {
|
|||
canvas_painter.drawPixmap(0, 0, canvas_, 1, 0, x, -1);
|
||||
|
||||
Scope::const_iterator it = s.begin(), end = s.end();
|
||||
if (scope_size_ != s.size()) {
|
||||
scope_size_ = s.size();
|
||||
updateBandSize(scope_size_);
|
||||
}
|
||||
|
||||
if (psychedelic_enabled_) {
|
||||
c = getPsychedelicColor(s, 20, 100);
|
||||
for (int y = height() - 1; y;) {
|
||||
if (it >= end || *it < .005) {
|
||||
c = palette().color(QPalette::Background);
|
||||
} else if (*it < .05) {
|
||||
c.setHsv(c.hue(), c.saturation(), 255 - static_cast<int>(*it * 4000.0));
|
||||
} else if (*it < 1.0) {
|
||||
c.setHsv((c.hue() + static_cast<int>(*it * 90.0)) % 255, 255, 255);
|
||||
} else {
|
||||
c = getPsychedelicColor(s, 10, 50);
|
||||
}
|
||||
|
||||
canvas_painter.setPen(c);
|
||||
canvas_painter.drawPoint(x, y--);
|
||||
|
||||
if (it < end) ++it;
|
||||
}
|
||||
} else {
|
||||
for (int y = height() - 1; y;) {
|
||||
if (it >= end || *it < .005)
|
||||
c = palette().color(QPalette::Background);
|
||||
|
@ -75,6 +106,7 @@ void Sonogram::analyze(QPainter& p, const Scope& s, bool new_frame) {
|
|||
|
||||
if (it < end) ++it;
|
||||
}
|
||||
}
|
||||
|
||||
canvas_painter.end();
|
||||
|
||||
|
@ -83,11 +115,11 @@ void Sonogram::analyze(QPainter& p, const Scope& s, bool new_frame) {
|
|||
|
||||
void Sonogram::transform(Scope& scope) {
|
||||
float* front = static_cast<float*>(&scope.front());
|
||||
m_fht->power2(front);
|
||||
m_fht->scale(front, 1.0 / 256);
|
||||
scope.resize(m_fht->size() / 2);
|
||||
fht_->power2(front);
|
||||
fht_->scale(front, 1.0 / 256);
|
||||
scope.resize(fht_->size() / 2);
|
||||
}
|
||||
|
||||
void Sonogram::demo(QPainter& p) {
|
||||
analyze(p, Scope(m_fht->size(), 0), new_frame_);
|
||||
analyze(p, Scope(fht_->size(), 0), new_frame_);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
Copyright 2014, John Maguire <john.maguire@gmail.com>
|
||||
Copyright 2015, Mark Furneaux <mark@furneaux.ca>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -25,10 +26,7 @@
|
|||
#define ANALYZERS_SONOGRAM_H_
|
||||
|
||||
#include "analyzerbase.h"
|
||||
|
||||
/**
|
||||
@author Melchior FRANZ
|
||||
*/
|
||||
#include "engines/enginebase.h"
|
||||
|
||||
class Sonogram : public Analyzer::Base {
|
||||
Q_OBJECT
|
||||
|
@ -43,8 +41,10 @@ class Sonogram : public Analyzer::Base {
|
|||
void transform(Analyzer::Scope&);
|
||||
void demo(QPainter& p);
|
||||
void resizeEvent(QResizeEvent*);
|
||||
void psychedelicModeChanged(bool);
|
||||
|
||||
QPixmap canvas_;
|
||||
int scope_size_;
|
||||
};
|
||||
|
||||
#endif // ANALYZERS_SONOGRAM_H_
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Copyright 2003, Stanislav Karchebny <berkus@users.sf.net>
|
||||
Copyright 2003, Max Howell <max.howell@methylblue.com>
|
||||
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||
Copyright 2014, Mark Furneaux <mark@romaco.ca>
|
||||
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
Copyright 2014, John Maguire <john.maguire@gmail.com>
|
||||
|
||||
|
@ -42,58 +42,64 @@ void TurbineAnalyzer::analyze(QPainter& p, const Scope& scope, bool new_frame) {
|
|||
|
||||
float h;
|
||||
const uint hd2 = height() / 2;
|
||||
const uint MAX_HEIGHT = hd2 - 1;
|
||||
const uint kMaxHeight = hd2 - 1;
|
||||
|
||||
QPainter canvas_painter(&canvas_);
|
||||
canvas_.fill(palette().color(QPalette::Background));
|
||||
|
||||
for (uint i = 0, x = 0, y; i < BAND_COUNT; ++i, x += COLUMN_WIDTH + 1) {
|
||||
h = log10(scope[i] * 256.0) * F * 0.5;
|
||||
Analyzer::interpolate(scope, scope_);
|
||||
|
||||
if (h > MAX_HEIGHT) h = MAX_HEIGHT;
|
||||
// update the graphics with the new colour
|
||||
if (psychedelic_enabled_) {
|
||||
paletteChange(QPalette());
|
||||
}
|
||||
|
||||
if (h > bar_height[i]) {
|
||||
bar_height[i] = h;
|
||||
for (uint i = 0, x = 0, y; i < bands_; ++i, x += kColumnWidth + 1) {
|
||||
h = log10(scope_[i] * 256.0) * F_ * 0.5;
|
||||
|
||||
if (h > peak_height[i]) {
|
||||
peak_height[i] = h;
|
||||
peak_speed[i] = 0.01;
|
||||
if (h > kMaxHeight) h = kMaxHeight;
|
||||
|
||||
if (h > bar_height_[i]) {
|
||||
bar_height_[i] = h;
|
||||
|
||||
if (h > peak_height_[i]) {
|
||||
peak_height_[i] = h;
|
||||
peak_speed_[i] = 0.01;
|
||||
} else {
|
||||
goto peak_handling;
|
||||
}
|
||||
} else {
|
||||
if (bar_height[i] > 0.0) {
|
||||
bar_height[i] -= K_barHeight; // 1.4
|
||||
if (bar_height[i] < 0.0) bar_height[i] = 0.0;
|
||||
if (bar_height_[i] > 0.0) {
|
||||
bar_height_[i] -= K_barHeight_; // 1.4
|
||||
if (bar_height_[i] < 0.0) bar_height_[i] = 0.0;
|
||||
}
|
||||
|
||||
peak_handling:
|
||||
|
||||
if (peak_height[i] > 0.0) {
|
||||
peak_height[i] -= peak_speed[i];
|
||||
peak_speed[i] *= F_peakSpeed; // 1.12
|
||||
if (peak_height_[i] > 0.0) {
|
||||
peak_height_[i] -= peak_speed_[i];
|
||||
peak_speed_[i] *= F_peakSpeed_; // 1.12
|
||||
|
||||
if (peak_height[i] < bar_height[i]) peak_height[i] = bar_height[i];
|
||||
if (peak_height[i] < 0.0) peak_height[i] = 0.0;
|
||||
if (peak_height_[i] < bar_height_[i]) peak_height_[i] = bar_height_[i];
|
||||
if (peak_height_[i] < 0.0) peak_height_[i] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
y = hd2 - static_cast<uint>(bar_height[i]);
|
||||
canvas_painter.drawPixmap(x + 1, y, barPixmap, 0, y, -1, -1);
|
||||
canvas_painter.drawPixmap(x + 1, hd2, barPixmap, 0,
|
||||
static_cast<int>(bar_height[i]),
|
||||
-1, -1);
|
||||
y = hd2 - static_cast<uint>(bar_height_[i]);
|
||||
canvas_painter.drawPixmap(x + 1, y, barPixmap_, 0, y, -1, -1);
|
||||
canvas_painter.drawPixmap(x + 1, hd2, barPixmap_, 0,
|
||||
static_cast<int>(bar_height_[i]), -1, -1);
|
||||
|
||||
canvas_painter.setPen(palette().color(QPalette::Highlight));
|
||||
if (bar_height[i] > 0)
|
||||
canvas_painter.drawRect(x, y, COLUMN_WIDTH - 1,
|
||||
static_cast<int>(bar_height[i]) * 2 - 1);
|
||||
canvas_painter.setPen(fg_);
|
||||
if (bar_height_[i] > 0)
|
||||
canvas_painter.drawRect(x, y, kColumnWidth - 1,
|
||||
static_cast<int>(bar_height_[i]) * 2 - 1);
|
||||
|
||||
const uint x2 = x + COLUMN_WIDTH - 1;
|
||||
canvas_painter.setPen(palette().color(QPalette::Base));
|
||||
y = hd2 - uint(peak_height[i]);
|
||||
const uint x2 = x + kColumnWidth - 1;
|
||||
canvas_painter.setPen(palette().color(QPalette::Midlight));
|
||||
y = hd2 - uint(peak_height_[i]);
|
||||
canvas_painter.drawLine(x, y, x2, y);
|
||||
y = hd2 + uint(peak_height[i]);
|
||||
y = hd2 + uint(peak_height_[i]);
|
||||
canvas_painter.drawLine(x, y, x2, y);
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
#include <QVariant>
|
||||
|
||||
const char* Database::kDatabaseFilename = "clementine.db";
|
||||
const int Database::kSchemaVersion = 49;
|
||||
const int Database::kSchemaVersion = 50;
|
||||
const char* Database::kMagicAllSongsTables = "%allsongstables";
|
||||
|
||||
int Database::sNextConnectionId = 1;
|
||||
|
|
|
@ -75,6 +75,10 @@ GlobalShortcuts::GlobalShortcuts(QWidget* parent)
|
|||
AddShortcut("toggle_last_fm_scrobbling",
|
||||
tr("Enable/disable Last.fm scrobbling"),
|
||||
SIGNAL(ToggleScrobbling()));
|
||||
AddShortcut("love_last_fm_scrobbling", tr("Love (Last.fm scrobbling)"),
|
||||
SIGNAL(Love()));
|
||||
AddShortcut("ban_last_fm_scrobbling", tr("Ban (Last.fm scrobbling)"),
|
||||
SIGNAL(Ban()));
|
||||
|
||||
AddRatingShortcut("rate_zero_star", tr("Rate the current song 0 stars"),
|
||||
rating_signals_mapper_, 0);
|
||||
|
|
|
@ -60,7 +60,7 @@ class GlobalShortcuts : public QWidget {
|
|||
void Unregister();
|
||||
void Register();
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void Play();
|
||||
void Pause();
|
||||
void PlayPause();
|
||||
|
@ -80,6 +80,8 @@ class GlobalShortcuts : public QWidget {
|
|||
void CycleShuffleMode();
|
||||
void CycleRepeatMode();
|
||||
void ToggleScrobbling();
|
||||
void Love();
|
||||
void Ban();
|
||||
|
||||
private:
|
||||
void AddShortcut(const QString& id, const QString& name, const char* signal,
|
||||
|
|
|
@ -244,7 +244,8 @@ int Mpris1Player::GetCaps(Engine::State state) const {
|
|||
if (playlists->active()->next_row() != -1) {
|
||||
caps |= CAN_GO_NEXT;
|
||||
}
|
||||
if (playlists->active()->previous_row() != -1) {
|
||||
if (playlists->active()->previous_row() != -1 ||
|
||||
app_->player()->PreviousWouldRestartTrack()) {
|
||||
caps |= CAN_GO_PREV;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,8 @@ const QStringList OrganiseFormat::kKnownTags = QStringList() << "title"
|
|||
<< "extension"
|
||||
<< "performer"
|
||||
<< "grouping"
|
||||
<< "lyrics";
|
||||
<< "lyrics"
|
||||
<< "originalyear";
|
||||
|
||||
// From http://en.wikipedia.org/wiki/8.3_filename#Directory_table
|
||||
const char OrganiseFormat::kInvalidFatCharacters[] = "\"*/\\:<>?|";
|
||||
|
@ -200,6 +201,8 @@ QString OrganiseFormat::TagValue(const QString& tag, const Song& song) const {
|
|||
value = song.comment();
|
||||
else if (tag == "year")
|
||||
value = QString::number(song.year());
|
||||
else if (tag == "originalyear")
|
||||
value = QString::number(song.effective_originalyear());
|
||||
else if (tag == "track")
|
||||
value = QString::number(song.track());
|
||||
else if (tag == "disc")
|
||||
|
|
|
@ -63,7 +63,8 @@ Player::Player(Application* app, QObject* parent)
|
|||
nb_errors_received_(0),
|
||||
volume_before_mute_(50),
|
||||
last_pressed_previous_(QDateTime::currentDateTime()),
|
||||
menu_previousmode_(PreviousBehaviour_DontRestart) {
|
||||
menu_previousmode_(PreviousBehaviour_DontRestart),
|
||||
seek_step_sec_(10) {
|
||||
settings_.beginGroup("Player");
|
||||
|
||||
SetVolume(settings_.value("volume", 50).toInt());
|
||||
|
@ -103,12 +104,24 @@ void Player::ReloadSettings() {
|
|||
|
||||
menu_previousmode_ = PreviousBehaviour(
|
||||
s.value("menu_previousmode", PreviousBehaviour_DontRestart).toInt());
|
||||
|
||||
seek_step_sec_ = s.value("seek_step_sec", 10).toInt();
|
||||
|
||||
s.endGroup();
|
||||
|
||||
engine_->ReloadSettings();
|
||||
}
|
||||
|
||||
void Player::HandleLoadResult(const UrlHandler::LoadResult& result) {
|
||||
// Might've been an async load, so check we're still on the same item
|
||||
shared_ptr<PlaylistItem> item = app_->playlist_manager()->active()->current_item();
|
||||
if (!item) {
|
||||
loading_async_ = QUrl();
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->Url() != result.original_url_) return;
|
||||
|
||||
switch (result.type_) {
|
||||
case UrlHandler::LoadResult::NoMoreTracks:
|
||||
qLog(Debug) << "URL handler for" << result.original_url_
|
||||
|
@ -119,14 +132,6 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult& result) {
|
|||
break;
|
||||
|
||||
case UrlHandler::LoadResult::TrackAvailable: {
|
||||
// Might've been an async load, so check we're still on the same item
|
||||
int current_index = app_->playlist_manager()->active()->current_row();
|
||||
if (current_index == -1) return;
|
||||
|
||||
shared_ptr<PlaylistItem> item =
|
||||
app_->playlist_manager()->active()->item_at(current_index);
|
||||
if (!item || item->Url() != result.original_url_) return;
|
||||
|
||||
qLog(Debug) << "URL handler for" << result.original_url_ << "returned"
|
||||
<< result.media_url_;
|
||||
|
||||
|
@ -301,6 +306,13 @@ void Player::StopAfterCurrent() {
|
|||
app_->playlist_manager()->active()->current_row());
|
||||
}
|
||||
|
||||
bool Player::PreviousWouldRestartTrack() const {
|
||||
// Check if it has been over two seconds since previous button was pressed
|
||||
return menu_previousmode_ == PreviousBehaviour_Restart &&
|
||||
last_pressed_previous_.isValid() &&
|
||||
last_pressed_previous_.secsTo(QDateTime::currentDateTime()) >= 2;
|
||||
}
|
||||
|
||||
void Player::Previous() { PreviousItem(Engine::Manual); }
|
||||
|
||||
void Player::PreviousItem(Engine::TrackChangeFlags change) {
|
||||
|
@ -444,11 +456,11 @@ void Player::SeekTo(int seconds) {
|
|||
}
|
||||
|
||||
void Player::SeekForward() {
|
||||
SeekTo(engine()->position_nanosec() / kNsecPerSec + 10);
|
||||
SeekTo(engine()->position_nanosec() / kNsecPerSec + seek_step_sec_);
|
||||
}
|
||||
|
||||
void Player::SeekBackward() {
|
||||
SeekTo(engine()->position_nanosec() / kNsecPerSec - 10);
|
||||
SeekTo(engine()->position_nanosec() / kNsecPerSec - seek_step_sec_);
|
||||
}
|
||||
|
||||
void Player::EngineMetadataReceived(const Engine::SimpleMetaBundle& bundle) {
|
||||
|
@ -589,6 +601,8 @@ void Player::TrackAboutToEnd() {
|
|||
next_item->Metadata().end_nanosec());
|
||||
}
|
||||
|
||||
void Player::IntroPointReached() { NextInternal(Engine::Intro); }
|
||||
|
||||
void Player::ValidSongRequested(const QUrl& url) {
|
||||
emit SongChangeRequestProcessed(url, true);
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ class PlayerInterface : public QObject {
|
|||
virtual void Play() = 0;
|
||||
virtual void ShowOSD() = 0;
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void Playing();
|
||||
void Paused();
|
||||
void Stopped();
|
||||
|
@ -142,6 +142,8 @@ class Player : public PlayerInterface {
|
|||
|
||||
const UrlHandler* HandlerForUrl(const QUrl& url) const;
|
||||
|
||||
bool PreviousWouldRestartTrack() const;
|
||||
|
||||
public slots:
|
||||
void ReloadSettings();
|
||||
|
||||
|
@ -163,6 +165,7 @@ class Player : public PlayerInterface {
|
|||
void Pause();
|
||||
void Stop(bool stop_after = false);
|
||||
void StopAfterCurrent();
|
||||
void IntroPointReached();
|
||||
void Play();
|
||||
void ShowOSD();
|
||||
void TogglePrettyOSD();
|
||||
|
@ -209,6 +212,7 @@ class Player : public PlayerInterface {
|
|||
|
||||
QDateTime last_pressed_previous_;
|
||||
PreviousBehaviour menu_previousmode_;
|
||||
int seek_step_sec_;
|
||||
};
|
||||
|
||||
#endif // CORE_PLAYER_H_
|
||||
|
|
|
@ -112,7 +112,9 @@ const QStringList Song::kColumns = QStringList() << "title"
|
|||
<< "etag"
|
||||
<< "performer"
|
||||
<< "grouping"
|
||||
<< "lyrics";
|
||||
<< "lyrics"
|
||||
<< "originalyear"
|
||||
<< "effective_originalyear";
|
||||
|
||||
const QString Song::kColumnSpec = Song::kColumns.join(", ");
|
||||
const QString Song::kBindSpec =
|
||||
|
@ -157,6 +159,7 @@ struct Song::Private : public QSharedData {
|
|||
int disc_;
|
||||
float bpm_;
|
||||
int year_;
|
||||
int originalyear_;
|
||||
QString genre_;
|
||||
QString comment_;
|
||||
bool compilation_; // From the file tag
|
||||
|
@ -230,6 +233,7 @@ Song::Private::Private()
|
|||
disc_(-1),
|
||||
bpm_(-1),
|
||||
year_(-1),
|
||||
originalyear_(-1),
|
||||
compilation_(false),
|
||||
sampler_(false),
|
||||
forced_compilation_on_(false),
|
||||
|
@ -285,6 +289,10 @@ int Song::track() const { return d->track_; }
|
|||
int Song::disc() const { return d->disc_; }
|
||||
float Song::bpm() const { return d->bpm_; }
|
||||
int Song::year() const { return d->year_; }
|
||||
int Song::originalyear() const { return d->originalyear_; }
|
||||
int Song::effective_originalyear() const {
|
||||
return d->originalyear_ < 0 ? d->year_ : d->originalyear_;
|
||||
}
|
||||
const QString& Song::genre() const { return d->genre_; }
|
||||
const QString& Song::comment() const { return d->comment_; }
|
||||
bool Song::is_compilation() const {
|
||||
|
@ -342,6 +350,7 @@ void Song::set_track(int v) { d->track_ = v; }
|
|||
void Song::set_disc(int v) { d->disc_ = v; }
|
||||
void Song::set_bpm(float v) { d->bpm_ = v; }
|
||||
void Song::set_year(int v) { d->year_ = v; }
|
||||
void Song::set_originalyear(int v) { d->originalyear_ = v; }
|
||||
void Song::set_genre(const QString& v) { d->genre_ = v; }
|
||||
void Song::set_comment(const QString& v) { d->comment_ = v; }
|
||||
void Song::set_compilation(bool v) { d->compilation_ = v; }
|
||||
|
@ -499,6 +508,7 @@ void Song::InitFromProtobuf(const pb::tagreader::SongMetadata& pb) {
|
|||
d->disc_ = pb.disc();
|
||||
d->bpm_ = pb.bpm();
|
||||
d->year_ = pb.year();
|
||||
d->originalyear_ = pb.originalyear();
|
||||
d->genre_ = QStringFromStdString(pb.genre());
|
||||
d->comment_ = QStringFromStdString(pb.comment());
|
||||
d->compilation_ = pb.compilation();
|
||||
|
@ -545,6 +555,7 @@ void Song::ToProtobuf(pb::tagreader::SongMetadata* pb) const {
|
|||
pb->set_disc(d->disc_);
|
||||
pb->set_bpm(d->bpm_);
|
||||
pb->set_year(d->year_);
|
||||
pb->set_originalyear(d->originalyear_);
|
||||
pb->set_genre(DataCommaSizeFromQString(d->genre_));
|
||||
pb->set_comment(DataCommaSizeFromQString(d->comment_));
|
||||
pb->set_compilation(d->compilation_);
|
||||
|
@ -585,6 +596,7 @@ void Song::InitFromQuery(const SqlRow& q, bool reliable_metadata, int col) {
|
|||
d->disc_ = toint(col + 7);
|
||||
d->bpm_ = tofloat(col + 8);
|
||||
d->year_ = toint(col + 9);
|
||||
d->originalyear_ = toint(col + 41);
|
||||
d->genre_ = tostr(col + 10);
|
||||
d->comment_ = tostr(col + 11);
|
||||
d->compilation_ = q.value(col + 12).toBool();
|
||||
|
@ -957,6 +969,8 @@ void Song::BindToQuery(QSqlQuery* query) const {
|
|||
query->bindValue(":performer", strval(d->performer_));
|
||||
query->bindValue(":grouping", strval(d->grouping_));
|
||||
query->bindValue(":lyrics", strval(d->lyrics_));
|
||||
query->bindValue(":originalyear", intval(d->originalyear_));
|
||||
query->bindValue(":effective_originalyear", intval(this->effective_originalyear()));
|
||||
|
||||
#undef intval
|
||||
#undef notnullintval
|
||||
|
@ -984,6 +998,9 @@ void Song::ToLastFM(lastfm::Track* track, bool prefer_album_artist) const {
|
|||
} else {
|
||||
mtrack.setArtist(d->artist_);
|
||||
}
|
||||
#if LASTFM_MAJOR_VERSION >= 1
|
||||
mtrack.setAlbumArtist(d->albumartist_);
|
||||
#endif
|
||||
mtrack.setAlbum(d->album_);
|
||||
mtrack.setTitle(d->title_);
|
||||
mtrack.setDuration(length_nanosec() / kNsecPerSec);
|
||||
|
@ -1056,7 +1073,8 @@ bool Song::IsMetadataEqual(const Song& other) const {
|
|||
d->performer_ == other.d->performer_ &&
|
||||
d->grouping_ == other.d->grouping_ && d->track_ == other.d->track_ &&
|
||||
d->disc_ == other.d->disc_ && qFuzzyCompare(d->bpm_, other.d->bpm_) &&
|
||||
d->year_ == other.d->year_ && d->genre_ == other.d->genre_ &&
|
||||
d->year_ == other.d->year_ && d->originalyear_ == other.d->originalyear_ &&
|
||||
d->genre_ == other.d->genre_ &&
|
||||
d->comment_ == other.d->comment_ &&
|
||||
d->compilation_ == other.d->compilation_ &&
|
||||
d->beginning_ == other.d->beginning_ &&
|
||||
|
|
|
@ -176,6 +176,8 @@ class Song {
|
|||
int disc() const;
|
||||
float bpm() const;
|
||||
int year() const;
|
||||
int originalyear() const;
|
||||
int effective_originalyear() const;
|
||||
const QString& genre() const;
|
||||
const QString& comment() const;
|
||||
bool is_compilation() const;
|
||||
|
@ -255,6 +257,7 @@ class Song {
|
|||
void set_disc(int v);
|
||||
void set_bpm(float v);
|
||||
void set_year(int v);
|
||||
void set_originalyear(int v);
|
||||
void set_genre(const QString& v);
|
||||
void set_genre_id3(int id);
|
||||
void set_comment(const QString& v);
|
||||
|
|
|
@ -52,7 +52,7 @@ class UrlHandler : public QObject {
|
|||
TrackAvailable,
|
||||
};
|
||||
|
||||
LoadResult(const QUrl& original_url = QUrl(), Type type = NoMoreTracks,
|
||||
LoadResult(const QUrl& original_url, Type type = NoMoreTracks,
|
||||
const QUrl& media_url = QUrl(), qint64 length_nanosec_ = -1);
|
||||
|
||||
// The url that the playlist item has in Url().
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <QIODevice>
|
||||
#include <QMetaEnum>
|
||||
#include <QMouseEvent>
|
||||
#include <QProcess>
|
||||
#include <QStringList>
|
||||
#include <QTcpServer>
|
||||
#include <QtDebug>
|
||||
|
@ -58,7 +59,6 @@
|
|||
#include <sys/statvfs.h>
|
||||
#elif defined(Q_OS_WIN32)
|
||||
#include <windows.h>
|
||||
#include <QProcess>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
|
@ -76,7 +76,6 @@
|
|||
#include "CoreServices/CoreServices.h"
|
||||
#include "IOKit/ps/IOPowerSources.h"
|
||||
#include "IOKit/ps/IOPSKeys.h"
|
||||
#include <QProcess>
|
||||
#endif
|
||||
|
||||
namespace Utilities {
|
||||
|
@ -411,7 +410,9 @@ void OpenInFileBrowser(const QList<QUrl>& urls) {
|
|||
#elif defined(Q_OS_WIN32)
|
||||
ShowFileInExplorer(path);
|
||||
#else
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(directory));
|
||||
QStringList args;
|
||||
args << directory;
|
||||
QProcess::startDetached("xdg-open", args);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ enum TrackChangeType {
|
|||
First = 0x01,
|
||||
Manual = 0x02,
|
||||
Auto = 0x04,
|
||||
Intro = 0x08,
|
||||
|
||||
// Any of:
|
||||
SameAlbum = 0x10,
|
||||
|
|
|
@ -103,6 +103,7 @@ GstEngine::GstEngine(TaskManager* task_manager)
|
|||
buffer_duration_nanosec_(1 * kNsecPerSec), // 1s
|
||||
buffer_min_fill_(33),
|
||||
mono_playback_(false),
|
||||
sample_rate_(kAutoSampleRate),
|
||||
seek_timer_(new QTimer(this)),
|
||||
timer_id_(-1),
|
||||
next_element_id_(0),
|
||||
|
@ -204,6 +205,7 @@ void GstEngine::ReloadSettings() {
|
|||
buffer_min_fill_ = s.value("bufferminfill", 33).toInt();
|
||||
|
||||
mono_playback_ = s.value("monoplayback", false).toBool();
|
||||
sample_rate_ = s.value("samplerate", kAutoSampleRate).toInt();
|
||||
}
|
||||
|
||||
qint64 GstEngine::position_nanosec() const {
|
||||
|
@ -302,8 +304,8 @@ void GstEngine::UpdateScope(int chunk_length) {
|
|||
gst_buffer_map(latest_buffer_, &map, GST_MAP_READ);
|
||||
|
||||
// determine where to split the buffer
|
||||
int chunk_density = (map.size * kNsecPerMsec) /
|
||||
GST_BUFFER_DURATION(latest_buffer_);
|
||||
int chunk_density =
|
||||
(map.size * kNsecPerMsec) / GST_BUFFER_DURATION(latest_buffer_);
|
||||
|
||||
int chunk_size = chunk_length * chunk_density;
|
||||
|
||||
|
@ -321,8 +323,7 @@ void GstEngine::UpdateScope(int chunk_length) {
|
|||
|
||||
// make sure we don't go beyond the end of the buffer
|
||||
if (scope_chunk_ == scope_chunks_ - 1) {
|
||||
bytes =
|
||||
qMin(static_cast<Engine::Scope::size_type>(
|
||||
bytes = qMin(static_cast<Engine::Scope::size_type>(
|
||||
map.size - (chunk_size * scope_chunk_)),
|
||||
scope_.size() * sizeof(sample_type));
|
||||
} else {
|
||||
|
@ -380,7 +381,9 @@ bool GstEngine::Load(const QUrl& url, Engine::TrackChangeFlags change,
|
|||
|
||||
bool crossfade =
|
||||
current_pipeline_ && ((crossfade_enabled_ && change & Engine::Manual) ||
|
||||
(autocrossfade_enabled_ && change & Engine::Auto));
|
||||
(autocrossfade_enabled_ && change & Engine::Auto) ||
|
||||
((crossfade_enabled_ || autocrossfade_enabled_) &&
|
||||
change & Engine::Intro));
|
||||
|
||||
if (change & Engine::Auto && change & Engine::SameAlbum &&
|
||||
!crossfade_same_album_)
|
||||
|
@ -767,9 +770,8 @@ GstEngine::PluginDetailsList GstEngine::GetPluginList(
|
|||
if (QString(gst_element_factory_get_klass(factory)).contains(classname)) {
|
||||
PluginDetails details;
|
||||
details.name = QString::fromUtf8(gst_plugin_feature_get_name(p->data));
|
||||
details.description = QString::fromUtf8(
|
||||
gst_element_factory_get_metadata(factory,
|
||||
GST_ELEMENT_METADATA_DESCRIPTION));
|
||||
details.description = QString::fromUtf8(gst_element_factory_get_metadata(
|
||||
factory, GST_ELEMENT_METADATA_DESCRIPTION));
|
||||
ret << details;
|
||||
}
|
||||
p = g_list_next(p);
|
||||
|
@ -788,6 +790,7 @@ shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline() {
|
|||
ret->set_buffer_duration_nanosec(buffer_duration_nanosec_);
|
||||
ret->set_buffer_min_fill(buffer_min_fill_);
|
||||
ret->set_mono_playback(mono_playback_);
|
||||
ret->set_sample_rate(sample_rate_);
|
||||
|
||||
ret->AddBufferConsumer(this);
|
||||
for (BufferConsumer* consumer : buffer_consumers_) {
|
||||
|
|
|
@ -50,7 +50,6 @@ struct _GTlsDatabase;
|
|||
typedef struct _GTlsDatabase GTlsDatabase;
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* @class GstEngine
|
||||
* @short GStreamer engine plugin
|
||||
|
@ -72,6 +71,7 @@ class GstEngine : public Engine::Base, public BufferConsumer {
|
|||
};
|
||||
typedef QList<OutputDetails> OutputDetailsList;
|
||||
|
||||
static const int kAutoSampleRate = -1;
|
||||
static const char* kSettingsGroup;
|
||||
static const char* kAutoSink;
|
||||
|
||||
|
@ -216,6 +216,7 @@ class GstEngine : public Engine::Base, public BufferConsumer {
|
|||
int buffer_min_fill_;
|
||||
|
||||
bool mono_playback_;
|
||||
int sample_rate_;
|
||||
|
||||
mutable bool can_decode_success_;
|
||||
mutable bool can_decode_last_;
|
||||
|
|
|
@ -69,6 +69,7 @@ GstEnginePipeline::GstEnginePipeline(GstEngine* engine)
|
|||
buffer_min_fill_(33),
|
||||
buffering_(false),
|
||||
mono_playback_(false),
|
||||
sample_rate_(GstEngine::kAutoSampleRate),
|
||||
end_offset_nanosec_(-1),
|
||||
next_beginning_offset_nanosec_(-1),
|
||||
next_end_offset_nanosec_(-1),
|
||||
|
@ -77,6 +78,7 @@ GstEnginePipeline::GstEnginePipeline(GstEngine* engine)
|
|||
pipeline_is_initialised_(false),
|
||||
pipeline_is_connected_(false),
|
||||
pending_seek_nanosec_(-1),
|
||||
last_known_position_ns_(0),
|
||||
volume_percent_(100),
|
||||
volume_modifier_(1.0),
|
||||
pipeline_(nullptr),
|
||||
|
@ -126,6 +128,8 @@ void GstEnginePipeline::set_mono_playback(bool enabled) {
|
|||
mono_playback_ = enabled;
|
||||
}
|
||||
|
||||
void GstEnginePipeline::set_sample_rate(int rate) { sample_rate_ = rate; }
|
||||
|
||||
bool GstEnginePipeline::ReplaceDecodeBin(GstElement* new_bin) {
|
||||
if (!new_bin) return false;
|
||||
|
||||
|
@ -386,7 +390,26 @@ bool GstEnginePipeline::Init() {
|
|||
gst_element_link(probe_queue, probe_converter);
|
||||
gst_element_link_many(audio_queue, equalizer_preamp_, equalizer_,
|
||||
stereo_panorama_, volume_, audioscale_, convert,
|
||||
audiosink_, nullptr);
|
||||
nullptr);
|
||||
|
||||
// add caps for fixed sample rate and mono, but only if requested
|
||||
if (sample_rate_ != GstEngine::kAutoSampleRate && sample_rate_ > 0) {
|
||||
GstCaps* caps = gst_caps_new_simple("audio/x-raw", "rate", G_TYPE_INT,
|
||||
sample_rate_, nullptr);
|
||||
if (mono_playback_) {
|
||||
gst_caps_set_simple(caps, "channels", G_TYPE_INT, 1, nullptr);
|
||||
}
|
||||
|
||||
gst_element_link_filtered(convert, audiosink_, caps);
|
||||
gst_caps_unref(caps);
|
||||
} else if (mono_playback_) {
|
||||
GstCaps* capsmono =
|
||||
gst_caps_new_simple("audio/x-raw", "channels", G_TYPE_INT, 1, nullptr);
|
||||
gst_element_link_filtered(convert, audiosink_, capsmono);
|
||||
gst_caps_unref(capsmono);
|
||||
} else {
|
||||
gst_element_link(convert, audiosink_);
|
||||
}
|
||||
|
||||
// Add probes and handlers.
|
||||
gst_pad_add_probe(gst_element_get_static_pad(probe_converter, "src"),
|
||||
|
@ -622,10 +645,7 @@ QPair<QString, QString> ParseAkamaiTag(const QString& tag) {
|
|||
return qMakePair(tag, QString());
|
||||
}
|
||||
|
||||
bool IsAkamaiTag(const QString& tag) {
|
||||
return tag.contains("- text=\"");
|
||||
}
|
||||
|
||||
bool IsAkamaiTag(const QString& tag) { return tag.contains("- text=\""); }
|
||||
}
|
||||
|
||||
void GstEnginePipeline::TagMessageReceived(GstMessage* msg) {
|
||||
|
@ -941,7 +961,8 @@ void GstEnginePipeline::SourceSetupCallback(GstURIDecodeBin* bin,
|
|||
nullptr);
|
||||
|
||||
#ifdef Q_OS_DARWIN
|
||||
g_object_set(element, "tls-database", instance->engine_->tls_database(), nullptr);
|
||||
g_object_set(element, "tls-database", instance->engine_->tls_database(),
|
||||
nullptr);
|
||||
g_object_set(element, "ssl-use-system-ca-file", false, nullptr);
|
||||
g_object_set(element, "ssl-strict", TRUE, nullptr);
|
||||
#endif
|
||||
|
@ -976,10 +997,11 @@ void GstEnginePipeline::TransitionToNext() {
|
|||
}
|
||||
|
||||
qint64 GstEnginePipeline::position() const {
|
||||
gint64 value = 0;
|
||||
gst_element_query_position(pipeline_, GST_FORMAT_TIME, &value);
|
||||
if (pipeline_is_initialised_)
|
||||
gst_element_query_position(pipeline_, GST_FORMAT_TIME,
|
||||
&last_known_position_ns_);
|
||||
|
||||
return value;
|
||||
return last_known_position_ns_;
|
||||
}
|
||||
|
||||
qint64 GstEnginePipeline::length() const {
|
||||
|
@ -1033,6 +1055,7 @@ bool GstEnginePipeline::Seek(qint64 nanosec) {
|
|||
}
|
||||
|
||||
pending_seek_nanosec_ = -1;
|
||||
last_known_position_ns_ = nanosec;
|
||||
return gst_element_seek_simple(pipeline_, GST_FORMAT_TIME,
|
||||
GST_SEEK_FLAG_FLUSH, nanosec);
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ class GstEnginePipeline : public QObject {
|
|||
void set_buffer_duration_nanosec(qint64 duration_nanosec);
|
||||
void set_buffer_min_fill(int percent);
|
||||
void set_mono_playback(bool enabled);
|
||||
void set_sample_rate(int rate);
|
||||
|
||||
// Creates the pipeline, returns false on error
|
||||
bool InitFromUrl(const QUrl& url, qint64 end_nanosec);
|
||||
|
@ -220,6 +221,7 @@ signals:
|
|||
bool buffering_;
|
||||
|
||||
bool mono_playback_;
|
||||
int sample_rate_;
|
||||
|
||||
// The URL that is currently playing, and the URL that is to be preloaded
|
||||
// when the current track is close to finishing.
|
||||
|
@ -258,6 +260,13 @@ signals:
|
|||
bool pipeline_is_connected_;
|
||||
qint64 pending_seek_nanosec_;
|
||||
|
||||
// We can only use gst_element_query_position() when the pipeline is in
|
||||
// PAUSED nor PLAYING state. Whenever we get a new position (e.g. after a
|
||||
// correct call to gst_element_query_position() or after a seek), we store
|
||||
// it here so that we can use it when using gst_element_query_position() is
|
||||
// not possible.
|
||||
mutable gint64 last_known_position_ns_;
|
||||
|
||||
int volume_percent_;
|
||||
qreal volume_modifier_;
|
||||
|
||||
|
|
|
@ -120,12 +120,26 @@ QStandardItem* GlobalSearchModel::BuildContainers(const Song& s,
|
|||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case LibraryModel::GroupBy_OriginalYearAlbum:
|
||||
year = qMax(0, s.effective_originalyear());
|
||||
display_text = LibraryModel::PrettyYearAlbum(year, s.album());
|
||||
sort_text = LibraryModel::SortTextForNumber(year) + s.album();
|
||||
unique_tag = s.album_id();
|
||||
has_album_icon = true;
|
||||
break;
|
||||
|
||||
case LibraryModel::GroupBy_Year:
|
||||
year = qMax(0, s.year());
|
||||
display_text = QString::number(year);
|
||||
sort_text = LibraryModel::SortTextForNumber(year) + " ";
|
||||
break;
|
||||
|
||||
case LibraryModel::GroupBy_OriginalYear:
|
||||
year = qMax(0, s.effective_originalyear());
|
||||
display_text = QString::number(year);
|
||||
sort_text = LibraryModel::SortTextForNumber(year) + " ";
|
||||
break;
|
||||
|
||||
case LibraryModel::GroupBy_Composer:
|
||||
display_text = s.composer();
|
||||
case LibraryModel::GroupBy_Performer:
|
||||
|
|
|
@ -101,12 +101,12 @@ GlobalSearchView::GlobalSearchView(Application* app, QWidget* parent)
|
|||
disabled_layout->setContentsMargins(16, 0, 16, 32);
|
||||
suggestions_layout->setContentsMargins(16, 0, 16, 6);
|
||||
|
||||
// Set the colour of the help text to the disabled text colour
|
||||
// Set the colour of the help text to the disabled window text colour
|
||||
QPalette help_palette = ui_->help_text->palette();
|
||||
const QColor help_color =
|
||||
help_palette.color(QPalette::Disabled, QPalette::Text);
|
||||
help_palette.setColor(QPalette::Normal, QPalette::Text, help_color);
|
||||
help_palette.setColor(QPalette::Inactive, QPalette::Text, help_color);
|
||||
help_palette.color(QPalette::Disabled, QPalette::WindowText);
|
||||
help_palette.setColor(QPalette::Normal, QPalette::WindowText, help_color);
|
||||
help_palette.setColor(QPalette::Inactive, QPalette::WindowText, help_color);
|
||||
ui_->help_text->setPalette(help_palette);
|
||||
|
||||
// Create suggestion widgets
|
||||
|
@ -337,7 +337,8 @@ void GlobalSearchView::LazyLoadArt(const QModelIndex& proxy_index) {
|
|||
proxy_index.data(LibraryModel::Role_ContainerType).toInt());
|
||||
if (container_type != LibraryModel::GroupBy_Album &&
|
||||
container_type != LibraryModel::GroupBy_AlbumArtist &&
|
||||
container_type != LibraryModel::GroupBy_YearAlbum) {
|
||||
container_type != LibraryModel::GroupBy_YearAlbum &&
|
||||
container_type != LibraryModel::GroupBy_OriginalYearAlbum) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -559,6 +560,11 @@ void GlobalSearchView::GroupByClicked(QAction* action) {
|
|||
}
|
||||
|
||||
void GlobalSearchView::SetGroupBy(const LibraryModel::Grouping& g) {
|
||||
// Clear requests: changing "group by" on the models will cause all the items to be removed/added
|
||||
// again, so all the QModelIndex here will become invalid. New requests will be created for those
|
||||
// songs when they will be displayed again anyway (when GlobalSearchItemDelegate::paint will call
|
||||
// LazyLoadArt)
|
||||
art_requests_.clear();
|
||||
// Update the models
|
||||
front_model_->SetGroupBy(g, true);
|
||||
back_model_->SetGroupBy(g, false);
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include "internet/amazon/amazonclouddrive.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
|
||||
#include <QtGlobal>
|
||||
|
@ -38,6 +39,7 @@
|
|||
#include "library/librarybackend.h"
|
||||
#include "ui/settingsdialog.h"
|
||||
|
||||
using std::chrono::seconds;
|
||||
using std::placeholders::_1;
|
||||
|
||||
const char* AmazonCloudDrive::kServiceName = "Amazon Cloud Drive";
|
||||
|
@ -199,16 +201,16 @@ void AmazonCloudDrive::MonitorReply(QNetworkReply* reply,
|
|||
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (code >= 500) { // Retry with exponential backoff.
|
||||
int max_delay_s = std::pow(std::min(retries + 1, 8), 2);
|
||||
int delay_s = qrand() % max_delay_s;
|
||||
seconds delay(qrand() % max_delay_s);
|
||||
qLog(Debug) << "Request failed with code:" << code << "- retrying after"
|
||||
<< delay_s << "seconds";
|
||||
<< delay << "seconds";
|
||||
DoAfter([=]() {
|
||||
if (post_data.isEmpty()) {
|
||||
Get(reply->request(), done, retries + 1);
|
||||
} else {
|
||||
Post(reply->request(), post_data, done, retries + 1);
|
||||
}
|
||||
}, delay_s * kMsecPerSec);
|
||||
}, delay);
|
||||
} else {
|
||||
// Request failed permanently.
|
||||
done(reply);
|
||||
|
|
|
@ -92,7 +92,6 @@ InternetModel::InternetModel(Application* app, QObject* parent)
|
|||
AddService(new JazzRadioService(app, this));
|
||||
AddService(new MagnatuneService(app, this));
|
||||
AddService(new PodcastService(app, this));
|
||||
AddService(new RadioGFMService(app, this));
|
||||
AddService(new RockRadioService(app, this));
|
||||
AddService(new SavedRadio(app, this));
|
||||
AddService(new RadioTunesService(app, this));
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "internet/core/oauthenticator.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QSslError>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
|
@ -121,6 +122,7 @@ void OAuthenticator::RequestAccessToken(const QByteArray& code,
|
|||
"application/x-www-form-urlencoded");
|
||||
|
||||
QNetworkReply* reply = network_.post(request, post_data.toUtf8());
|
||||
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), SLOT(SslErrors(QList<QSslError>)));
|
||||
NewClosure(reply, SIGNAL(finished()), this,
|
||||
SLOT(FetchAccessTokenFinished(QNetworkReply*)), reply);
|
||||
}
|
||||
|
@ -196,3 +198,9 @@ void OAuthenticator::RefreshAccessTokenFinished(QNetworkReply* reply) {
|
|||
SetExpiryTime(json_result["expires_in"].toInt());
|
||||
emit Finished();
|
||||
}
|
||||
|
||||
void OAuthenticator::SslErrors(const QList<QSslError>& errors) {
|
||||
for (const QSslError& error : errors) {
|
||||
qLog(Debug) << error.errorString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ class OAuthenticator : public QObject {
|
|||
void RedirectArrived(LocalRedirectServer* server, QUrl url);
|
||||
void FetchAccessTokenFinished(QNetworkReply* reply);
|
||||
void RefreshAccessTokenFinished(QNetworkReply* reply);
|
||||
void SslErrors(const QList<QSslError>& errors);
|
||||
|
||||
private:
|
||||
static const char* kRemoteURL;
|
||||
|
|
|
@ -87,6 +87,7 @@ void DigitallyImportedUrlHandler::LoadPlaylistFinished(QIODevice* device) {
|
|||
// Failed to get playlist?
|
||||
if (songs.count() == 0) {
|
||||
service_->StreamError(tr("Error loading di.fm playlist"));
|
||||
emit AsyncLoadComplete(LoadResult(last_original_url_));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -338,7 +338,8 @@ void LastFMService::NowPlaying(const Song& song) {
|
|||
return;
|
||||
}
|
||||
#else
|
||||
// TODO(John Maguire): validity was removed from liblastfm1 but might reappear, it should have
|
||||
// TODO(John Maguire): validity was removed from liblastfm1 but might reappear,
|
||||
// it should have
|
||||
// no impact as we get a different error when actually trying to scrobble.
|
||||
#endif
|
||||
|
||||
|
@ -375,6 +376,8 @@ void LastFMService::Love() {
|
|||
}
|
||||
|
||||
void LastFMService::Ban() {
|
||||
if (!IsAuthenticated()) ShowConfig();
|
||||
|
||||
lastfm::MutableTrack mtrack(last_track_);
|
||||
mtrack.ban();
|
||||
last_track_ = mtrack;
|
||||
|
|
|
@ -264,10 +264,3 @@ SomaFMService::SomaFMService(Application* app, InternetModel* parent)
|
|||
: SomaFMServiceBase(
|
||||
app, parent, "SomaFM", QUrl("http://somafm.com/channels.xml"),
|
||||
QUrl("http://somafm.com"), QUrl(), QIcon(":providers/somafm.png")) {}
|
||||
|
||||
RadioGFMService::RadioGFMService(Application* app, InternetModel* parent)
|
||||
: SomaFMServiceBase(app, parent, "Radio GFM",
|
||||
QUrl("http://streams.radio-gfm.net/channels.xml"),
|
||||
QUrl("http://www.radio-gfm.net"),
|
||||
QUrl("http://www.radio-gfm.net/spenden"),
|
||||
QIcon(":providers/radiogfm.png")) {}
|
||||
|
|
|
@ -109,11 +109,6 @@ class SomaFMService : public SomaFMServiceBase {
|
|||
SomaFMService(Application* app, InternetModel* parent);
|
||||
};
|
||||
|
||||
class RadioGFMService : public SomaFMServiceBase {
|
||||
public:
|
||||
RadioGFMService(Application* app, InternetModel* parent);
|
||||
};
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const SomaFMService::Stream& stream);
|
||||
QDataStream& operator>>(QDataStream& in, SomaFMService::Stream& stream);
|
||||
Q_DECLARE_METATYPE(SomaFMService::Stream)
|
||||
|
|
|
@ -668,12 +668,14 @@ void SpotifyService::EnsureMenuCreated() {
|
|||
playlist_context_menu_->addAction(GetNewShowConfigAction());
|
||||
|
||||
song_context_menu_ = new QMenu;
|
||||
song_context_menu_->addActions(GetPlaylistActions());
|
||||
song_context_menu_->addSeparator();
|
||||
remove_from_playlist_ = song_context_menu_->addAction(
|
||||
IconLoader::Load("list-remove"), tr("Remove from playlist"), this,
|
||||
SLOT(RemoveCurrentFromPlaylist()));
|
||||
song_context_menu_->addAction(tr("Get a URL to share this Spotify song"),
|
||||
this, SLOT(GetCurrentSongUrlToShare()));
|
||||
|
||||
song_context_menu_->addSeparator();
|
||||
song_context_menu_->addAction(GetNewShowConfigAction());
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ SubsonicUrlHandler::SubsonicUrlHandler(SubsonicService* service,
|
|||
|
||||
UrlHandler::LoadResult SubsonicUrlHandler::StartLoading(const QUrl& url) {
|
||||
if (service_->login_state() != SubsonicService::LoginState_Loggedin)
|
||||
return LoadResult();
|
||||
return LoadResult(url);
|
||||
|
||||
QUrl newurl = service_->BuildRequestUrl("stream");
|
||||
QUrlQuery url_query;
|
||||
|
|
|
@ -63,7 +63,8 @@ const char* VkService::kUrlScheme = "vk";
|
|||
const char* VkService::kDefCacheFilename = "%artist - %title";
|
||||
const int VkService::kMaxVkSongList = 6000;
|
||||
const int VkService::kMaxVkWallPostList = 100;
|
||||
const int VkService::kCustomSongCount = 50;
|
||||
const int VkService::kMaxVkSongCount = 300;
|
||||
const int VkService::kSearchDelayMsec = 400;
|
||||
|
||||
QString VkService::DefaultCacheDir() {
|
||||
return QDir::toNativeSeparators(
|
||||
|
@ -220,7 +221,8 @@ VkService::VkService(Application* app, InternetModel* parent)
|
|||
url_handler_(new VkUrlHandler(this, this)),
|
||||
audio_provider_(new Vreen::AudioProvider(client_.get())),
|
||||
cache_(new VkMusicCache(app_, this)),
|
||||
last_search_id_(0) {
|
||||
last_search_id_(0),
|
||||
search_delay_(new QTimer(this)) {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingGroup);
|
||||
|
||||
|
@ -245,7 +247,14 @@ VkService::VkService(Application* app, InternetModel* parent)
|
|||
VkSearchProvider* search_provider = new VkSearchProvider(app_, this);
|
||||
search_provider->Init(this);
|
||||
app_->global_search()->AddProvider(search_provider);
|
||||
|
||||
search_delay_->setInterval(kSearchDelayMsec);
|
||||
search_delay_->setSingleShot(true);
|
||||
connect(search_delay_, SIGNAL(timeout()), SLOT(DoLocalSearch()));
|
||||
|
||||
connect(search_box_, SIGNAL(TextChanged(QString)), SLOT(FindSongs(QString)));
|
||||
connect(this, SIGNAL(SongSearchResult(SearchID, SongList)),
|
||||
SLOT(SearchResultLoaded(SearchID, SongList)));
|
||||
|
||||
app_->player()->RegisterUrlHandler(url_handler_);
|
||||
}
|
||||
|
@ -662,7 +671,7 @@ void VkService::UpdateRecommendations() {
|
|||
CreateAndAppendRow(recommendations_item_, Type_Loading);
|
||||
|
||||
auto my_audio =
|
||||
audio_provider_->getRecommendationsForUser(0, kCustomSongCount, 0);
|
||||
audio_provider_->getRecommendationsForUser(0, kMaxVkSongCount, 0);
|
||||
|
||||
NewClosure(my_audio, SIGNAL(resultReady(QVariant)), this,
|
||||
SLOT(RecommendationsLoaded(Vreen::AudioItemListReply*)), my_audio);
|
||||
|
@ -673,7 +682,7 @@ void VkService::MoreRecommendations() {
|
|||
CreateAndAppendRow(recommendations_item_, Type_Loading);
|
||||
|
||||
auto my_audio = audio_provider_->getRecommendationsForUser(
|
||||
0, kCustomSongCount, recommendations_item_->rowCount() - 1);
|
||||
0, kMaxVkSongCount, recommendations_item_->rowCount() - 1);
|
||||
|
||||
NewClosure(my_audio, SIGNAL(resultReady(QVariant)), this,
|
||||
SLOT(RecommendationsLoaded(Vreen::AudioItemListReply*)), my_audio);
|
||||
|
@ -818,8 +827,8 @@ QStandardItem* VkService::AppendAlbumList(QStandardItem* parent, bool myself) {
|
|||
|
||||
if (myself) {
|
||||
item = new QStandardItem(QIcon(":vk/discography.png"), tr("My Albums"));
|
||||
// TODO(Ivan Leontiev): Do this better. We have incomplete MusicOwner instance
|
||||
// for logged in user.
|
||||
// TODO(Ivan Leontiev): Do this better. We have incomplete MusicOwner
|
||||
// instance for logged in user.
|
||||
owner.setId(UserID());
|
||||
my_albums_item_ = item;
|
||||
} else {
|
||||
|
@ -876,8 +885,8 @@ QStandardItem* VkService::AppendMusic(QStandardItem* parent, bool myself) {
|
|||
|
||||
if (myself) {
|
||||
item = new QStandardItem(QIcon(":vk/my_music.png"), tr("My Music"));
|
||||
// TODO(Ivan Leontiev): Do this better. We have incomplete MusicOwner instance
|
||||
// for logged in user.
|
||||
// TODO(Ivan Leontiev): Do this better. We have incomplete MusicOwner
|
||||
// instance for logged in user.
|
||||
owner.setId(UserID());
|
||||
my_music_item_ = item;
|
||||
} else {
|
||||
|
@ -962,8 +971,8 @@ void VkService::FindThisArtist() {
|
|||
void VkService::AddToMyMusic() {
|
||||
SongId id = ExtractIds(selected_song_.url());
|
||||
auto reply = audio_provider_->addToLibrary(id.audio_id, id.owner_id);
|
||||
connect(reply, SIGNAL(resultReady(QVariant)), this,
|
||||
SLOT(UpdateMusic(my_music_item_)));
|
||||
NewClosure(reply, SIGNAL(resultReady(QVariant)), this,
|
||||
SLOT(UpdateMusic(QStandardItem*)), my_music_item_);
|
||||
}
|
||||
|
||||
void VkService::AddToMyMusicCurrent() {
|
||||
|
@ -977,8 +986,8 @@ void VkService::RemoveFromMyMusic() {
|
|||
SongId id = ExtractIds(selected_song_.url());
|
||||
if (id.owner_id == UserID()) {
|
||||
auto reply = audio_provider_->removeFromLibrary(id.audio_id, id.owner_id);
|
||||
connect(reply, SIGNAL(resultReady(QVariant)), this,
|
||||
SLOT(UpdateMusic(my_music_item_)));
|
||||
NewClosure(reply, SIGNAL(resultReady(QVariant)), this,
|
||||
SLOT(UpdateMusic(QStandardItem*)), my_music_item_);
|
||||
} else {
|
||||
qLog(Error) << "Tried to delete song that not owned by user (" << UserID()
|
||||
<< selected_song_.url();
|
||||
|
@ -1003,29 +1012,40 @@ void VkService::CopyShareUrl() {
|
|||
* Search
|
||||
*/
|
||||
|
||||
void VkService::DoLocalSearch() {
|
||||
ClearStandardItem(search_result_item_);
|
||||
CreateAndAppendRow(search_result_item_, Type_Loading);
|
||||
SearchID id(SearchID::LocalSearch);
|
||||
|
||||
last_search_id_ = id.id();
|
||||
SongSearch(id, last_query_);
|
||||
}
|
||||
|
||||
void VkService::FindSongs(const QString& query) {
|
||||
last_query_ = query;
|
||||
|
||||
if (query.isEmpty()) {
|
||||
search_delay_->stop();
|
||||
root_item_->removeRow(search_result_item_->row());
|
||||
search_result_item_ = NULL;
|
||||
last_search_id_ = 0;
|
||||
} else {
|
||||
last_query_ = query;
|
||||
return;
|
||||
}
|
||||
|
||||
search_delay_->start();
|
||||
|
||||
if (!search_result_item_) {
|
||||
CreateAndAppendRow(root_item_, Type_Search);
|
||||
connect(this, SIGNAL(SongSearchResult(SearchID, SongList)),
|
||||
SLOT(SearchResultLoaded(SearchID, SongList)));
|
||||
}
|
||||
ClearStandardItem(search_result_item_);
|
||||
CreateAndAppendRow(search_result_item_, Type_Loading);
|
||||
SongSearch(SearchID(SearchID::LocalSearch), query);
|
||||
}
|
||||
}
|
||||
|
||||
void VkService::FindMore() {
|
||||
RemoveLastRow(search_result_item_, Type_More);
|
||||
|
||||
CreateAndAppendRow(search_result_item_, Type_Loading);
|
||||
SearchID id(SearchID::MoreLocalSearch);
|
||||
SongSearch(id, last_query_, kCustomSongCount,
|
||||
|
||||
last_search_id_ = id.id();
|
||||
SongSearch(id, last_query_, kMaxVkSongCount,
|
||||
search_result_item_->rowCount() - 1);
|
||||
}
|
||||
|
||||
|
@ -1034,7 +1054,7 @@ void VkService::SearchResultLoaded(const SearchID& id, const SongList& songs) {
|
|||
return; // Result received when search is already over.
|
||||
}
|
||||
|
||||
if (id.id() >= last_search_id_) {
|
||||
if (id.id() == last_search_id_) {
|
||||
if (id.type() == SearchID::LocalSearch) {
|
||||
ClearStandardItem(search_result_item_);
|
||||
} else if (id.type() == SearchID::MoreLocalSearch) {
|
||||
|
@ -1043,8 +1063,6 @@ void VkService::SearchResultLoaded(const SearchID& id, const SongList& songs) {
|
|||
return; // Others request types ignored.
|
||||
}
|
||||
|
||||
last_search_id_ = id.id();
|
||||
|
||||
if (!songs.isEmpty()) {
|
||||
AppendSongs(search_result_item_, songs);
|
||||
CreateAndAppendRow(search_result_item_, Type_More);
|
||||
|
@ -1173,19 +1191,23 @@ UrlHandler::LoadResult VkService::GetSongResult(const QUrl& url) {
|
|||
if (media_url.isValid()) {
|
||||
Song song = FromAudioItem(audio_item);
|
||||
SongStarting(song);
|
||||
|
||||
if (cachingEnabled_) {
|
||||
cache_->AddToCache(url, media_url);
|
||||
}
|
||||
|
||||
return UrlHandler::LoadResult(url, UrlHandler::LoadResult::TrackAvailable,
|
||||
media_url, song.length_nanosec());
|
||||
}
|
||||
|
||||
return UrlHandler::LoadResult();
|
||||
return UrlHandler::LoadResult(url);
|
||||
}
|
||||
|
||||
UrlHandler::LoadResult VkService::GetGroupNextSongUrl(const QUrl& url) {
|
||||
QStringList tokens = url.path().split('/');
|
||||
if (tokens.count() < 3) {
|
||||
qLog(Error) << "Wrong url" << url;
|
||||
return UrlHandler::LoadResult();
|
||||
return UrlHandler::LoadResult(url);
|
||||
}
|
||||
|
||||
int gid = tokens[1].toInt();
|
||||
|
@ -1214,7 +1236,7 @@ UrlHandler::LoadResult VkService::GetGroupNextSongUrl(const QUrl& url) {
|
|||
}
|
||||
|
||||
qLog(Info) << "Unresolved group url" << url;
|
||||
return UrlHandler::LoadResult();
|
||||
return UrlHandler::LoadResult(url);
|
||||
}
|
||||
|
||||
/***
|
||||
|
@ -1444,7 +1466,7 @@ void VkService::AppendSongs(QStandardItem* parent, const SongList& songs) {
|
|||
void VkService::ReloadSettings() {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingGroup);
|
||||
maxGlobalSearch_ = s.value("max_global_search", kCustomSongCount).toInt();
|
||||
maxGlobalSearch_ = s.value("max_global_search", kMaxVkSongCount).toInt();
|
||||
cachingEnabled_ = s.value("cache_enabled", false).toBool();
|
||||
cacheDir_ = s.value("cache_dir", DefaultCacheDir()).toString();
|
||||
cacheFilename_ = s.value("cache_filename", kDefCacheFilename).toString();
|
||||
|
|
|
@ -116,7 +116,8 @@ class VkService : public InternetService {
|
|||
static QString DefaultCacheDir();
|
||||
static const int kMaxVkSongList;
|
||||
static const int kMaxVkWallPostList;
|
||||
static const int kCustomSongCount;
|
||||
static const int kMaxVkSongCount;
|
||||
static const int kSearchDelayMsec;
|
||||
|
||||
enum ItemType {
|
||||
Type_Loading = InternetModel::TypeCount,
|
||||
|
@ -167,8 +168,8 @@ class VkService : public InternetService {
|
|||
// Return random song result from group playlist.
|
||||
UrlHandler::LoadResult GetGroupNextSongUrl(const QUrl& url);
|
||||
|
||||
void SongSearch(SearchID id, const QString& query, int count = 50,
|
||||
int offset = 0);
|
||||
void SongSearch(SearchID id, const QString& query,
|
||||
int count = kMaxVkSongCount, int offset = 0);
|
||||
void GroupSearch(SearchID id, const QString& query);
|
||||
|
||||
/* Settings */
|
||||
|
@ -181,7 +182,7 @@ class VkService : public InternetService {
|
|||
QString cacheFilename() const { return cacheFilename_; }
|
||||
bool isLoveAddToMyMusic() const { return love_is_add_to_mymusic_; }
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void NameUpdated(const QString& name);
|
||||
void ConnectionStateChanged(Vreen::Client::State state);
|
||||
void LoginSuccess(bool success);
|
||||
|
@ -195,6 +196,7 @@ class VkService : public InternetService {
|
|||
void UpdateRoot();
|
||||
void ShowConfig();
|
||||
void FindUserOrGroup(const QString& q);
|
||||
void DoLocalSearch();
|
||||
|
||||
private slots:
|
||||
/* Interface */
|
||||
|
@ -304,6 +306,7 @@ class VkService : public InternetService {
|
|||
// Keeping when more recent results recived.
|
||||
// Using for prevent loading tardy result instead.
|
||||
uint last_search_id_;
|
||||
QTimer* search_delay_;
|
||||
QString last_query_;
|
||||
Song selected_song_; // Store for context menu actions.
|
||||
Song current_song_; // Store for actions with now playing song.
|
||||
|
|
|
@ -30,7 +30,7 @@ VkUrlHandler::VkUrlHandler(VkService* service, QObject* parent)
|
|||
|
||||
UrlHandler::LoadResult VkUrlHandler::StartLoading(const QUrl& url) {
|
||||
QStringList args = url.path().split("/");
|
||||
LoadResult result;
|
||||
LoadResult result(url);
|
||||
|
||||
if (args.size() < 2) {
|
||||
qLog(Error)
|
||||
|
@ -58,6 +58,6 @@ UrlHandler::LoadResult VkUrlHandler::LoadNext(const QUrl& url) {
|
|||
if (url.host() == "group") {
|
||||
return StartLoading(url);
|
||||
} else {
|
||||
return LoadResult();
|
||||
return LoadResult(url);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,11 +76,13 @@ GroupByDialog::GroupByDialog(QWidget* parent)
|
|||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_FileType, 5));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_Genre, 6));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_Year, 7));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_YearAlbum, 8));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_Bitrate, 9));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_Disc, 10));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_Performer, 11));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_Grouping, 12));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_OriginalYear, 8));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_YearAlbum, 9));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_OriginalYearAlbum, 10));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_Bitrate, 11));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_Disc, 12));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_Performer, 13));
|
||||
p_->mapping_.insert(Mapping(LibraryModel::GroupBy_Grouping, 14));
|
||||
|
||||
connect(ui_->button_box->button(QDialogButtonBox::Reset), SIGNAL(clicked()),
|
||||
SLOT(Reset()));
|
||||
|
|
|
@ -83,11 +83,21 @@
|
|||
<string>Year</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Original year</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Year - Album</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Original year - Album</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bitrate</string>
|
||||
|
@ -98,6 +108,16 @@
|
|||
<string>Disc</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Performer</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Grouping</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
|
@ -149,11 +169,21 @@
|
|||
<string>Year</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Original year</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Year - Album</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Original year - Album</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bitrate</string>
|
||||
|
@ -164,6 +194,16 @@
|
|||
<string>Disc</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Performer</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Grouping</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
|
@ -215,11 +255,21 @@
|
|||
<string>Year</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Original year</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Year - Album</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Original year - Album</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bitrate</string>
|
||||
|
@ -230,6 +280,16 @@
|
|||
<string>Disc</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Performer</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Grouping</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
|
@ -115,6 +115,7 @@ Library::Library(Application* app, QObject* parent)
|
|||
|
||||
// full rescan revisions
|
||||
full_rescan_revisions_[26] = tr("CUE sheet support");
|
||||
full_rescan_revisions_[50] = tr("Original year tag support");
|
||||
|
||||
ReloadSettings();
|
||||
}
|
||||
|
|
|
@ -222,9 +222,15 @@ void LibraryModel::SongsDiscovered(const SongList& songs) {
|
|||
case GroupBy_Year:
|
||||
key = QString::number(qMax(0, song.year()));
|
||||
break;
|
||||
case GroupBy_OriginalYear:
|
||||
key = QString::number(qMax(0, song.effective_originalyear()));
|
||||
break;
|
||||
case GroupBy_YearAlbum:
|
||||
key = PrettyYearAlbum(qMax(0, song.year()), song.album());
|
||||
break;
|
||||
case GroupBy_OriginalYearAlbum:
|
||||
key = PrettyYearAlbum(qMax(0, song.effective_originalyear()), song.album());
|
||||
break;
|
||||
case GroupBy_FileType:
|
||||
key = song.filetype();
|
||||
break;
|
||||
|
@ -314,11 +320,15 @@ QString LibraryModel::DividerKey(GroupBy type, LibraryItem* item) const {
|
|||
}
|
||||
|
||||
case GroupBy_Year:
|
||||
case GroupBy_OriginalYear:
|
||||
return SortTextForNumber(item->sort_text.toInt() / 10 * 10);
|
||||
|
||||
case GroupBy_YearAlbum:
|
||||
return SortTextForNumber(item->metadata.year());
|
||||
|
||||
case GroupBy_OriginalYearAlbum:
|
||||
return SortTextForNumber(item->metadata.effective_originalyear());
|
||||
|
||||
case GroupBy_Bitrate:
|
||||
return SortTextForNumber(item->metadata.bitrate());
|
||||
|
||||
|
@ -348,10 +358,12 @@ QString LibraryModel::DividerDisplayText(GroupBy type,
|
|||
return key.toUpper();
|
||||
|
||||
case GroupBy_YearAlbum:
|
||||
case GroupBy_OriginalYearAlbum:
|
||||
if (key == "0000") return tr("Unknown");
|
||||
return key.toUpper();
|
||||
|
||||
case GroupBy_Year:
|
||||
case GroupBy_OriginalYear:
|
||||
if (key == "0000") return tr("Unknown");
|
||||
return QString::number(key.toInt()); // To remove leading 0s
|
||||
|
||||
|
@ -544,7 +556,8 @@ QVariant LibraryModel::data(const QModelIndex& index, int role) const {
|
|||
item->type == LibraryItem::Type_Container) {
|
||||
GroupBy container_type = group_by_[item->container_level];
|
||||
is_album_node = container_type == GroupBy_Album ||
|
||||
container_type == GroupBy_YearAlbum;
|
||||
container_type == GroupBy_YearAlbum ||
|
||||
container_type == GroupBy_OriginalYearAlbum;
|
||||
}
|
||||
if (is_album_node) {
|
||||
// It has const behaviour some of the time - that's ok right?
|
||||
|
@ -573,6 +586,7 @@ QVariant LibraryModel::data(const LibraryItem* item, int role) const {
|
|||
switch (container_type) {
|
||||
case GroupBy_Album:
|
||||
case GroupBy_YearAlbum:
|
||||
case GroupBy_OriginalYearAlbum:
|
||||
return album_icon_;
|
||||
case GroupBy_Artist:
|
||||
case GroupBy_AlbumArtist:
|
||||
|
@ -798,9 +812,15 @@ void LibraryModel::InitQuery(GroupBy type, LibraryQuery* q) {
|
|||
case GroupBy_YearAlbum:
|
||||
q->SetColumnSpec("DISTINCT year, album, grouping");
|
||||
break;
|
||||
case GroupBy_OriginalYearAlbum:
|
||||
q->SetColumnSpec("DISTINCT year, originalyear, album, grouping");
|
||||
break;
|
||||
case GroupBy_Year:
|
||||
q->SetColumnSpec("DISTINCT year");
|
||||
break;
|
||||
case GroupBy_OriginalYear:
|
||||
q->SetColumnSpec("DISTINCT effective_originalyear");
|
||||
break;
|
||||
case GroupBy_Genre:
|
||||
q->SetColumnSpec("DISTINCT genre");
|
||||
break;
|
||||
|
@ -842,9 +862,19 @@ void LibraryModel::FilterQuery(GroupBy type, LibraryItem* item,
|
|||
q->AddWhere("album", item->metadata.album());
|
||||
q->AddWhere("grouping", item->metadata.grouping());
|
||||
break;
|
||||
case GroupBy_OriginalYearAlbum:
|
||||
q->AddWhere("year", item->metadata.year());
|
||||
q->AddWhere("originalyear", item->metadata.originalyear());
|
||||
q->AddWhere("album", item->metadata.album());
|
||||
q->AddWhere("grouping", item->metadata.grouping());
|
||||
break;
|
||||
|
||||
case GroupBy_Year:
|
||||
q->AddWhere("year", item->key);
|
||||
break;
|
||||
case GroupBy_OriginalYear:
|
||||
q->AddWhere("effective_originalyear", item->key);
|
||||
break;
|
||||
case GroupBy_Composer:
|
||||
q->AddWhere("composer", item->key);
|
||||
break;
|
||||
|
@ -904,6 +934,7 @@ LibraryItem* LibraryModel::ItemFromQuery(GroupBy type, bool signal,
|
|||
int container_level) {
|
||||
LibraryItem* item = InitItem(type, signal, parent, container_level);
|
||||
int year = 0;
|
||||
int effective_originalyear = 0;
|
||||
int bitrate = 0;
|
||||
int disc = 0;
|
||||
|
||||
|
@ -924,12 +955,29 @@ LibraryItem* LibraryModel::ItemFromQuery(GroupBy type, bool signal,
|
|||
item->metadata.album();
|
||||
break;
|
||||
|
||||
case GroupBy_OriginalYearAlbum:
|
||||
item->metadata.set_year(row.value(0).toInt());
|
||||
item->metadata.set_originalyear(row.value(1).toInt());
|
||||
item->metadata.set_album(row.value(2).toString());
|
||||
item->metadata.set_grouping(row.value(3).toString());
|
||||
effective_originalyear = qMax(0, item->metadata.effective_originalyear());
|
||||
item->key = PrettyYearAlbum(effective_originalyear, item->metadata.album());
|
||||
item->sort_text = SortTextForNumber(effective_originalyear) + item->metadata.grouping()
|
||||
+ item->metadata.album();
|
||||
break;
|
||||
|
||||
case GroupBy_Year:
|
||||
year = qMax(0, row.value(0).toInt());
|
||||
item->key = QString::number(year);
|
||||
item->sort_text = SortTextForNumber(year) + " ";
|
||||
break;
|
||||
|
||||
case GroupBy_OriginalYear:
|
||||
year = qMax(0, row.value(0).toInt());
|
||||
item->key = QString::number(year);
|
||||
item->sort_text = SortTextForNumber(year) + " ";
|
||||
break;
|
||||
|
||||
case GroupBy_Composer:
|
||||
case GroupBy_Performer:
|
||||
case GroupBy_Grouping:
|
||||
|
@ -976,6 +1024,8 @@ LibraryItem* LibraryModel::ItemFromSong(GroupBy type, bool signal,
|
|||
int container_level) {
|
||||
LibraryItem* item = InitItem(type, signal, parent, container_level);
|
||||
int year = 0;
|
||||
int originalyear = 0;
|
||||
int effective_originalyear = 0;
|
||||
int bitrate = 0;
|
||||
|
||||
switch (type) {
|
||||
|
@ -993,12 +1043,29 @@ LibraryItem* LibraryModel::ItemFromSong(GroupBy type, bool signal,
|
|||
item->sort_text = SortTextForNumber(year) + s.grouping() + s.album();
|
||||
break;
|
||||
|
||||
case GroupBy_OriginalYearAlbum:
|
||||
year = qMax(0, s.year());
|
||||
originalyear = qMax(0, s.originalyear());
|
||||
effective_originalyear = qMax(0, s.effective_originalyear());
|
||||
item->metadata.set_year(year);
|
||||
item->metadata.set_originalyear(originalyear);
|
||||
item->metadata.set_album(s.album());
|
||||
item->key = PrettyYearAlbum(effective_originalyear, s.album());
|
||||
item->sort_text = SortTextForNumber(effective_originalyear) + s.grouping() + s.album();
|
||||
break;
|
||||
|
||||
case GroupBy_Year:
|
||||
year = qMax(0, s.year());
|
||||
item->key = QString::number(year);
|
||||
item->sort_text = SortTextForNumber(year) + " ";
|
||||
break;
|
||||
|
||||
case GroupBy_OriginalYear:
|
||||
year = qMax(0, s.effective_originalyear());
|
||||
item->key = QString::number(year);
|
||||
item->sort_text = SortTextForNumber(year) + " ";
|
||||
break;
|
||||
|
||||
case GroupBy_Composer:
|
||||
item->key = s.composer();
|
||||
case GroupBy_Performer:
|
||||
|
|
|
@ -85,6 +85,8 @@ class LibraryModel : public SimpleTreeModel<LibraryItem> {
|
|||
GroupBy_Grouping = 10,
|
||||
GroupBy_Bitrate = 11,
|
||||
GroupBy_Disc = 12,
|
||||
GroupBy_OriginalYearAlbum = 13,
|
||||
GroupBy_OriginalYear = 14,
|
||||
};
|
||||
|
||||
struct Grouping {
|
||||
|
|
|
@ -62,6 +62,8 @@ void LibraryItemDelegate::paint(QPainter* painter,
|
|||
QString text(index.data().toString());
|
||||
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
painter->setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
|
||||
QRect text_rect(opt.rect);
|
||||
|
||||
|
@ -89,23 +91,42 @@ void LibraryItemDelegate::paint(QPainter* painter,
|
|||
|
||||
painter->drawPixmap(icon_rect, pixmap);
|
||||
} else {
|
||||
text_rect.setLeft(text_rect.left() + 30);
|
||||
text_rect.setLeft(text_rect.right() - (text_rect.height() + 10));
|
||||
text_rect.setWidth(text_rect.height());
|
||||
}
|
||||
|
||||
QRect highlight_rect;
|
||||
QFont norm_font(opt.font);
|
||||
QColor highlight_color(opt.palette.color(QPalette::Text));
|
||||
highlight_color.setAlpha(200);
|
||||
QBrush brush(highlight_color, Qt::SolidPattern);
|
||||
painter->setPen(QColor(0, 0, 0, 0));
|
||||
painter->setBrush(brush);
|
||||
|
||||
// Draw text highlight fill
|
||||
if (text.length() == 1) {
|
||||
highlight_rect = text_rect;
|
||||
highlight_rect.setWidth(text_rect.width() - 1);
|
||||
highlight_rect.setHeight(text_rect.height() - 1);
|
||||
highlight_rect.setLeft(text_rect.left() + 1);
|
||||
highlight_rect.setTop(text_rect.top() + 1);
|
||||
painter->drawEllipse(highlight_rect);
|
||||
} else {
|
||||
QFontMetrics fm(norm_font);
|
||||
text_rect.setLeft(text_rect.right() - (fm.width(text) + 22));
|
||||
text_rect.setWidth(fm.width(text) + 12);
|
||||
highlight_rect = text_rect;
|
||||
highlight_rect.setWidth(text_rect.width() - 1);
|
||||
highlight_rect.setHeight(text_rect.height() - 1);
|
||||
highlight_rect.setLeft(text_rect.left() + 1);
|
||||
highlight_rect.setTop(text_rect.top() + 1);
|
||||
painter->fillRect(highlight_rect, brush);
|
||||
}
|
||||
|
||||
// Draw the text
|
||||
QFont bold_font(opt.font);
|
||||
bold_font.setBold(true);
|
||||
|
||||
painter->setPen(opt.palette.color(QPalette::Text));
|
||||
painter->setFont(bold_font);
|
||||
painter->drawText(text_rect, text);
|
||||
|
||||
// Draw the line under the item
|
||||
QPen line_pen(opt.palette.color(QPalette::Dark));
|
||||
line_pen.setWidth(2);
|
||||
|
||||
painter->setPen(line_pen);
|
||||
painter->drawLine(opt.rect.bottomLeft(), opt.rect.bottomRight());
|
||||
painter->setPen(opt.palette.color(QPalette::Window));
|
||||
painter->setFont(norm_font);
|
||||
painter->drawText(text_rect, text, Qt::AlignVCenter | Qt::AlignHCenter);
|
||||
|
||||
painter->restore();
|
||||
} else {
|
||||
|
|
|
@ -390,6 +390,8 @@ int main(int argc, char* argv[]) {
|
|||
// Add root CA cert for SoundCloud, whose certificate is missing on OS X.
|
||||
QSslSocket::addDefaultCaCertificates(
|
||||
QSslCertificate::fromPath(":/soundcloud-ca.pem", QSsl::Pem));
|
||||
QSslSocket::addDefaultCaCertificates(
|
||||
QSslCertificate::fromPath(":/Equifax_Secure_Certificate_Authority.pem", QSsl::Pem));
|
||||
|
||||
// Has the user forced a different language?
|
||||
QString override_language = options.language();
|
||||
|
|
|
@ -33,6 +33,10 @@
|
|||
#include "core/logging.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
MoodbarLoader::MoodbarLoader(Application* app, QObject* parent)
|
||||
: QObject(parent),
|
||||
cache_(new QNetworkDiskCache(this)),
|
||||
|
@ -166,6 +170,15 @@ void MoodbarLoader::RequestFinished(MoodbarPipeline* request, const QUrl& url) {
|
|||
QFile mood_file(mood_filename);
|
||||
if (mood_file.open(QIODevice::WriteOnly)) {
|
||||
mood_file.write(request->data());
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
if (!SetFileAttributes((LPCTSTR)mood_filename.utf16(),
|
||||
FILE_ATTRIBUTE_HIDDEN)) {
|
||||
qLog(Warning) << "Error setting hidden attribute for file"
|
||||
<< mood_filename;
|
||||
}
|
||||
#endif
|
||||
|
||||
} else {
|
||||
qLog(Warning) << "Error opening mood file for writing" << mood_filename;
|
||||
}
|
||||
|
|
|
@ -58,8 +58,8 @@ void MusicBrainzClient::Start(int id, const QStringList& mbid_list) {
|
|||
|
||||
QNetworkReply* reply = network_->get(req);
|
||||
NewClosure(reply, SIGNAL(finished()), this,
|
||||
SLOT(RequestFinished(QNetworkReply*, int, int)),
|
||||
reply, id, request_number++);
|
||||
SLOT(RequestFinished(QNetworkReply*, int, int)), reply, id,
|
||||
request_number++);
|
||||
requests_.insert(id, reply);
|
||||
|
||||
timeouts_->AddReply(reply);
|
||||
|
@ -104,12 +104,13 @@ void MusicBrainzClient::DiscIdRequestFinished(const QString& discid,
|
|||
ResultList ret;
|
||||
QString artist;
|
||||
QString album;
|
||||
int year = 0;
|
||||
|
||||
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() !=
|
||||
200) {
|
||||
qLog(Error) << "Error:" <<
|
||||
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() <<
|
||||
"http status code received";
|
||||
qLog(Error) << "Error:"
|
||||
<< reply->attribute(QNetworkRequest::HttpStatusCodeAttribute)
|
||||
.toInt() << "http status code received";
|
||||
qLog(Error) << reply->readAll();
|
||||
emit Finished(artist, album, ret);
|
||||
return;
|
||||
|
@ -118,6 +119,7 @@ void MusicBrainzClient::DiscIdRequestFinished(const QString& discid,
|
|||
// Parse xml result:
|
||||
// -get title
|
||||
// -get artist
|
||||
// -get year
|
||||
// -get all the tracks' tags
|
||||
// Note: If there are multiple releases for the discid, the first
|
||||
// release is chosen.
|
||||
|
@ -128,6 +130,11 @@ void MusicBrainzClient::DiscIdRequestFinished(const QString& discid,
|
|||
QStringRef name = reader.name();
|
||||
if (name == "title") {
|
||||
album = reader.readElementText();
|
||||
} else if (name == "date") {
|
||||
QRegExp regex(kDateRegex);
|
||||
if (regex.indexIn(reader.readElementText()) == 0) {
|
||||
year = regex.cap(0).toInt();
|
||||
}
|
||||
} else if (name == "artist-credit") {
|
||||
ParseArtist(&reader, &artist);
|
||||
} else if (name == "medium-list") {
|
||||
|
@ -156,16 +163,25 @@ void MusicBrainzClient::DiscIdRequestFinished(const QString& discid,
|
|||
}
|
||||
}
|
||||
|
||||
// If we parsed a year, copy it to the tracks.
|
||||
if (year > 0) {
|
||||
for (ResultList::iterator it = ret.begin(); it != ret.end(); ++it) {
|
||||
it->year_ = year;
|
||||
}
|
||||
}
|
||||
|
||||
emit Finished(artist, album, UniqueResults(ret, SortResults));
|
||||
}
|
||||
|
||||
void MusicBrainzClient::RequestFinished(QNetworkReply* reply, int id, int request_number) {
|
||||
void MusicBrainzClient::RequestFinished(QNetworkReply* reply, int id,
|
||||
int request_number) {
|
||||
reply->deleteLater();
|
||||
|
||||
const int nb_removed = requests_.remove(id, reply);
|
||||
if (nb_removed != 1) {
|
||||
qLog(Error) << "Error: unknown reply received:" << nb_removed <<
|
||||
"requests removed, while only one was supposed to be removed";
|
||||
qLog(Error)
|
||||
<< "Error: unknown reply received:" << nb_removed
|
||||
<< "requests removed, while only one was supposed to be removed";
|
||||
}
|
||||
|
||||
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() ==
|
||||
|
@ -185,8 +201,9 @@ void MusicBrainzClient::RequestFinished(QNetworkReply* reply, int id, int reques
|
|||
}
|
||||
pending_results_[id] << PendingResults(request_number, res);
|
||||
} else {
|
||||
qLog(Error) << "Error:" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() <<
|
||||
"http status code received";
|
||||
qLog(Error) << "Error:"
|
||||
<< reply->attribute(QNetworkRequest::HttpStatusCodeAttribute)
|
||||
.toInt() << "http status code received";
|
||||
qLog(Error) << reply->readAll();
|
||||
}
|
||||
|
||||
|
@ -366,7 +383,6 @@ MusicBrainzClient::Release MusicBrainzClient::ParseRelease(
|
|||
|
||||
MusicBrainzClient::ResultList MusicBrainzClient::UniqueResults(
|
||||
const ResultList& results, UniqueResultsSortOption opt) {
|
||||
|
||||
ResultList ret;
|
||||
if (opt == SortResults) {
|
||||
ret = QSet<Result>::fromList(results).toList();
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <algorithm>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "engines/enginebase.h"
|
||||
#include "internet/core/internetmodel.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
|
@ -60,6 +61,9 @@ IncomingDataParser::IncomingDataParser(Application* app) : app_(app) {
|
|||
connect(this, SIGNAL(InsertUrls(int, const QList<QUrl>&, int, bool, bool)),
|
||||
app_->playlist_manager(),
|
||||
SLOT(InsertUrls(int, const QList<QUrl>&, int, bool, bool)));
|
||||
connect(this, SIGNAL(InsertSongs(int, const SongList&, int, bool, bool)),
|
||||
app_->playlist_manager(),
|
||||
SLOT(InsertSongs(int, const SongList&, int, bool, bool)));
|
||||
connect(this, SIGNAL(RemoveSongs(int, const QList<int>&)),
|
||||
app_->playlist_manager(),
|
||||
SLOT(RemoveItemsWithoutUndo(int, const QList<int>&)));
|
||||
|
@ -235,7 +239,8 @@ void IncomingDataParser::SetShuffleMode(const pb::remote::Shuffle& shuffle) {
|
|||
void IncomingDataParser::InsertUrls(const pb::remote::Message& msg) {
|
||||
const pb::remote::RequestInsertUrls& request = msg.request_insert_urls();
|
||||
|
||||
// Extract urls
|
||||
// Insert plain urls without metadata
|
||||
if (request.urls().size() > 0) {
|
||||
QList<QUrl> urls;
|
||||
for (auto it = request.urls().begin(); it != request.urls().end(); ++it) {
|
||||
std::string s = *it;
|
||||
|
@ -245,6 +250,17 @@ void IncomingDataParser::InsertUrls(const pb::remote::Message& msg) {
|
|||
// Insert the urls
|
||||
emit InsertUrls(request.playlist_id(), urls, request.position(),
|
||||
request.play_now(), request.enqueue());
|
||||
}
|
||||
|
||||
// Add songs with metadata if present
|
||||
if (request.songs().size() > 0) {
|
||||
SongList songs;
|
||||
for (int i = 0; i < request.songs().size(); i++) {
|
||||
songs << CreateSongFromProtobuf(request.songs(i));
|
||||
}
|
||||
emit InsertSongs(request.playlist_id(), songs, request.position(),
|
||||
request.play_now(), request.enqueue());
|
||||
}
|
||||
}
|
||||
|
||||
void IncomingDataParser::RemoveSongs(const pb::remote::Message& msg) {
|
||||
|
@ -301,3 +317,26 @@ void IncomingDataParser::GlobalSearch(RemoteClient *client, const pb::remote::Me
|
|||
emit DoGlobalSearch(QStringFromStdString(msg.request_global_search().query()),
|
||||
client);
|
||||
}
|
||||
|
||||
Song IncomingDataParser::CreateSongFromProtobuf(const pb::remote::SongMetadata& pb){
|
||||
Song song;
|
||||
song.Init(QStringFromStdString(pb.title()),
|
||||
QStringFromStdString(pb.artist()),
|
||||
QStringFromStdString(pb.album()),
|
||||
pb.length() * kNsecPerSec);
|
||||
|
||||
song.set_albumartist(QStringFromStdString(pb.albumartist()));
|
||||
song.set_genre(QStringFromStdString(pb.genre()));
|
||||
song.set_year(QStringFromStdString(pb.pretty_year()).toInt());
|
||||
song.set_track(pb.track());
|
||||
song.set_disc(pb.disc());
|
||||
song.set_url(QUrl(QStringFromStdString(pb.url())));
|
||||
song.set_filesize(pb.file_size());
|
||||
song.set_rating(pb.rating());
|
||||
song.set_basefilename(QStringFromStdString(pb.filename()));
|
||||
song.set_art_automatic(QStringFromStdString(pb.art_automatic()));
|
||||
song.set_art_manual(QStringFromStdString(pb.art_manual()));
|
||||
song.set_filetype(static_cast<Song::FileType>(pb.type()));
|
||||
|
||||
return song;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ signals:
|
|||
void SetShuffleMode(PlaylistSequence::ShuffleMode mode);
|
||||
void InsertUrls(int id, const QList<QUrl>& urls, int pos, bool play_now,
|
||||
bool enqueue);
|
||||
void InsertSongs(int id, const SongList& songs, int pos, bool play_now,
|
||||
bool enqueue);
|
||||
void RemoveSongs(int id, const QList<int>& indices);
|
||||
void SeekTo(int seconds);
|
||||
void SendLibrary(RemoteClient* client);
|
||||
|
@ -67,6 +69,8 @@ signals:
|
|||
void ClosePlaylist(const pb::remote::Message& msg);
|
||||
void RateSong(const pb::remote::Message& msg);
|
||||
void GlobalSearch(RemoteClient* client, const pb::remote::Message& msg);
|
||||
|
||||
Song CreateSongFromProtobuf(const pb::remote::SongMetadata& pb);
|
||||
};
|
||||
|
||||
#endif // INCOMINGDATAPARSER_H
|
||||
|
|
|
@ -79,10 +79,8 @@ void OutgoingDataCreator::SetClients(QList<RemoteClient*>* clients) {
|
|||
SLOT(ResultsAvailable(int, SearchProvider::ResultList)),
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(app_->global_search(),
|
||||
SIGNAL(SearchFinished(int)),
|
||||
SLOT(SearchFinished(int)),
|
||||
Qt::QueuedConnection);
|
||||
connect(app_->global_search(), SIGNAL(SearchFinished(int)),
|
||||
SLOT(SearchFinished(int)), Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::CheckEnabledProviders() {
|
||||
|
@ -379,6 +377,11 @@ void OutgoingDataCreator::CreateSong(const Song& song, const QImage& art,
|
|||
song_metadata->set_file_size(song.filesize());
|
||||
song_metadata->set_rating(song.rating());
|
||||
song_metadata->set_url(DataCommaSizeFromQString(song.url().toString()));
|
||||
song_metadata->set_art_automatic(
|
||||
DataCommaSizeFromQString(song.art_automatic()));
|
||||
song_metadata->set_art_manual(DataCommaSizeFromQString(song.art_manual()));
|
||||
song_metadata->set_type(
|
||||
static_cast< ::pb::remote::SongMetadata_Type>(song.filetype()));
|
||||
|
||||
// Append coverart
|
||||
if (!art.isNull()) {
|
||||
|
@ -503,6 +506,9 @@ void OutgoingDataCreator::SendRepeatMode(PlaylistSequence::RepeatMode mode) {
|
|||
case PlaylistSequence::Repeat_OneByOne:
|
||||
msg.mutable_repeat()->set_repeat_mode(pb::remote::Repeat_OneByOne);
|
||||
break;
|
||||
case PlaylistSequence::Repeat_Intro:
|
||||
msg.mutable_repeat()->set_repeat_mode(pb::remote::Repeat_Intro);
|
||||
break;
|
||||
}
|
||||
|
||||
SendDataToClients(&msg);
|
||||
|
@ -670,7 +676,8 @@ void OutgoingDataCreator::DoGlobalSearch(const QString& query,
|
|||
|
||||
// Send status message
|
||||
pb::remote::Message msg;
|
||||
pb::remote::ResponseGlobalSearchStatus* status = msg.mutable_response_global_search_status();
|
||||
pb::remote::ResponseGlobalSearchStatus* status =
|
||||
msg.mutable_response_global_search_status();
|
||||
|
||||
msg.set_type(pb::remote::GLOBAL_SEARCH_STATUS);
|
||||
status->set_id(id);
|
||||
|
@ -715,7 +722,8 @@ void OutgoingDataCreator::ResultsAvailable(
|
|||
|
||||
client->SendData(&msg);
|
||||
|
||||
qLog(Debug) << "ResultsAvailable" << id << results.first().provider_->name() << results.size();
|
||||
qLog(Debug) << "ResultsAvailable" << id << results.first().provider_->name()
|
||||
<< results.size();
|
||||
}
|
||||
|
||||
void OutgoingDataCreator::SearchFinished(int id) {
|
||||
|
@ -725,7 +733,8 @@ void OutgoingDataCreator::SearchFinished(int id) {
|
|||
|
||||
// Send status message
|
||||
pb::remote::Message msg;
|
||||
pb::remote::ResponseGlobalSearchStatus* status = msg.mutable_response_global_search_status();
|
||||
pb::remote::ResponseGlobalSearchStatus* status =
|
||||
msg.mutable_response_global_search_status();
|
||||
|
||||
msg.set_type(pb::remote::GLOBAL_SEARCH_STATUS);
|
||||
status->set_id(req.id_);
|
||||
|
|
|
@ -284,6 +284,8 @@ QVariant Playlist::data(const QModelIndex& index, int role) const {
|
|||
return song.disc();
|
||||
case Column_Year:
|
||||
return song.year();
|
||||
case Column_OriginalYear:
|
||||
return song.effective_originalyear();
|
||||
case Column_Genre:
|
||||
return song.genre();
|
||||
case Column_AlbumArtist:
|
||||
|
@ -562,6 +564,7 @@ int Playlist::next_row(bool ignore_repeat_track) const {
|
|||
|
||||
switch (playlist_sequence_->repeat_mode()) {
|
||||
case PlaylistSequence::Repeat_Off:
|
||||
case PlaylistSequence::Repeat_Intro:
|
||||
return -1;
|
||||
case PlaylistSequence::Repeat_Track:
|
||||
next_virtual_index = current_virtual_index_;
|
||||
|
@ -1250,6 +1253,8 @@ bool Playlist::CompareItems(int column, Qt::SortOrder order,
|
|||
cmp(disc);
|
||||
case Column_Year:
|
||||
cmp(year);
|
||||
case Column_OriginalYear:
|
||||
cmp(originalyear);
|
||||
case Column_Genre:
|
||||
strcmp(genre);
|
||||
case Column_AlbumArtist:
|
||||
|
@ -1319,6 +1324,8 @@ QString Playlist::column_name(Column column) {
|
|||
return tr("Disc");
|
||||
case Column_Year:
|
||||
return tr("Year");
|
||||
case Column_OriginalYear:
|
||||
return tr("Original year");
|
||||
case Column_Genre:
|
||||
return tr("Genre");
|
||||
case Column_AlbumArtist:
|
||||
|
@ -1394,8 +1401,18 @@ void Playlist::sort(int column, Qt::SortOrder order) {
|
|||
if (dynamic_playlist_ && current_item_index_.isValid())
|
||||
begin += current_item_index_.row() + 1;
|
||||
|
||||
if (column == Column_Album) {
|
||||
// When sorting by album, also take into account discs and tracks.
|
||||
qStableSort(begin, new_items.end(), std::bind(&Playlist::CompareItems,
|
||||
Column_Track, order, _1, _2));
|
||||
qStableSort(begin, new_items.end(),
|
||||
std::bind(&Playlist::CompareItems, Column_Disc, order, _1, _2));
|
||||
qStableSort(begin, new_items.end(), std::bind(&Playlist::CompareItems,
|
||||
Column_Album, order, _1, _2));
|
||||
} else {
|
||||
qStableSort(begin, new_items.end(),
|
||||
std::bind(&Playlist::CompareItems, column, order, _1, _2));
|
||||
}
|
||||
|
||||
undo_stack_->push(
|
||||
new PlaylistUndoCommands::SortItems(this, column, order, new_items));
|
||||
|
|
|
@ -114,6 +114,7 @@ class Playlist : public QAbstractListModel {
|
|||
Column_Mood,
|
||||
Column_Performer,
|
||||
Column_Grouping,
|
||||
Column_OriginalYear,
|
||||
ColumnCount
|
||||
};
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ PlaylistFilter::PlaylistFilter(QObject* parent)
|
|||
column_names_["track"] = Playlist::Column_Track;
|
||||
column_names_["disc"] = Playlist::Column_Disc;
|
||||
column_names_["year"] = Playlist::Column_Year;
|
||||
column_names_["originalyear"] = Playlist::Column_OriginalYear;
|
||||
column_names_["genre"] = Playlist::Column_Genre;
|
||||
column_names_["score"] = Playlist::Column_Score;
|
||||
column_names_["comment"] = Playlist::Column_Comment;
|
||||
|
@ -48,8 +49,9 @@ PlaylistFilter::PlaylistFilter(QObject* parent)
|
|||
|
||||
numerical_columns_ << Playlist::Column_Length << Playlist::Column_Track
|
||||
<< Playlist::Column_Disc << Playlist::Column_Year
|
||||
<< Playlist::Column_Score << Playlist::Column_BPM
|
||||
<< Playlist::Column_Bitrate << Playlist::Column_Rating;
|
||||
<< Playlist::Column_OriginalYear << Playlist::Column_Score
|
||||
<< Playlist::Column_BPM << Playlist::Column_Bitrate
|
||||
<< Playlist::Column_Rating;
|
||||
}
|
||||
|
||||
PlaylistFilter::~PlaylistFilter() {}
|
||||
|
|
|
@ -470,6 +470,13 @@ void PlaylistManager::InsertUrls(int id, const QList<QUrl>& urls, int pos,
|
|||
playlists_[id].p->InsertUrls(urls, pos, play_now, enqueue);
|
||||
}
|
||||
|
||||
void PlaylistManager::InsertSongs(int id, const SongList& songs, int pos,
|
||||
bool play_now, bool enqueue) {
|
||||
Q_ASSERT(playlists_.contains(id));
|
||||
|
||||
playlists_[id].p->InsertSongs(songs, pos, play_now, enqueue);
|
||||
}
|
||||
|
||||
void PlaylistManager::RemoveItemsWithoutUndo(int id,
|
||||
const QList<int>& indices) {
|
||||
Q_ASSERT(playlists_.contains(id));
|
||||
|
|
|
@ -220,6 +220,8 @@ class PlaylistManager : public PlaylistManagerInterface {
|
|||
|
||||
void InsertUrls(int id, const QList<QUrl>& urls, int pos = -1,
|
||||
bool play_now = false, bool enqueue = false);
|
||||
void InsertSongs(int id, const SongList& songs, int pos = -1,
|
||||
bool play_now = false, bool enqueue = false);
|
||||
// Removes items with given indices from the playlist. This operation is not
|
||||
// undoable.
|
||||
void RemoveItemsWithoutUndo(int id, const QList<int>& indices);
|
||||
|
|
|
@ -53,6 +53,7 @@ PlaylistSequence::PlaylistSequence(QWidget* parent, SettingsProvider* settings)
|
|||
repeat_group->addAction(ui_->action_repeat_album);
|
||||
repeat_group->addAction(ui_->action_repeat_playlist);
|
||||
repeat_group->addAction(ui_->action_repeat_onebyone);
|
||||
repeat_group->addAction(ui_->action_repeat_intro);
|
||||
repeat_menu_->addActions(repeat_group->actions());
|
||||
ui_->repeat->setMenu(repeat_menu_);
|
||||
|
||||
|
@ -120,6 +121,7 @@ void PlaylistSequence::RepeatActionTriggered(QAction* action) {
|
|||
if (action == ui_->action_repeat_album) mode = Repeat_Album;
|
||||
if (action == ui_->action_repeat_playlist) mode = Repeat_Playlist;
|
||||
if (action == ui_->action_repeat_onebyone) mode = Repeat_OneByOne;
|
||||
if (action == ui_->action_repeat_intro) mode = Repeat_Intro;
|
||||
|
||||
SetRepeatMode(mode);
|
||||
}
|
||||
|
@ -152,6 +154,9 @@ void PlaylistSequence::SetRepeatMode(RepeatMode mode) {
|
|||
case Repeat_OneByOne:
|
||||
ui_->action_repeat_onebyone->setChecked(true);
|
||||
break;
|
||||
case Repeat_Intro:
|
||||
ui_->action_repeat_intro->setChecked(true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (mode != repeat_mode_) {
|
||||
|
@ -245,6 +250,9 @@ void PlaylistSequence::CycleRepeatMode() {
|
|||
mode = Repeat_OneByOne;
|
||||
break;
|
||||
case Repeat_OneByOne:
|
||||
mode = Repeat_Intro;
|
||||
break;
|
||||
case Repeat_Intro:
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ class PlaylistSequence : public QWidget {
|
|||
Repeat_Album = 2,
|
||||
Repeat_Playlist = 3,
|
||||
Repeat_OneByOne = 4,
|
||||
Repeat_Intro = 5,
|
||||
};
|
||||
enum ShuffleMode {
|
||||
Shuffle_Off = 0,
|
||||
|
|
|
@ -106,6 +106,14 @@
|
|||
<string>Stop after each track</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_repeat_intro">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Intro tracks</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_shuffle_off">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
|
|
|
@ -321,6 +321,7 @@ void PlaylistView::LoadGeometry() {
|
|||
if (!header_->restoreState(state)) {
|
||||
header_->HideSection(Playlist::Column_Disc);
|
||||
header_->HideSection(Playlist::Column_Year);
|
||||
header_->HideSection(Playlist::Column_OriginalYear);
|
||||
header_->HideSection(Playlist::Column_Genre);
|
||||
header_->HideSection(Playlist::Column_BPM);
|
||||
header_->HideSection(Playlist::Column_Bitrate);
|
||||
|
@ -586,10 +587,10 @@ void PlaylistView::keyPressEvent(QKeyEvent* event) {
|
|||
emit PlayPause();
|
||||
event->accept();
|
||||
} else if (event->key() == Qt::Key_Left) {
|
||||
emit SeekTrack(-1);
|
||||
emit SeekBackward();
|
||||
event->accept();
|
||||
} else if (event->key() == Qt::Key_Right) {
|
||||
emit SeekTrack(1);
|
||||
emit SeekForward();
|
||||
event->accept();
|
||||
} else if (event->modifiers() ==
|
||||
Qt::NoModifier // No modifier keys currently pressed...
|
||||
|
@ -1191,6 +1192,7 @@ ColumnAlignmentMap PlaylistView::DefaultColumnAlignment() {
|
|||
ret[Playlist::Column_Filesize] =
|
||||
ret[Playlist::Column_PlayCount] =
|
||||
ret[Playlist::Column_SkipCount] =
|
||||
ret[Playlist::Column_OriginalYear] =
|
||||
(Qt::AlignRight | Qt::AlignVCenter);
|
||||
ret[Playlist::Column_Score] = (Qt::AlignCenter);
|
||||
|
||||
|
|
|
@ -109,7 +109,8 @@ signals:
|
|||
void PlayItem(const QModelIndex& index);
|
||||
void PlayPause();
|
||||
void RightClicked(const QPoint& global_pos, const QModelIndex& index);
|
||||
void SeekTrack(int gap);
|
||||
void SeekForward();
|
||||
void SeekBackward();
|
||||
void FocusOnFilterSignal(QKeyEvent* event);
|
||||
void BackgroundPropertyChanged();
|
||||
void ColumnAlignmentChanged(const ColumnAlignmentMap& alignment);
|
||||
|
|
|
@ -286,12 +286,14 @@ QString RipCDDialog::GetOutputFileName(const QString& basename) const {
|
|||
QString RipCDDialog::ParseFileFormatString(const QString& file_format,
|
||||
int track_no) const {
|
||||
QString to_return = file_format;
|
||||
to_return.replace(QString("%artist%"), ui_->artistLineEdit->text());
|
||||
to_return.replace(QString("%album%"), ui_->albumLineEdit->text());
|
||||
to_return.replace(QString("%genre%"), ui_->genreLineEdit->text());
|
||||
to_return.replace(QString("%year%"), ui_->yearLineEdit->text());
|
||||
to_return.replace(QString("%tracknum%"), QString::number(track_no));
|
||||
to_return.replace(QString("%track%"),
|
||||
to_return.replace(QString("%artist"), ui_->artistLineEdit->text());
|
||||
to_return.replace(QString("%album"), ui_->albumLineEdit->text());
|
||||
to_return.replace(QString("%disc"), ui_->discLineEdit->text());
|
||||
to_return.replace(QString("%genre"), ui_->genreLineEdit->text());
|
||||
to_return.replace(QString("%year"), ui_->yearLineEdit->text());
|
||||
to_return.replace(QString("%title"),
|
||||
track_names_.value(track_no - 1)->text());
|
||||
to_return.replace(QString("%track"), QString::number(track_no));
|
||||
|
||||
return to_return;
|
||||
}
|
||||
|
|
|
@ -204,7 +204,7 @@
|
|||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="format_filename">
|
||||
<property name="text">
|
||||
<string notr="true">%tracknum% - %artist% - %track%</string>
|
||||
<string notr="true">%track - %artist - %title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -160,6 +160,7 @@ SearchTerm::Type SearchTerm::TypeOf(Field field) {
|
|||
case Field_Track:
|
||||
case Field_Disc:
|
||||
case Field_Year:
|
||||
case Field_OriginalYear:
|
||||
case Field_BPM:
|
||||
case Field_Bitrate:
|
||||
case Field_Samplerate:
|
||||
|
@ -253,6 +254,8 @@ QString SearchTerm::FieldColumnName(Field field) {
|
|||
return "disc";
|
||||
case Field_Year:
|
||||
return "year";
|
||||
case Field_OriginalYear:
|
||||
return "originalyear";
|
||||
case Field_BPM:
|
||||
return "bpm";
|
||||
case Field_Bitrate:
|
||||
|
@ -311,6 +314,8 @@ QString SearchTerm::FieldName(Field field) {
|
|||
return Playlist::column_name(Playlist::Column_Disc);
|
||||
case Field_Year:
|
||||
return Playlist::column_name(Playlist::Column_Year);
|
||||
case Field_OriginalYear:
|
||||
return Playlist::column_name(Playlist::Column_OriginalYear);
|
||||
case Field_BPM:
|
||||
return Playlist::column_name(Playlist::Column_BPM);
|
||||
case Field_Bitrate:
|
||||
|
|
|
@ -52,6 +52,7 @@ class SearchTerm {
|
|||
Field_Filepath,
|
||||
Field_Performer,
|
||||
Field_Grouping,
|
||||
Field_OriginalYear,
|
||||
FieldCount
|
||||
};
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <QPropertyAnimation>
|
||||
#include <QTimer>
|
||||
#include <QtDebug>
|
||||
#include <QKeyEvent>
|
||||
|
||||
// Exported by QtGui
|
||||
void qt_blurImage(QPainter* p, QImage& blurImage, qreal radius, bool quality,
|
||||
|
@ -49,6 +50,7 @@ class SearchTermWidget::Overlay : public QWidget {
|
|||
protected:
|
||||
void paintEvent(QPaintEvent*);
|
||||
void mouseReleaseEvent(QMouseEvent*);
|
||||
void keyReleaseEvent(QKeyEvent* e);
|
||||
|
||||
private:
|
||||
SearchTermWidget* parent_;
|
||||
|
@ -209,6 +211,8 @@ void SearchTermWidget::SetActive(bool active) {
|
|||
delete overlay_;
|
||||
overlay_ = nullptr;
|
||||
|
||||
ui_->container->setEnabled(active);
|
||||
|
||||
if (!active) {
|
||||
overlay_ = new Overlay(this);
|
||||
}
|
||||
|
@ -352,6 +356,7 @@ SearchTermWidget::Overlay::Overlay(SearchTermWidget* parent)
|
|||
text_(tr("Add search term")),
|
||||
icon_(IconLoader::Load("list-add").pixmap(kIconSize)) {
|
||||
raise();
|
||||
setFocusPolicy(Qt::TabFocus);
|
||||
}
|
||||
|
||||
void SearchTermWidget::Overlay::SetOpacity(float opacity) {
|
||||
|
@ -417,4 +422,8 @@ void SearchTermWidget::Overlay::mouseReleaseEvent(QMouseEvent*) {
|
|||
emit parent_->Clicked();
|
||||
}
|
||||
|
||||
void SearchTermWidget::Overlay::keyReleaseEvent(QKeyEvent* e) {
|
||||
if (e->key() == Qt::Key_Space) emit parent_->Clicked();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue