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