Compare commits

...
Sign in to create a new pull request.

68 commits

Author SHA1 Message Date
e9c3a93e16 Unfix value that wasn't supposed to be fixed, resulting in the system title bar always being used.
Some checks failed
Build / build-gentoo (push) Failing after 21s
Build / download-system-deps (push) Successful in 7m25s
Build / get-source-code (push) Successful in 26m16s
Build / build-deb (push) Failing after 9m57s
Build / build-appimage (push) Successful in 7m59s
Build / build-android (push) Failing after 6m53s
Build / build-windows (push) Failing after 13m7s
2025-01-25 10:59:33 -08:00
68e067348b Support using system titlebar in Imgui backend
Some checks failed
Build / build-deb (push) Blocked by required conditions
Build / build-appimage (push) Blocked by required conditions
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / build-gentoo (push) Failing after 17s
Build / download-system-deps (push) Successful in 6m37s
Build / get-source-code (push) Has been cancelled
2025-01-25 10:48:34 -08:00
49ea484034 Support properties in GME backend 2025-01-25 10:48:02 -08:00
a90da8fa95 Support enable/disable of voices in GME backend 2025-01-25 08:11:07 -08:00
a53b2498cb Don't scale cat larger than it is
Some checks failed
Build / build-gentoo (push) Failing after 15s
Build / download-system-deps (push) Successful in 4m34s
Build / get-source-code (push) Successful in 14m59s
Build / build-deb (push) Failing after 5m15s
Build / build-appimage (push) Successful in 4m41s
Build / build-android (push) Failing after 3m15s
Build / build-windows (push) Failing after 7m50s
2025-01-21 12:48:19 -08:00
c1fe6cae2b Remove libcurl header requirement 2025-01-21 12:47:31 -08:00
82c3a77f51 Fix overflow bug in Haiku frontend when the playback position ends up larger than the length 2025-01-21 12:46:53 -08:00
c2a971d225 Use system game-music-emu
Some checks failed
Build / download-system-deps (push) Successful in 5m0s
Build / build-gentoo (push) Failing after 14s
Build / get-source-code (push) Successful in 12m59s
Build / build-deb (push) Failing after 5m28s
Build / build-appimage (push) Successful in 4m46s
Build / build-android (push) Failing after 3m22s
Build / build-windows (push) Failing after 7m54s
2025-01-18 09:59:06 -08:00
8d9cf9372e Add Game Music Emu backend
Some checks failed
Build / build-gentoo (push) Failing after 14s
Build / download-system-deps (push) Successful in 4m36s
Build / get-source-code (push) Successful in 17m33s
Build / build-deb (push) Failing after 12m45s
Build / build-appimage (push) Successful in 5m23s
Build / build-android (push) Failing after 3m11s
Build / build-windows (push) Failing after 7m37s
2025-01-18 07:47:47 -08:00
53332db3e6 Change how getting the current title works, and fix ZSM backend not setting itself as opened.
Some checks failed
Build / build-gentoo (push) Successful in 2m12s
Build / download-system-deps (push) Successful in 6m0s
Build / get-source-code (push) Successful in 13m41s
Build / build-deb (push) Failing after 5m47s
Build / build-appimage (push) Successful in 4m44s
Build / build-android (push) Failing after 3m7s
Build / build-windows (push) Failing after 7m19s
2025-01-16 09:15:21 -08:00
a41c63d059 Make subtitle color configurable, and prepare for more configurable colors specific to Looper
Some checks failed
Build / build-gentoo (push) Successful in 1m52s
Build / download-system-deps (push) Successful in 5m28s
Build / get-source-code (push) Successful in 16m4s
Build / build-deb (push) Failing after 6m59s
Build / build-appimage (push) Successful in 6m19s
Build / build-android (push) Failing after 3m19s
Build / build-windows (push) Failing after 7m13s
2025-01-15 13:52:50 -08:00
3bb33cb3bf Add forgotten multiply by two to titlebar button right alignment position calculation.
Some checks failed
Build / build-gentoo (push) Successful in 2m52s
Build / download-system-deps (push) Successful in 5m53s
Build / get-source-code (push) Successful in 13m26s
Build / build-deb (push) Failing after 5m53s
Build / build-appimage (push) Successful in 4m33s
Build / build-android (push) Failing after 3m13s
Build / build-windows (push) Failing after 6m56s
2025-01-15 12:54:30 -08:00
152ee66a04 Fix using addition instead of subtraction for right alignment of titlebar buttons.
Some checks failed
Build / build-deb (push) Blocked by required conditions
Build / build-appimage (push) Blocked by required conditions
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / build-gentoo (push) Successful in 2m15s
Build / download-system-deps (push) Successful in 5m49s
Build / get-source-code (push) Has been cancelled
2025-01-15 12:45:17 -08:00
553e8b58a5 Use menu items for titlebar buttons, so the titlebar looks better.
Some checks failed
Build / build-deb (push) Blocked by required conditions
Build / build-appimage (push) Blocked by required conditions
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / build-gentoo (push) Successful in 2m8s
Build / download-system-deps (push) Successful in 5m20s
Build / get-source-code (push) Has been cancelled
2025-01-15 12:27:41 -08:00
0bcb5d42d2 Add subtitle system to imgui backend, and use it.
Some checks failed
Build / build-gentoo (push) Successful in 2m0s
Build / download-system-deps (push) Successful in 5m23s
Build / get-source-code (push) Successful in 13m49s
Build / build-deb (push) Failing after 5m19s
Build / build-appimage (push) Successful in 4m35s
Build / build-android (push) Failing after 2m50s
Build / build-windows (push) Failing after 7m18s
2025-01-15 08:54:52 -08:00
84b324e01c Fix not being able to get the title of the playing audio file, due to using an obsolete way to get it in the playback class 2025-01-15 08:54:09 -08:00
16995b71b3 Use is_maximized and fix incorrect ifdef
Some checks failed
Build / build-gentoo (push) Successful in 1m49s
Build / download-system-deps (push) Successful in 4m42s
Build / get-source-code (push) Successful in 13m57s
Build / build-deb (push) Failing after 5m37s
Build / build-appimage (push) Successful in 4m32s
Build / build-android (push) Failing after 3m0s
Build / build-windows (push) Failing after 7m30s
2025-01-14 16:38:16 -08:00
a2a93558a8 Don't allow resize when maximized, and prepare for potential fullscreen mode
Some checks failed
Build / build-deb (push) Blocked by required conditions
Build / build-appimage (push) Blocked by required conditions
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / build-gentoo (push) Successful in 1m29s
Build / download-system-deps (push) Successful in 3m46s
Build / get-source-code (push) Has been cancelled
2025-01-14 16:22:51 -08:00
80f29047bd Fix issues related to sample-based time codes
Some checks failed
Build / build-gentoo (push) Successful in 2m19s
Build / download-system-deps (push) Successful in 6m53s
Build / get-source-code (push) Successful in 14m32s
Build / build-deb (push) Failing after 5m36s
Build / build-appimage (push) Successful in 4m42s
Build / build-android (push) Failing after 3m5s
Build / build-windows (push) Failing after 7m18s
2025-01-14 15:33:49 -08:00
0b2b6bc459 Use CSD in ImGui backend, update QT backend, prepare for sample-based positioning, and disable multiprocess on Emscripten
Some checks failed
Build / build-gentoo (push) Successful in 1m56s
Build / download-system-deps (push) Successful in 5m25s
Build / get-source-code (push) Successful in 12m33s
Build / build-deb (push) Failing after 5m56s
Build / build-appimage (push) Successful in 4m47s
Build / build-android (push) Failing after 3m11s
Build / build-windows (push) Has been cancelled
2025-01-14 15:01:53 -08:00
6920d3c0ff Use internal log function for Fluidsynth
Some checks failed
Build / download-system-deps (push) Successful in 1m15s
Build / build-gentoo (push) Successful in 1m34s
Build / get-source-code (push) Successful in 4m5s
Build / build-deb (push) Failing after 2m37s
Build / build-appimage (push) Successful in 1m26s
Build / build-android (push) Failing after 16s
Build / build-windows (push) Failing after 4m11s
2024-12-29 09:19:49 -08:00
3fd1a3ca42 Default to Wayland if possible in ImGui backend
Some checks failed
Build / download-system-deps (push) Successful in 1m25s
Build / build-gentoo (push) Successful in 2m45s
Build / get-source-code (push) Successful in 4m24s
Build / build-deb (push) Failing after 2m39s
Build / build-appimage (push) Successful in 1m22s
Build / build-android (push) Failing after 16s
Build / build-windows (push) Failing after 4m16s
2024-12-29 07:48:57 -08:00
c6ec09cd14 Show video driver in ImGui backend 2024-12-29 07:48:26 -08:00
e3de8516c0 Send signals in playback engine that were meant to be sent 2024-12-29 07:46:56 -08:00
39fcf9f280 Fix fluidsynth backend 2024-12-29 07:46:34 -08:00
93ba23cd41 Add extended Prism Launcher cat pack support
Some checks failed
Build / download-system-deps (push) Successful in 41s
Build / build-gentoo (push) Successful in 1m34s
Build / get-source-code (push) Successful in 3m59s
Build / build-deb (push) Failing after 2m15s
Build / build-appimage (push) Successful in 1m5s
Build / build-android (push) Failing after 7s
Build / build-windows (push) Failing after 3m45s
2024-12-24 12:43:41 -08:00
286242a126 Fix support for sdbus-c++ 2.0
Some checks failed
Build / download-system-deps (push) Successful in 42s
Build / build-gentoo (push) Successful in 1m41s
Build / get-source-code (push) Successful in 4m4s
Build / build-deb (push) Failing after 2m9s
Build / build-appimage (push) Successful in 1m7s
Build / build-android (push) Failing after 7s
Build / build-windows (push) Failing after 3m50s
2024-12-24 11:06:37 -08:00
791c5103ea Finish cat support
Some checks failed
Build / download-system-deps (push) Successful in 42s
Build / build-gentoo (push) Failing after 1m5s
Build / get-source-code (push) Successful in 3m50s
Build / build-deb (push) Successful in 5m27s
Build / build-appimage (push) Successful in 1m9s
Build / build-android (push) Failing after 4s
Build / build-windows (push) Failing after 3m45s
2024-12-23 14:06:11 -08:00
f2bf642db2 Use custom image view widget in Haiku frontend
Some checks failed
Build / download-system-deps (push) Failing after 0s
Build / get-source-code (push) Has been skipped
Build / build-deb (push) Has been skipped
Build / build-appimage (push) Has been skipped
Build / build-android (push) Has been skipped
Build / build-windows (push) Has been skipped
Build / build-gentoo (push) Failing after 1m6s
2024-12-22 16:47:27 -08:00
b0bfa7945f Fix crash and Haiku preference window bugs
Some checks failed
Build / build-gentoo (push) Failing after 1m7s
Build / download-system-deps (push) Successful in 5m25s
Build / get-source-code (push) Successful in 11m27s
Build / build-deb (push) Successful in 11m17s
Build / build-appimage (push) Successful in 4m46s
Build / build-android (push) Failing after 3m22s
Build / build-windows (push) Failing after 9m41s
2024-12-22 14:12:23 -08:00
8e242d345b Fix some more compiler errors
Some checks failed
Build / build-deb (push) Blocked by required conditions
Build / build-appimage (push) Blocked by required conditions
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / build-gentoo (push) Failing after 1m8s
Build / download-system-deps (push) Successful in 4m30s
Build / get-source-code (push) Has been cancelled
2024-12-22 13:31:43 -08:00
a2c297592d Fix usage of nonexistant function
Some checks failed
Build / build-deb (push) Blocked by required conditions
Build / build-appimage (push) Blocked by required conditions
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / build-gentoo (push) Failing after 1m6s
Build / download-system-deps (push) Successful in 4m13s
Build / get-source-code (push) Has been cancelled
2024-12-22 13:03:35 -08:00
19e1c862a1 Fix another typo
Some checks failed
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / get-source-code (push) Blocked by required conditions
Build / build-deb (push) Blocked by required conditions
Build / build-appimage (push) Blocked by required conditions
Build / build-gentoo (push) Failing after 1m1s
Build / download-system-deps (push) Has been cancelled
2024-12-22 13:01:50 -08:00
b13870e8e5 Fix typo in prefs.cpp
Some checks failed
Build / get-source-code (push) Blocked by required conditions
Build / build-deb (push) Blocked by required conditions
Build / build-appimage (push) Blocked by required conditions
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / download-system-deps (push) Has been cancelled
Build / build-gentoo (push) Has been cancelled
2024-12-22 13:00:46 -08:00
9316f82023 Fix missing colon in haiku UI frontend
Some checks failed
Build / get-source-code (push) Blocked by required conditions
Build / build-deb (push) Blocked by required conditions
Build / build-appimage (push) Blocked by required conditions
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / build-gentoo (push) Failing after 1m10s
Build / download-system-deps (push) Has been cancelled
2024-12-22 12:59:22 -08:00
9899794b81 Add cat support
Some checks failed
Build / build-gentoo (push) Failing after 1m11s
Build / download-system-deps (push) Successful in 3m44s
Build / get-source-code (push) Successful in 13m23s
Build / build-deb (push) Successful in 11m40s
Build / build-appimage (push) Successful in 4m53s
Build / build-android (push) Failing after 3m19s
Build / build-windows (push) Failing after 8m9s
2024-12-21 14:23:00 -08:00
d6f440c8d4 Update Cat OC 2024-12-21 14:18:41 -08:00
775505120b Set explicit X11 class name in ImGui backend, and only use one WMClass in .desktop file
Some checks failed
Build / build-gentoo (push) Failing after 1m3s
Build / download-system-deps (push) Successful in 3m28s
Build / get-source-code (push) Successful in 10m15s
Build / build-deb (push) Failing after 6m7s
Build / build-appimage (push) Successful in 4m32s
Build / build-android (push) Failing after 3m2s
Build / build-windows (push) Failing after 7m25s
2024-12-19 14:00:34 -08:00
2c2892e0db Add new Debian release
Some checks failed
Build / build-gentoo (push) Failing after 1m9s
Build / download-system-deps (push) Successful in 3m44s
Build / get-source-code (push) Successful in 10m23s
Build / build-deb (push) Successful in 11m10s
Build / build-appimage (push) Successful in 4m39s
Build / build-android (push) Failing after 3m3s
Build / build-windows (push) Failing after 7m30s
2024-12-19 11:38:41 -08:00
cb0b4cf1e7 Hopefully fix DEB workflow
Some checks failed
Build / build-gentoo (push) Failing after 1m12s
Build / download-system-deps (push) Successful in 3m30s
Build / get-source-code (push) Successful in 11m26s
Build / build-deb (push) Successful in 10m38s
Build / build-appimage (push) Successful in 4m30s
Build / build-android (push) Failing after 3m1s
Build / build-windows (push) Failing after 7m18s
2024-12-19 09:00:34 -08:00
ec8984ff50 Change icon
Some checks failed
Build / build-deb (push) Blocked by required conditions
Build / build-appimage (push) Blocked by required conditions
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / build-gentoo (push) Failing after 1m6s
Build / download-system-deps (push) Successful in 3m39s
Build / get-source-code (push) Has been cancelled
2024-12-18 13:53:42 -08:00
9a1fc78248 Hopefully make CI work better
Some checks failed
Build / build-gentoo (push) Failing after 1m7s
Build / download-system-deps (push) Successful in 3m31s
Build / get-source-code (push) Successful in 10m29s
Build / build-deb (push) Failing after 5m15s
Build / build-appimage (push) Successful in 4m41s
Build / build-android (push) Failing after 3m3s
Build / build-windows (push) Failing after 7m30s
2024-12-18 13:09:07 -08:00
5475841a3f Hopefully fix deb package build
Some checks failed
Build / build-gentoo (push) Failing after 1m12s
Build / download-system-deps (push) Successful in 3m41s
Build / get-source-code (push) Successful in 11m55s
Build / build-deb (push) Failing after 5m9s
Build / build-appimage (push) Successful in 4m13s
Build / build-android (push) Failing after 3m2s
Build / build-windows (push) Failing after 7m38s
2024-12-18 11:29:42 -08:00
bffc0c4c48 Upload Debian package to repo
Some checks failed
Build / build-gentoo (push) Failing after 1m16s
Build / download-system-deps (push) Successful in 4m14s
Build / get-source-code (push) Successful in 11m35s
Build / build-appimage (push) Successful in 4m11s
Build / build-android (push) Failing after 3m18s
Build / build-windows (push) Failing after 7m34s
Build / build-deb (push) Failing after 4m36s
2024-12-18 09:55:28 -08:00
b4ce52a5cf Add missing dependency
Some checks failed
Build / download-system-deps (push) Has been cancelled
Build / get-source-code (push) Has been cancelled
Build / build-deb (push) Has been cancelled
Build / build-appimage (push) Has been cancelled
Build / build-gentoo (push) Has been cancelled
Build / build-android (push) Has been cancelled
Build / build-windows (push) Has been cancelled
2024-12-18 09:43:10 -08:00
1b1f669ac5 Fix missing function prototype
Some checks failed
Build / build-gentoo (push) Failing after 1m10s
Build / download-system-deps (push) Successful in 3m59s
Build / get-source-code (push) Successful in 9m45s
Build / build-deb (push) Failing after 4m54s
Build / build-appimage (push) Successful in 3m58s
Build / build-windows (push) Has been cancelled
Build / build-android (push) Has been cancelled
2024-12-18 09:18:00 -08:00
942987bbcb Fix workflow
Some checks failed
Build / build-gentoo (push) Failing after 1m9s
Build / download-system-deps (push) Successful in 3m41s
Build / get-source-code (push) Successful in 10m47s
Build / build-deb (push) Failing after 5m18s
Build / build-android (push) Has been cancelled
Build / build-windows (push) Has been cancelled
Build / build-appimage (push) Has been cancelled
2024-12-18 08:53:51 -08:00
1e7f1e6cf1 Remove ignored files 2024-12-18 08:41:42 -08:00
09699c140a Fix DEB package workflow 2024-12-17 18:21:46 -08:00
efbe0e8717 Don't endlessly set position, volume, speed, or tempo in QT UI 2024-12-17 18:19:04 -08:00
6fe53addb3 Add debian package build workflow 2024-12-17 16:46:20 -08:00
9e4704d2ad Update .gitignore and remove generated assets
Some checks failed
Build / build-gentoo (push) Failing after 1m5s
Build / download-system-deps (push) Successful in 3m22s
Build / get-source-code (push) Successful in 12m21s
Build / build-appimage (push) Successful in 3m54s
Build / build-android (push) Failing after 2m58s
Build / build-windows (push) Failing after 7m19s
2024-12-17 16:35:16 -08:00
6240edefce Update CMakePresets.json to work on Debian 12 2024-12-17 16:34:05 -08:00
88926489e4 Add Debian packaging
Some checks failed
Build / get-source-code (push) Blocked by required conditions
Build / build-appimage (push) Blocked by required conditions
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / build-gentoo (push) Failing after 1m7s
Build / download-system-deps (push) Has been cancelled
2024-12-17 16:33:30 -08:00
cab2a8c71b Use shared vgmstream on desktop
Some checks failed
Build / build-gentoo (push) Failing after 1m10s
Build / download-system-deps (push) Successful in 3m36s
Build / get-source-code (push) Successful in 11m47s
Build / build-appimage (push) Successful in 3m54s
Build / build-android (push) Failing after 3m0s
Build / build-windows (push) Failing after 7m14s
2024-12-17 11:08:19 -08:00
9e60b7cac7 Add basic CPack Debian package generation support.
Some checks failed
Build / get-source-code (push) Blocked by required conditions
Build / build-appimage (push) Blocked by required conditions
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / build-gentoo (push) Failing after 1m2s
Build / download-system-deps (push) Has been cancelled
2024-12-17 11:06:54 -08:00
cf4643e1b3 Support old sdbus-c++, for Debian and other stable distros 2024-12-17 11:06:29 -08:00
5244a7dd83 Set default gain for Fluidsynth to a value more in-line with most audio files
Some checks failed
Build / build-gentoo (push) Failing after 1m24s
Build / download-system-deps (push) Successful in 4m5s
Build / get-source-code (push) Successful in 11m39s
Build / build-appimage (push) Successful in 4m54s
Build / build-android (push) Failing after 3m24s
Build / build-windows (push) Failing after 8m6s
2024-12-12 10:43:42 -08:00
e8af95be9b Use pkgconfig version of fludsynth
Some checks failed
Build / build-gentoo (push) Failing after 1m24s
Build / download-system-deps (push) Successful in 3m43s
Build / get-source-code (push) Successful in 13m21s
Build / build-appimage (push) Successful in 4m2s
Build / build-android (push) Failing after 3m10s
Build / build-windows (push) Failing after 7m28s
2024-12-11 16:33:27 -08:00
b886e6a585 Add Fluidsynth backend and stop using SDL Mixer X for that.
Some checks failed
Build / build-gentoo (push) Failing after 2m4s
Build / download-system-deps (push) Successful in 4m1s
Build / get-source-code (push) Successful in 12m47s
Build / build-appimage (push) Successful in 4m11s
Build / build-android (push) Failing after 3m1s
Build / build-windows (push) Failing after 7m25s
2024-12-11 12:56:17 -08:00
ee94b97d19 Migrate to sdbus-c++ v2
Some checks failed
Build / build-gentoo (push) Failing after 31s
Build / download-system-deps (push) Successful in 3m5s
Build / get-source-code (push) Successful in 13m4s
Build / build-appimage (push) Successful in 5m0s
Build / build-android (push) Failing after 3m46s
Build / build-windows (push) Failing after 8m42s
2024-12-11 10:03:32 -08:00
fc6753f575 Haiku frontend updates
Some checks failed
Build / build-gentoo (push) Failing after 30s
Build / download-system-deps (push) Successful in 3m4s
Build / get-source-code (push) Successful in 9m44s
Build / build-appimage (push) Successful in 3m49s
Build / build-android (push) Failing after 2m49s
Build / build-windows (push) Failing after 7m18s
2024-12-09 15:28:12 -08:00
255fbe4c88 Use proper priority for playback process on Haiku 2024-12-09 15:27:50 -08:00
5f67083eea Don't automoc the entire project - Only the QT UI 2024-12-09 15:26:49 -08:00
038fefd131 Use shared libraries for liblooper, and playback backends/UI frontends. 2024-12-09 15:26:26 -08:00
58804677f2 Fix missing patches
Some checks failed
Build / build-gentoo (push) Failing after 31s
Build / download-system-deps (push) Successful in 3m9s
Build / get-source-code (push) Successful in 10m33s
Build / build-appimage (push) Successful in 4m21s
Build / build-android (push) Failing after 3m9s
Build / build-windows (push) Failing after 7m23s
2024-12-09 11:39:57 -08:00
3b37d27983 Fix Haiku libxspf build and change name formatting of libxspf patches to reflect there being multiple
Some checks failed
Build / download-system-deps (push) Successful in 3m30s
Build / build-gentoo (push) Failing after 41s
Build / get-source-code (push) Successful in 12m31s
Build / build-appimage (push) Successful in 4m21s
Build / build-android (push) Failing after 5m0s
Build / build-windows (push) Failing after 9m37s
2024-12-08 11:16:11 -08:00
867fa563f0 Fix internal.proto
Some checks failed
Build / download-system-deps (push) Successful in 3m25s
Build / build-android (push) Blocked by required conditions
Build / build-windows (push) Blocked by required conditions
Build / get-source-code (push) Blocked by required conditions
Build / build-appimage (push) Blocked by required conditions
Build / build-gentoo (push) Failing after 25s
2024-12-08 11:06:56 -08:00
90 changed files with 4620 additions and 2318 deletions

View file

@ -39,6 +39,32 @@ jobs:
overwrite: true
include-hidden-files: true
if-no-files-found: error
build-deb:
runs-on: ubuntu-latest
needs: get-source-code
steps:
- name: Download and cache system dependencies
uses: https://complecwaft.com/catmeow/cache-apt-pkgs-action@latest
with:
packages: debhelper-compat devscripts cmake git python3 libprotobuf-dev protobuf-compiler libsoundtouch-dev libsdl2-dev libsdl2-image-dev qt6-base-dev liburiparser-dev libexpat1-dev libsdbus-c++-dev libsdbus-c++-bin build-essential zstd wget git-buildpackage libfmt-dev libogg-dev libvorbis-dev libmpg123-dev libportal-dev libjsoncpp-dev libfluidsynth-dev
version: 1.0
- name: Download repository code
uses: actions/download-artifact@v3
with:
name: source_archive
path: .
- name: Extract repository code
run: mkdir -p looper/subprojects/vgmstream/dependencies/{vorbis,ogg} && tar -xf source.tar.zst -C looper
- name: Build package
run: cd looper && gbp buildpackage --git-ignore-new
- name: Upload package artifacts
uses: actions/upload-artifact@v3
with:
name: debian-packages
path: ./looper*.deb
- name: Push package to repository
if: ${{vars.user != null && secrets.token != null}}
run: for i in looper*.deb; do curl --user ${{vars.user}}:${{secrets.token}} --upload-file "$i" https://complecwaft.com/api/packages/${{vars.user}}/debian/pool/bookworm/main/upload; done
build-appimage:
runs-on: ubuntu-latest
needs: get-source-code

8
.gitignore vendored
View file

@ -1,4 +1,5 @@
assets/*.h
assets/*.hpp
build*
!build-env
.vscode/settings.json
@ -25,3 +26,10 @@ local.properties
hs_err_*.log
replay_pid*.log
assets/btcc
.debhelper
/debian/looper
obj-*/
debian/files
debian/*.log
debian/debhelper-build-stamp
debian/*.substvars

21
02-xspf-haiku.patch Normal file
View file

@ -0,0 +1,21 @@
--- b/build-aux/config.guess 2009-03-06 16:08:51.041943040 -0800
+++ a/build-aux/config.guess 2024-12-08 11:12:59.774111232 -0800
@@ -121,7 +121,7 @@
fi ;
done ;
if test x"$CC_FOR_BUILD" = x ; then
- CC_FOR_BUILD=no_compiler_found ;
+ CC_FOR_BUILD=no_compiler_found ;
fi
;;
,,*) CC_FOR_BUILD=$CC ;;
@@ -1353,6 +1353,9 @@
i*86:AROS:*:*)
echo ${UNAME_MACHINE}-pc-aros
exit ;;
+ *:Haiku:*:*)
+ echo ${UNAME_MACHINE}-pc-haiku
+ exit ;;
esac
#echo '(No uname command or uname output not recognized.)' 1>&2

View file

@ -17,14 +17,14 @@ set(SDL_MIXER_X_SHARED OFF CACHE BOOL "")
set(DOWNLOAD_AUDIO_CODECS_DEPENDENCY OFF CACHE BOOL "")
set(AUDIO_CODECS_BUILD_LOCAL_SDL2 OFF CACHE BOOL "" FORCE)
set(MIXERX_LGPL ON CACHE BOOL "" FORCE)
set(USE_MIDI ON CACHE BOOL "" FORCE)
set(USE_MIDI OFF CACHE BOOL "" FORCE)
set(USE_MIDI_NATIVE_ALT OFF CACHE BOOL "" FORCE)
set(USE_MIDI_NATIVE OFF CACHE BOOL "" FORCE)
set(USE_MIDI_TIMIDITY OFF CACHE BOOL "" FORCE)
set(USE_MIDI_FLUIDLITE OFF CACHE BOOL "" FORCE)
set(USE_MIDI_OPNMIDI OFF CACHE BOOL "" FORCE)
set(USE_MIDI_ADLMIDI OFF CACHE BOOL "" FORCE)
set(USE_MIDI_FLUIDSYNTH ON CACHE BOOL "" FORCE)
set(USE_MIDI_FLUIDSYNTH OFF CACHE BOOL "" FORCE)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(BUILD_FB2K OFF CACHE BOOL "" FORCE)
@ -43,6 +43,10 @@ if (UNIX AND NOT APPLE AND NOT ANDROID AND NOT HAIKU)
option(ENABLE_DBUS "Enables DBus support" ON)
endif()
option(BUILD_LIBFMT "Builds libfmt" OFF)
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
endif()
if(HAIKU)
set(USE_CELT OFF CACHE BOOL "" FORCE)
set(USE_SPEEX OFF CACHE BOOL "" FORCE)
@ -311,8 +315,8 @@ prefix_all(LIBRARY_SOURCES
base85.h
)
run_protoc(OUTDIR ${CMAKE_BINARY_DIR}/google/protobuf SOURCE google/protobuf/any.proto OUTVAR _src)
add_library(liblooper STATIC ${LIBRARY_SOURCES})
add_library(liblooper SHARED ${LIBRARY_SOURCES})
set_target_properties(liblooper PROPERTIES PREFIX "")
if(FOR_WASMER)
target_compile_definitions(liblooper PUBLIC FOR_WASMER)
endif()
@ -343,6 +347,7 @@ if(BUILD_JSONCPP)
set(JSONCPP_TARGET jsoncpp_static)
endif()
if(BUILD_SOUNDTOUCH)
message("Building SoundTouch.")
set(SOUNDSTRETCH OFF CACHE BOOL "" FORCE)
set(INTEGER_SAMPLES OFF CACHE BOOL "")
add_subdirectory(subprojects/soundtouch)
@ -362,12 +367,12 @@ if (DEFINED ANDROID_NDK)
target_link_libraries(liblooper PUBLIC oboe)
endif()
pkg_check_modules(libxspf IMPORTED_TARGET libxspf)
include(ExternalProject)
if (NOT libxspf_FOUND)
include(ExternalProject)
set(XSPF ${CMAKE_BINARY_DIR}/libxspf-prefix/src/libxspf-build/.libs/${CMAKE_STATIC_LIBRARY_PREFIX}xspf${CMAKE_STATIC_LIBRARY_SUFFIX})
ExternalProject_Add(libxspf
URL https://ftp.osuosl.org/pub/xiph/releases/xspf/libxspf-1.2.0.tar.lzma
PATCH_COMMAND cd <SOURCE_DIR> && patch -Np1 -i ${CMAKE_SOURCE_DIR}/xspf-no-examples.patch
PATCH_COMMAND cd <SOURCE_DIR> && patch -Np1 -i ${CMAKE_SOURCE_DIR}/01-xspf-no-examples.patch && patch -Np1 -i ${CMAKE_SOURCE_DIR}/02-xspf-haiku.patch
CONFIGURE_COMMAND <SOURCE_DIR>/configure --disable-test --enable-static --disable-shared
BUILD_COMMAND make
INSTALL_COMMAND make install DESTDIR=<BINARY_DIR>
@ -402,7 +407,7 @@ else()
target_include_directories(SDL2::SDL2 INTERFACE /boot/system/develop/headers/SDL2 ${sdl2_INCLUDE_DIRS})
target_link_libraries(SDL2::SDL2 INTERFACE ${sdl2_LIBRARIES})
else()
find_package(SDL2 REQUIRED)
find_package(SDL2 REQUIRED)
endif()
endif()
if (NOT BUILD_SDL_IMAGE)
@ -413,28 +418,34 @@ else()
target_link_libraries(SDL2_image::SDL2_image INTERFACE ${sdl2_image_LIBRARIES})
endif()
endif()
if (WINDOWS)
set(ENABLE_DBUS OFF CACHE BOOL "" FORCE)
endif()
if (ENABLE_DBUS)
find_package(sdbus-c++)
if(NOT ${sdbus-c++_FOUND})
set(ENABLE_DBUS OFF)
message("Warning: Dbus support not found - Not enabling DBus")
find_package(sdbus-c++ 2.0)
if(${sdbus-c++_FOUND})
set(OLD_SDBUS OFF)
else()
find_package(sdbus-c++)
if (${sdbus-c++_FOUND})
set(OLD_SDBUS ON)
else()
set(OLD_SDBUS ON)
set(ENABLE_DBUS OFF)
message("Warning: Dbus support not found - Not enabling DBus. This program requires version 2.0 or later.")
endif()
endif()
endif()
set(SDL2_TARGET SDL2::SDL2)
if (TARGET SDL2-static)
set(SDL2_TARGET SDL2-static)
endif()
target_link_libraries(liblooper PUBLIC ${SDL2_TARGET} ${SDL_MIXER_X_TARGET} ${SOUNDTOUCH_TARGET} libvgmstream ${JSONCPP_TARGET})
target_link_libraries(liblooper PUBLIC ${SDL2_TARGET} ${SDL_MIXER_X_TARGET} ${SOUNDTOUCH_TARGET} libvgmstream_shared ${JSONCPP_TARGET})
endif()
if(BUILD_PROTOBUF)
add_subdirectory(subprojects/protobuf)
else()
if (CMAKE_SYSTEM_NAME STREQUAL "Haiku")
find_package(Protobuf REQUIRED)
else()
find_package(protobuf REQUIRED)
find_package(absl CONFIG REQUIRED)
endif()
endif()
if (${ENABLE_DBUS})
@ -444,7 +455,8 @@ endif()
macro(add_ui_backend)
set(ARGS ${ARGV})
list(POP_FRONT ARGS target)
add_library(${target} STATIC ${ARGS})
add_library(${target} SHARED ${ARGS})
set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_NAME looper_frontend_${target})
message("Enabling UI backend '" ${target} "'...")
list(APPEND UI_BACKENDS ${target})
set(UI_BACKENDS ${UI_BACKENDS} PARENT_SCOPE)
@ -479,7 +491,8 @@ endmacro()
macro(add_playback_backend)
set(ARGS ${ARGV})
list(POP_FRONT ARGS target)
add_library(${target} STATIC ${ARGS})
add_library(${target} SHARED ${ARGS})
set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_NAME looper_playback_backend_${target})
message("Enabling playback backend '" ${target} "'...")
list(APPEND PLAYBACK_BACKENDS ${target})
set(PLAYBACK_BACKENDS ${PLAYBACK_BACKENDS} PARENT_SCOPE)
@ -515,6 +528,8 @@ endif()
playback_backend_subdir(NAME "VGMSTREAM" READABLE_NAME "VgmStream" SUBDIR backends/playback/vgmstream)
playback_backend_subdir(NAME "SDL_MIXER_X" READABLE_NAME "SDL Mixer X" SUBDIR backends/playback/sdl_mixer_x)
playback_backend_subdir(NAME "ZSM" READABLE_NAME "ZSM" SUBDIR backends/playback/zsm)
playback_backend_subdir(NAME "FLUIDSYNTH" READABLE_NAME "Fluidsynth" SUBDIR backends/playback/fluidsynth)
playback_backend_subdir(NAME "GME" READABLE_NAME "Game Music Emu" SUBDIR backends/playback/gme)
execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen_ui_backend_inc.py ${CMAKE_CURRENT_BINARY_DIR} --ui ${ENABLED_UIS} --playback ${ENABLED_PLAYBACK_BACKENDS})
prefix_all(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ main.cpp daemon_backend.cpp daemon_backend.hpp proxy_backend.cpp proxy_backend.hpp)
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/backend_glue.cpp)
@ -561,12 +576,15 @@ if(DEFINED EMSCRIPTEN)
copy_to_bindir(assets/ForkAwesome/css/fork-awesome.min.css.map fork-awesome.min.css.map)
endif()
target_link_libraries(${TARGET_NAME} PUBLIC liblooper ${UI_BACKENDS} ${PLAYBACK_BACKENDS})
install(TARGETS ${TARGET_NAME} ${EXTRA_LIBS})
install(TARGETS ${TARGET_NAME} liblooper ${EXTRA_LIBS} ${UI_BACKENDS} ${PLAYBACK_BACKENDS})
if (UNIX AND NOT APPLE)
install(FILES assets/zsm-mime.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/audio RENAME x-zsound.xml)
endif()
if (${BUILD_SDL2})
install(EXPORT SDL2-static SDL2main)
endif()
if (NOT DEFINED EMSCRIPTEN)
install(FILES assets/icon.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps/)
install(FILES assets/icon.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps/ RENAME looper.svg)
install(FILES assets/com.complecwaft.Looper.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES assets/com.complecwaft.Looper.metainfo.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
install(DIRECTORY assets/translations/ TYPE LOCALE PATTERN "*" EXCLUDE PATTERN "looper.pot")
@ -596,3 +614,7 @@ if(TESTS)
target_link_libraries(liblooper PUBLIC gtest_main)
target_compile_definitions(liblooper PUBLIC TESTS)
endif()
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS ON)
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Catmeow72")
include(CPack)

View file

@ -1,5 +1,5 @@
{
"version": 9,
"version": 6,
"cmakeMinimumRequired": {
"major": 3,
"minor": 23,

BIN
assets/catoc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

493
assets/catoc.svg Normal file
View file

@ -0,0 +1,493 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="535"
height="1000"
viewBox="0 0 141.55207 264.58331"
version="1.1"
id="svg1"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="catoc.svg"
inkscape:export-filename="catoc.png"
inkscape:export-xdpi="24.576"
inkscape:export-ydpi="24.576"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.64"
inkscape:cx="-82.03125"
inkscape:cy="670.3125"
inkscape:window-width="1280"
inkscape:window-height="963"
inkscape:window-x="0"
inkscape:window-y="61"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showguides="false"
showgrid="false" />
<defs
id="defs1">
<linearGradient
id="linearGradient2"
inkscape:collect="always">
<stop
style="stop-color:#3155a7;stop-opacity:1;"
offset="0"
id="stop1" />
<stop
style="stop-color:#3155a7;stop-opacity:1;"
offset="0.15000001"
id="stop4" />
<stop
style="stop-color:#6a00d8;stop-opacity:1;"
offset="0.15000001"
id="stop3" />
<stop
style="stop-color:#6a00d8;stop-opacity:1;"
offset="0.30000001"
id="stop5" />
<stop
style="stop-color:#3155a7;stop-opacity:1;"
offset="0.30000001"
id="stop6" />
<stop
style="stop-color:#3155a7;stop-opacity:1;"
offset="0.44710121"
id="stop7" />
<stop
style="stop-color:#6a00d8;stop-opacity:1;"
offset="0.44710121"
id="stop8" />
<stop
style="stop-color:#6a00d8;stop-opacity:1;"
offset="0.59767956"
id="stop9" />
<stop
style="stop-color:#3155a7;stop-opacity:1;"
offset="0.59847426"
id="stop10" />
<stop
style="stop-color:#3155a7;stop-opacity:1;"
offset="0.74785447"
id="stop11" />
<stop
style="stop-color:#6a00d8;stop-opacity:1;"
offset="0.75"
id="stop13" />
<stop
style="stop-color:#6a00d8;stop-opacity:1;"
offset="0.89999998"
id="stop12" />
<stop
style="stop-color:#3155a7;stop-opacity:1;"
offset="0.89999998"
id="stop14" />
<stop
style="stop-color:#3155a7;stop-opacity:1;"
offset="1"
id="stop2" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2"
id="linearGradient59"
x1="9.3510761"
y1="100.67726"
x2="110.34445"
y2="100.67726"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2"
id="linearGradient60"
x1="9.5552998"
y1="97.639015"
x2="103.40803"
y2="97.639015"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2"
id="linearGradient61"
x1="9.3773193"
y1="104.65613"
x2="104.10086"
y2="104.65613"
gradientUnits="userSpaceOnUse" />
<filter
inkscape:label="Opacity"
style="color-interpolation-filters:sRGB;"
id="filter23"
x="-0.0014374264"
y="-0.007162038"
width="1.0028749"
height="1.0143241">
<feColorMatrix
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 5 -1 "
result="colormatrix"
id="feColorMatrix23" />
<feComposite
in2="colormatrix"
operator="arithmetic"
k2="0.5"
result="composite"
id="feComposite23" />
</filter>
</defs>
<g
inkscape:label="Head"
inkscape:groupmode="layer"
id="layer1">
<g
id="g50"
inkscape:label="Tail">
<path
style="fill:#7f5e26;fill-opacity:1;stroke:#000000;stroke-width:2.117;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 81.31513,201.57342 c 0,0 44.65674,2.06431 52.75519,-16.5361 8.09842,-18.60041 6.16431,-44.43869 6.16431,-44.43869 0,0 2.16066,-12.67379 -6.04901,-12.76544 -8.20968,-0.0917 -6.02588,14.46574 -6.02588,14.46574 0.024,0.82478 2.30658,45.23958 -20.11661,46.5612 -21.45737,1.26469 -26.07784,4.79133 -26.44439,-1.84445 z"
id="path38"
sodipodi:nodetypes="cscscscc"
inkscape:label="Tail BG" />
<path
d="m 140.20217,141.17324 c 0,0 2.16066,-12.6738 -6.049,-12.76545 -8.20967,-0.0916 -5.90345,13.21277 -5.90345,13.21277"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.117;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="path1"
sodipodi:nodetypes="csc"
inkscape:label="Tail Tip" />
</g>
<path
id="path37-6"
style="fill:#7f5e26;fill-opacity:1;stroke:none;stroke-width:2.117;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
inkscape:label="BG"
d="M 55.3035,8.4955596 C 25.344765,8.4954496 1.0583871,32.715097 1.0585001,62.591508 c 0.05254,17.299173 8.398299,33.529924 22.4539769,43.668282 -11.643,3.58532 -16.6885119,10.11067 -18.6377609,25.95713 l -0.086159,41.94243 c 0,0 0.1588792,8.39256 7.4463319,7.75774 7.556655,-0.65827 6.525522,-9.32683 6.525522,-9.32683 l 2.617341,-44.10625 c 0.361695,14.81035 0.285403,55.63255 1.778478,73.45536 2.489066,7.93619 4.366747,14.09362 12.481949,15.10093 v 38.14935 c 0,0 2.26e-4,8.33511 8.358064,8.33511 8.357847,0 8.357347,-8.33511 8.357347,-8.33511 v -36.81917 c 1.50095,0.0507 3.002,0.0815 4.50548,0.0927 v 36.72655 c 0,0 -4.9e-4,8.33511 8.35735,8.33511 8.35783,0 8.35806,-8.33511 8.35806,-8.33511 v -37.49538 c 8.40465,-0.8609 11.03872,-10.43343 12.78036,-16.78447 1.88113,-23.6903 -1.01595,-50.65735 0.72463,-72.60112 l 4.34539,43.55489 c 0,0 0.553951,7.50293 7.272843,6.69329 6.896977,-0.8311 6.426267,-5.46934 6.426267,-5.46934 l 1.94888,-42.84854 C 106.26406,113.49311 103.10594,110.76612 87.08948,106.26414 101.1483,96.125962 109.49617,79.893145 109.5485,62.591548 109.54862,32.715137 85.26223,8.4954896 55.3035,8.4955996 Z"
sodipodi:nodetypes="cccccscccccsccccscccccscccccc" />
<g
id="g33"
inkscape:label="Eyes"
transform="matrix(1.3929758,0,0,1.3891483,-19.917224,-15.351834)"
style="stroke-width:0.718876">
<g
id="g22"
transform="matrix(1.5232552,0,0,1.5232552,-35.323913,-24.272608)"
style="stroke-width:0.471934"
inkscape:label="Right">
<circle
style="fill:#8e37e8;fill-opacity:1;stroke:#000000;stroke-width:0.471934;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="path8-3"
cy="49.415627"
cx="63.892059"
r="3.6106884"
inkscape:label="BG" />
<ellipse
style="fill:#0a0213;fill-opacity:1;stroke:none;stroke-width:0.471934;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="path21-2"
cx="63.892059"
cy="49.415627"
rx="1.5"
ry="2.3700783"
inkscape:label="Pupil" />
</g>
<g
id="g21"
transform="matrix(1.5232552,0,0,1.5232552,-21.191835,-24.069739)"
style="stroke-width:0.471934"
inkscape:label="Left">
<circle
style="fill:#8e37e8;fill-opacity:1;stroke:#000000;stroke-width:0.471934;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="path8"
cy="49.282444"
cx="44.110687"
r="3.6106884"
inkscape:label="BG" />
<ellipse
style="fill:#0a0213;fill-opacity:1;stroke:none;stroke-width:0.471934;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="path21"
cx="44.133587"
cy="49.14122"
rx="1.5"
ry="2.3700783"
inkscape:label="Pupil" />
</g>
</g>
<g
id="g29"
inkscape:label="Mouth"
transform="matrix(1.8992416,0,0,1.9433789,-46.72397,-43.132852)"
style="stroke-width:0.520513">
<path
id="path7"
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.520513;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
inkscape:label="Mouth"
d="m 44.91884,65.772675 c 0,0 0.315319,2.172806 4.400627,2.172806 4.274019,0 4.608539,-2.121784 4.608539,-2.121784 0,0 0.465709,2.146332 4.192716,2.146332 4.017862,0 4.400628,-2.147625 4.400628,-2.147625"
sodipodi:nodetypes="cscsc" />
<g
id="g28"
inkscape:label="Right Whiskers"
style="stroke-width:0.520513">
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.520513;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="M 40.952837,63.163701 32.127438,60.798942"
id="path27" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.520513;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="M 41.153873,64.908123 H 30.687658"
id="path25" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.520513;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 41.038936,66.695674 -8.423664,2.257115"
id="path26" />
</g>
<g
id="g27"
inkscape:label="Left Whiskers"
style="stroke-width:0.520513">
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.520513;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="M 66.286649,64.975847 H 76.752864"
id="path25-9" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.520513;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 66.401586,66.763398 8.423664,2.257115"
id="path26-3" />
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.520513;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 66.487685,63.231425 8.825399,-2.364759"
id="path27-1" />
</g>
</g>
<g
id="g36"
inkscape:label="Nose"
transform="matrix(1.3929758,0,0,1.3891483,-0.41556395,-0.413298)"
style="stroke-width:0.718876">
<path
style="fill:#660d0d;fill-opacity:1;stroke:#000000;stroke-width:0.718876;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="M 40.325309,60.936139 35,55 h 10 z"
id="path34"
sodipodi:nodetypes="cccc"
inkscape:label="BG" />
<g
id="g37"
inkscape:label="Nose Holes"
style="stroke-width:0.718876">
<circle
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.718876;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="path35"
cx="38.5"
cy="56.499996"
r="0.5" />
<circle
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.718876;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="path35-6"
cx="41.5"
cy="56.5"
r="0.5"
inkscape:label="path35-6" />
</g>
</g>
<g
id="g49"
inkscape:label="Paw Pads"
transform="matrix(1,0,0,0.82665321,0,29.198221)"
style="stroke-width:1.09986">
<g
id="g39"
transform="matrix(1.3929758,0,0,1.3891483,-70.497406,-13.791684)"
style="stroke-width:0.790665">
<path
id="path39"
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.197666;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 56.884125,138.58073 c 0,0.69984 -0.26898,1.11549 -0.600782,1.11549 -0.331801,0 -0.600781,-0.42038 -0.600781,-1.11549 0,-0.67707 0.268978,-1.1155 0.600781,-1.1155 0.331804,0 0.600782,0.41882 0.600782,1.1155 z"
sodipodi:nodetypes="sssss" />
<path
id="path39-2"
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.197666;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 59.997876,139.85128 c 0,0.69984 -0.26898,1.11549 -0.600782,1.11549 -0.331801,0 -0.600781,-0.42038 -0.600781,-1.11549 0,-0.67707 0.268978,-1.1155 0.600781,-1.1155 0.331804,0 0.600782,0.41882 0.600782,1.1155 z"
sodipodi:nodetypes="sssss" />
<path
id="path39-2-8"
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.197666;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 58.325884,139.93293 c 0,0.69984 -0.26898,1.11549 -0.600782,1.11549 -0.331801,0 -0.600781,-0.42038 -0.600781,-1.11549 0,-0.67707 0.268978,-1.1155 0.600781,-1.1155 0.331804,0 0.600782,0.41882 0.600782,1.1155 z"
sodipodi:nodetypes="sssss" />
<path
id="path39-4"
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.197666;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 61.592165,138.07349 c 0,0.69984 -0.26898,1.11549 -0.600782,1.11549 -0.331801,0 -0.600781,-0.42038 -0.600781,-1.11549 0,-0.67707 0.268978,-1.1155 0.600781,-1.1155 0.331804,0 0.600782,0.41882 0.600782,1.1155 z"
sodipodi:nodetypes="sssss" />
<path
id="path39-7"
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.197666;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 60.961403,135.10548 c 0,0.79141 -0.304435,1.41111 -0.752213,1.51736 -0.493691,0.11707 -0.626988,0.36745 -0.847147,0.64265 -0.182081,0.22762 -0.07256,1.15351 -0.887792,1.15351 -0.827635,0 -0.65112,-0.91251 -0.931846,-1.16492 -0.281516,-0.25307 -0.574749,-0.51043 -0.929047,-0.56656 -0.504565,-0.0799 -0.692101,-0.70847 -0.692101,-1.49454 0,-1.53131 1.138003,-1.81964 2.62222,-1.82979 1.372821,-0.01 2.417926,0.16663 2.417926,1.74229 z"
sodipodi:nodetypes="sssssssss" />
</g>
<g
id="g39-4"
transform="matrix(1.3929758,0,0,1.3891483,17.273715,-16.822661)"
style="stroke-width:0.790665">
<path
id="path39-9"
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.197666;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 56.884125,138.58073 c 0,0.69984 -0.26898,1.11549 -0.600782,1.11549 -0.331801,0 -0.600781,-0.42038 -0.600781,-1.11549 0,-0.67707 0.268978,-1.1155 0.600781,-1.1155 0.331804,0 0.600782,0.41882 0.600782,1.1155 z"
sodipodi:nodetypes="sssss" />
<path
id="path39-2-2"
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.197666;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 59.997876,139.85128 c 0,0.69984 -0.26898,1.11549 -0.600782,1.11549 -0.331801,0 -0.600781,-0.42038 -0.600781,-1.11549 0,-0.67707 0.268978,-1.1155 0.600781,-1.1155 0.331804,0 0.600782,0.41882 0.600782,1.1155 z"
sodipodi:nodetypes="sssss" />
<path
id="path39-2-8-0"
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.197666;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 58.325884,139.93293 c 0,0.69984 -0.26898,1.11549 -0.600782,1.11549 -0.331801,0 -0.600781,-0.42038 -0.600781,-1.11549 0,-0.67707 0.268978,-1.1155 0.600781,-1.1155 0.331804,0 0.600782,0.41882 0.600782,1.1155 z"
sodipodi:nodetypes="sssss" />
<path
id="path39-4-6"
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.197666;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 61.592165,138.07349 c 0,0.69984 -0.26898,1.11549 -0.600782,1.11549 -0.331801,0 -0.600781,-0.42038 -0.600781,-1.11549 0,-0.67707 0.268978,-1.1155 0.600781,-1.1155 0.331804,0 0.600782,0.41882 0.600782,1.1155 z"
sodipodi:nodetypes="sssss" />
<path
id="path39-7-8"
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.197666;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 60.961403,135.10548 c 0,0.79141 -0.304435,1.41111 -0.752213,1.51736 -0.493691,0.11707 -0.626988,0.36745 -0.847147,0.64265 -0.182081,0.22762 -0.07256,1.15351 -0.887792,1.15351 -0.827635,0 -0.65112,-0.91251 -0.931846,-1.16492 -0.281516,-0.25307 -0.574749,-0.51043 -0.929047,-0.56656 -0.504565,-0.0799 -0.692101,-0.70847 -0.692101,-1.49454 0,-1.53131 1.138003,-1.81964 2.62222,-1.82979 1.372821,-0.01 2.417926,0.16663 2.417926,1.74229 z"
sodipodi:nodetypes="sssssssss" />
</g>
</g>
<g
id="g48"
inkscape:label="Claw Sheaths">
<g
id="g47">
<path
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0.608953;paint-order:normal"
d="m 39.460601,261.60263 v -8.94561"
id="path42" />
<path
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0.608953;paint-order:normal"
d="m 43.986037,262.7061 v -8.94562"
id="path42-6" />
<path
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0.608953;paint-order:normal"
d="m 48.408425,261.60326 v -8.94562"
id="path42-0" />
</g>
<g
id="g46">
<path
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0.608953;paint-order:normal"
d="m 61.13422,262.35416 v -8.94562"
id="path42-1" />
<path
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0.608953;paint-order:normal"
d="m 65.65967,263.45726 v -8.94562"
id="path42-6-7" />
<path
style="fill:#b862d3;fill-opacity:1;stroke:#000000;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:0.608953;paint-order:normal"
d="m 70.08205,262.35441 v -8.94562"
id="path42-0-2" />
</g>
</g>
<path
id="path37-6-6"
style="fill:#7240c6;fill-opacity:1;stroke:none;stroke-width:2.117;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
inkscape:label="Shirt"
d="M 23.641128,106.48962 C 11.998135,110.07494 6.9522611,116.60036 5.0030151,132.44679 l -0.55499,36.96847 14.6228339,0.16009 2.435214,-40.86143 c 0.3617,14.81033 0.285657,55.63245 1.778732,73.45526 2.489067,7.93616 4.366826,14.09364 12.482025,15.10094 12.126669,1.82005 25.25376,0.78509 37.9363,0.65402 8.21893,-0.84186 10.89922,-9.98211 12.64327,-16.32634 2.06804,-13.8909 -0.88582,-51.02841 0.86165,-73.05898 l 3.98361,39.92461 15.20373,-1.52832 0.80622,-36.46596 C 106.98142,112.64201 97.75062,108.88688 86.4099,106.3796 67.32316,105.7358 52.88194,103.37042 23.641128,106.48962 Z"
sodipodi:nodetypes="ccccccccccccccc" />
<path
id="path37-6-8"
style="fill:#3155a7;fill-opacity:1;stroke:none;stroke-width:2.117;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
inkscape:label="Pants"
d="m 23.04314,200.177 c 0.04037,0.54899 0.07011,1.25017 0.113013,1.76233 2.489065,7.93619 4.366836,14.09367 12.48203,15.10097 v 35.41071 H 52.3536 v -34.08052 c 1.50094,0.0507 3.00199,0.0815 4.50547,0.0926 v 33.98787 h 16.71541 v -34.75674 c 8.40465,-0.8609 11.03914,-10.4333 12.78076,-16.78435 0.0191,-0.24057 0.0222,-0.49167 0.0403,-0.73292 z" />
<path
style="fill:#3155a7;fill-opacity:1;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 23.173635,200.20717 H 86.646638"
id="path14"
inkscape:label="Pants Shirt Separator" />
<g
id="g45"
inkscape:label="Shirt Bands">
<path
id="path37-6-9"
style="fill:#4487fd;fill-opacity:1;stroke:none;stroke-width:2.117;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
inkscape:label="path37-6"
d="m 4.7188891,151.38605 -0.168443,11.22377 H 19.343881 l 0.668734,-11.22377 z m 17.0969249,0 c 0.04085,3.69823 0.07986,7.30165 0.135327,11.22377 H 86.74251 c -0.0826,-3.78094 -0.1474,-7.51941 -0.19219,-11.22377 z m 67.672176,0 1.12007,11.22377 h 15.74867 l 0.24834,-11.22377 z" />
<path
id="path37-6-9-5"
style="fill:#4487fd;fill-opacity:1;stroke:none;stroke-width:2.117;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
inkscape:label="path37-6"
d="m 5.1925441,131.09782 c -0.06285,0.45053 -0.132653,0.88233 -0.190038,1.34886 l -0.148288,9.8742 H 20.552494 l 0.668731,-11.22306 z m 16.3475699,0 c 0.06093,3.09208 0.113598,6.80043 0.162684,11.22306 H 86.54385 c 0.039,-3.83184 0.12941,-7.6015 0.35128,-11.22306 z m 65.922966,0 1.11935,11.22306 h 18.22276 l 0.24835,-11.22306 z" />
</g>
<path
id="path37-6-67"
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:2.117;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
inkscape:label="Outline"
d="M 55.30367,8.4954506 C 25.34493,8.4953406 1.0585521,32.714988 1.0586651,62.591399 1.1112051,79.890572 9.4569641,96.121322 23.512642,106.25968 11.869641,109.845 6.8241301,116.37035 4.8748811,132.21681 l -0.14504,40.16752 c 0,0 -0.860414,10.08289 6.4270389,9.44807 7.556655,-0.65827 7.27456,-9.45652 7.27456,-9.45652 l 2.946477,-43.89198 c 0.361695,14.81035 0.285403,55.63256 1.778478,73.45536 2.489067,7.93619 4.366748,14.09363 12.481951,15.10093 v 38.14935 c 0,0 2.22e-4,8.33511 8.358063,8.33511 8.357851,0 8.357341,-8.33511 8.357341,-8.33511 v -36.81917 c 1.50095,0.0507 3.002,0.0815 4.50548,0.0927 v 36.72655 c 0,0 -4.9e-4,8.33511 8.35735,8.33511 8.35783,0 8.35805,-8.33511 8.35805,-8.33511 v -37.49538 c 8.40467,-0.8609 11.03874,-10.43343 12.78038,-16.78447 1.88113,-23.6903 -1.01596,-50.65735 0.72463,-72.60112 l 5.148385,41.90888 c 0,0 0.0058,9.28455 6.74456,8.66148 6.594675,-0.60975 6.254675,-7.77439 6.254675,-7.77439 l 1.84575,-40.86567 C 106.26423,113.493 103.1061,110.76602 87.08964,106.26403 101.14845,96.125852 109.49634,79.893036 109.54867,62.591439 109.54878,32.715028 85.26239,8.4953806 55.30367,8.4954906 Z"
sodipodi:nodetypes="cccccscccccsccccscccccscccccc" />
<g
id="g34"
inkscape:label="Ears"
transform="matrix(1.3929758,0,0,1.3891483,-15.90372,-14.367408)"
style="stroke-width:0.718876">
<g
id="g31"
inkscape:label="Right"
style="stroke-width:0.718876">
<path
style="fill:#7f5e26;fill-opacity:1;stroke:#000000;stroke-width:1.52187;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none"
d="M 60.974461,29.862762 71.752186,11.103415 77.322667,31.99494"
id="path3"
sodipodi:nodetypes="ccc"
inkscape:label="Outer" />
<path
style="fill:#d05b5b;fill-opacity:1;stroke:#000000;stroke-width:1.52187;stroke-dasharray:none"
d="m 66.028789,30.129287 3.664363,-6.491093 2.107466,8.0439"
id="path5"
inkscape:label="Inner" />
</g>
<g
id="g32"
inkscape:label="Left"
style="stroke-width:0.718876">
<path
style="fill:#7f5e26;fill-opacity:1;stroke:#000000;stroke-width:1.52187;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none"
d="M 24.914191,32.689715 30.664597,11.228906 40.939569,29.025678"
id="path6"
sodipodi:nodetypes="ccc"
inkscape:label="Outer" />
<path
style="fill:#d05b5b;fill-opacity:1;stroke:#000000;stroke-width:1.52187;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;paint-order:normal"
d="m 30.1894,31.368573 2.014095,-7.516708 3.38236,5.858416"
id="path4"
inkscape:label="Inner" />
</g>
</g>
<g
id="g14"
inkscape:label="Scarf"
style="stroke-width:1;stroke-dasharray:none">
<path
style="fill:url(#linearGradient60);fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 11.637857,96.798891 c 0,0 80.076142,-7.520607 87.375922,-4.190299 7.299781,3.330308 3.026241,8.003668 3.026241,8.003668 l -92.3597245,2.90803 z"
id="path51" />
<path
style="fill:url(#linearGradient61);fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 14.525243,99.534235 c 0,0 25.459413,-1.07202 41.280118,-1.04653 15.820705,0.02548 43.931474,-1.122979 43.931474,-1.122979 0,0 4.381055,3.496594 4.235455,8.063244 -0.1456,4.56665 -7.330932,4.96115 -7.330932,4.96115 L 12.2758,111.94754 c 0,0 -2.8460075,-0.55174 -2.7720644,-7.18035 0.073943,-6.628619 5.0215074,-5.232955 5.0215074,-5.232955 z"
id="path50"
sodipodi:nodetypes="cscsccsc" />
<path
id="path52"
style="fill:url(#linearGradient59);fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 112.54197,95.644808 -2.66186,1.55236 -0.001,0.01757 c -4.77517,-3.439318 -98.929315,0.344308 -99.731896,2.056722 0,0 -0.5685094,0.673104 -0.659391,2.913 -0.089194,2.19831 0.3622518,2.78019 0.3622518,2.78019 1.4417732,1.48841 92.6509052,-0.0855 99.7939052,-2.85254 l 2.8696,1.57303 z" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -6,7 +6,7 @@ Comment=An audio player that can properly loop audio files
GenericName=Looping audio player
Exec=looper -n %f
Icon=looper
StartupWMClass=looper;com.complecwaft.Looper;com.complecwaft.Looper.GTK
StartupWMClass=looper
MimeType=audio/x-wav;audio/ogg;audio/x-vorbis+ogg;audio/x-opus+ogg;audio/mpeg;audio/flac;audio/xm;audio/x-mod;audio/x-zsound;
Categories=Audio;AudioVideo;
Terminal=false

View file

@ -1,232 +0,0 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_stub_adaptor_hpp__adaptor__H__
#define __sdbuscpp__dbus_stub_adaptor_hpp__adaptor__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
class Application_adaptor
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.Application";
protected:
Application_adaptor(sdbus::IObject& object)
: object_(&object)
{
object_->registerMethod("Activate").onInterface(INTERFACE_NAME).withInputParamNames("platform_data").implementedAs([this](const std::map<std::string, sdbus::Variant>& platform_data){ return this->Activate(platform_data); });
object_->registerMethod("Open").onInterface(INTERFACE_NAME).withInputParamNames("uris", "platform_data").implementedAs([this](const std::vector<std::string>& uris, const std::map<std::string, sdbus::Variant>& platform_data){ return this->Open(uris, platform_data); });
object_->registerMethod("ActivateAction").onInterface(INTERFACE_NAME).withInputParamNames("action_name", "parameter", "platform_data").implementedAs([this](const std::string& action_name, const std::vector<sdbus::Variant>& parameter, const std::map<std::string, sdbus::Variant>& platform_data){ return this->ActivateAction(action_name, parameter, platform_data); });
}
Application_adaptor(const Application_adaptor&) = delete;
Application_adaptor& operator=(const Application_adaptor&) = delete;
Application_adaptor(Application_adaptor&&) = default;
Application_adaptor& operator=(Application_adaptor&&) = default;
~Application_adaptor() = default;
private:
virtual void Activate(const std::map<std::string, sdbus::Variant>& platform_data) = 0;
virtual void Open(const std::vector<std::string>& uris, const std::map<std::string, sdbus::Variant>& platform_data) = 0;
virtual void ActivateAction(const std::string& action_name, const std::vector<sdbus::Variant>& parameter, const std::map<std::string, sdbus::Variant>& platform_data) = 0;
private:
sdbus::IObject* object_;
};
}} // namespaces
namespace com {
namespace complecwaft {
class looper_adaptor
{
public:
static constexpr const char* INTERFACE_NAME = "com.complecwaft.looper";
protected:
looper_adaptor(sdbus::IObject& object)
: object_(&object)
{
object_->registerMethod("CreateHandle").onInterface(INTERFACE_NAME).withOutputParamNames("new_handle").implementedAs([this](){ return this->CreateHandle(); });
object_->registerMethod("ClearHandle").onInterface(INTERFACE_NAME).withInputParamNames("handle").implementedAs([this](const std::string& handle){ return this->ClearHandle(handle); });
object_->registerMethod("Start").onInterface(INTERFACE_NAME).withInputParamNames("path", "isUri").implementedAs([this](const std::string& path, const bool& isUri){ return this->Start(path, isUri); });
object_->registerMethod("StartWithStreamIndex").onInterface(INTERFACE_NAME).withInputParamNames("path", "isUri", "streamIndex").implementedAs([this](const std::string& path, const bool& isUri, const uint32_t& streamIndex){ return this->StartWithStreamIndex(path, isUri, streamIndex); });
object_->registerMethod("Load").onInterface(INTERFACE_NAME).withInputParamNames("path", "isUri").implementedAs([this](const std::string& path, const bool& isUri){ return this->Load(path, isUri); });
object_->registerMethod("Quit").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Quit(); });
object_->registerMethod("Stop").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Stop(); });
object_->registerMethod("TogglePause").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->TogglePause(); });
object_->registerMethod("GetStreams").onInterface(INTERFACE_NAME).withOutputParamNames("streams").implementedAs([this](){ return this->GetStreams(); });
object_->registerMethod("PlayStream").onInterface(INTERFACE_NAME).withInputParamNames("idx").implementedAs([this](const uint32_t& idx){ return this->PlayStream(idx); });
object_->registerSignal("PlaybackEngineStarted").onInterface(INTERFACE_NAME);
object_->registerSignal("SpeedChanged").onInterface(INTERFACE_NAME).withParameters<double>("new_speed");
object_->registerSignal("TempoChanged").onInterface(INTERFACE_NAME).withParameters<double>("new_tempo");
object_->registerSignal("PitchChanged").onInterface(INTERFACE_NAME).withParameters<double>("new_pitch");
object_->registerSignal("PauseChanged").onInterface(INTERFACE_NAME).withParameters<bool>("now_paused");
object_->registerSignal("Stopped").onInterface(INTERFACE_NAME);
object_->registerSignal("ErrorOccurred").onInterface(INTERFACE_NAME).withParameters<std::string, std::string>("error_desc", "error_type");
object_->registerSignal("Seeked").onInterface(INTERFACE_NAME).withParameters<double>("to_position");
object_->registerSignal("FileChanged").onInterface(INTERFACE_NAME).withParameters<std::string, std::string>("path", "title");
object_->registerProperty("FilePath").onInterface(INTERFACE_NAME).withGetter([this](){ return this->FilePath(); });
object_->registerProperty("FileTitle").onInterface(INTERFACE_NAME).withGetter([this](){ return this->FileTitle(); });
object_->registerProperty("Position").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Position(); }).withSetter([this](const double& value){ this->Position(value); });
object_->registerProperty("Length").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Length(); });
object_->registerProperty("Speed").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Speed(); }).withSetter([this](const double& value){ this->Speed(value); });
object_->registerProperty("Tempo").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Tempo(); }).withSetter([this](const double& value){ this->Tempo(value); });
object_->registerProperty("Pitch").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Pitch(); }).withSetter([this](const double& value){ this->Pitch(value); });
object_->registerProperty("Volume").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Volume(); }).withSetter([this](const double& value){ this->Volume(value); });
object_->registerProperty("Paused").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Paused(); }).withSetter([this](const bool& value){ this->Paused(value); });
object_->registerProperty("IsStopped").onInterface(INTERFACE_NAME).withGetter([this](){ return this->IsStopped(); });
object_->registerProperty("IsDaemon").onInterface(INTERFACE_NAME).withGetter([this](){ return this->IsDaemon(); });
object_->registerProperty("StreamIdx").onInterface(INTERFACE_NAME).withGetter([this](){ return this->StreamIdx(); });
}
looper_adaptor(const looper_adaptor&) = delete;
looper_adaptor& operator=(const looper_adaptor&) = delete;
looper_adaptor(looper_adaptor&&) = default;
looper_adaptor& operator=(looper_adaptor&&) = default;
~looper_adaptor() = default;
public:
void emitPlaybackEngineStarted()
{
object_->emitSignal("PlaybackEngineStarted").onInterface(INTERFACE_NAME);
}
void emitSpeedChanged(const double& new_speed)
{
object_->emitSignal("SpeedChanged").onInterface(INTERFACE_NAME).withArguments(new_speed);
}
void emitTempoChanged(const double& new_tempo)
{
object_->emitSignal("TempoChanged").onInterface(INTERFACE_NAME).withArguments(new_tempo);
}
void emitPitchChanged(const double& new_pitch)
{
object_->emitSignal("PitchChanged").onInterface(INTERFACE_NAME).withArguments(new_pitch);
}
void emitPauseChanged(const bool& now_paused)
{
object_->emitSignal("PauseChanged").onInterface(INTERFACE_NAME).withArguments(now_paused);
}
void emitStopped()
{
object_->emitSignal("Stopped").onInterface(INTERFACE_NAME);
}
void emitErrorOccurred(const std::string& error_desc, const std::string& error_type)
{
object_->emitSignal("ErrorOccurred").onInterface(INTERFACE_NAME).withArguments(error_desc, error_type);
}
void emitSeeked(const double& to_position)
{
object_->emitSignal("Seeked").onInterface(INTERFACE_NAME).withArguments(to_position);
}
void emitFileChanged(const std::string& path, const std::string& title)
{
object_->emitSignal("FileChanged").onInterface(INTERFACE_NAME).withArguments(path, title);
}
private:
virtual std::string CreateHandle() = 0;
virtual void ClearHandle(const std::string& handle) = 0;
virtual void Start(const std::string& path, const bool& isUri) = 0;
virtual void StartWithStreamIndex(const std::string& path, const bool& isUri, const uint32_t& streamIndex) = 0;
virtual void Load(const std::string& path, const bool& isUri) = 0;
virtual void Quit() = 0;
virtual void Stop() = 0;
virtual void TogglePause() = 0;
virtual std::vector<sdbus::Struct<double, std::string, int32_t>> GetStreams() = 0;
virtual void PlayStream(const uint32_t& idx) = 0;
private:
virtual std::string FilePath() = 0;
virtual std::string FileTitle() = 0;
virtual double Position() = 0;
virtual void Position(const double& value) = 0;
virtual double Length() = 0;
virtual double Speed() = 0;
virtual void Speed(const double& value) = 0;
virtual double Tempo() = 0;
virtual void Tempo(const double& value) = 0;
virtual double Pitch() = 0;
virtual void Pitch(const double& value) = 0;
virtual double Volume() = 0;
virtual void Volume(const double& value) = 0;
virtual bool Paused() = 0;
virtual void Paused(const bool& value) = 0;
virtual bool IsStopped() = 0;
virtual bool IsDaemon() = 0;
virtual uint32_t StreamIdx() = 0;
private:
sdbus::IObject* object_;
};
}} // namespaces
namespace com {
namespace complecwaft {
namespace looper {
class Errors_adaptor
{
public:
static constexpr const char* INTERFACE_NAME = "com.complecwaft.looper.Errors";
protected:
Errors_adaptor(sdbus::IObject& object)
: object_(&object)
{
object_->registerMethod("PopFront").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("error").implementedAs([this](const std::string& handle){ return this->PopFront(handle); });
object_->registerMethod("PopBack").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("error").implementedAs([this](const std::string& handle){ return this->PopBack(handle); });
object_->registerMethod("PeekFront").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("error").implementedAs([this](const std::string& handle){ return this->PeekFront(handle); });
object_->registerMethod("PeekBack").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("error").implementedAs([this](const std::string& handle){ return this->PeekBack(handle); });
object_->registerMethod("GetCount").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("count").implementedAs([this](const std::string& handle){ return this->GetCount(handle); });
object_->registerMethod("IsEmpty").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("empty").implementedAs([this](const std::string& handle){ return this->IsEmpty(handle); });
object_->registerMethod("Clear").onInterface(INTERFACE_NAME).withInputParamNames("handle").implementedAs([this](const std::string& handle){ return this->Clear(handle); });
object_->registerMethod("PeekAll").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("errors").implementedAs([this](const std::string& handle){ return this->PeekAll(handle); });
object_->registerMethod("GetAllAndClear").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("errors").implementedAs([this](const std::string& handle){ return this->GetAllAndClear(handle); });
}
Errors_adaptor(const Errors_adaptor&) = delete;
Errors_adaptor& operator=(const Errors_adaptor&) = delete;
Errors_adaptor(Errors_adaptor&&) = default;
Errors_adaptor& operator=(Errors_adaptor&&) = default;
~Errors_adaptor() = default;
private:
virtual std::string PopFront(const std::string& handle) = 0;
virtual std::string PopBack(const std::string& handle) = 0;
virtual std::string PeekFront(const std::string& handle) = 0;
virtual std::string PeekBack(const std::string& handle) = 0;
virtual uint32_t GetCount(const std::string& handle) = 0;
virtual bool IsEmpty(const std::string& handle) = 0;
virtual void Clear(const std::string& handle) = 0;
virtual std::vector<std::string> PeekAll(const std::string& handle) = 0;
virtual std::vector<std::string> GetAllAndClear(const std::string& handle) = 0;
private:
sdbus::IObject* object_;
};
}}} // namespaces
#endif

View file

@ -1,338 +0,0 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_stub_proxy_hpp__proxy__H__
#define __sdbuscpp__dbus_stub_proxy_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
class Application_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.Application";
protected:
Application_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
}
Application_proxy(const Application_proxy&) = delete;
Application_proxy& operator=(const Application_proxy&) = delete;
Application_proxy(Application_proxy&&) = default;
Application_proxy& operator=(Application_proxy&&) = default;
~Application_proxy() = default;
public:
void Activate(const std::map<std::string, sdbus::Variant>& platform_data)
{
proxy_->callMethod("Activate").onInterface(INTERFACE_NAME).withArguments(platform_data);
}
void Open(const std::vector<std::string>& uris, const std::map<std::string, sdbus::Variant>& platform_data)
{
proxy_->callMethod("Open").onInterface(INTERFACE_NAME).withArguments(uris, platform_data);
}
void ActivateAction(const std::string& action_name, const std::vector<sdbus::Variant>& parameter, const std::map<std::string, sdbus::Variant>& platform_data)
{
proxy_->callMethod("ActivateAction").onInterface(INTERFACE_NAME).withArguments(action_name, parameter, platform_data);
}
private:
sdbus::IProxy* proxy_;
};
}} // namespaces
namespace com {
namespace complecwaft {
class looper_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "com.complecwaft.looper";
protected:
looper_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
proxy_->uponSignal("PlaybackEngineStarted").onInterface(INTERFACE_NAME).call([this](){ this->onPlaybackEngineStarted(); });
proxy_->uponSignal("SpeedChanged").onInterface(INTERFACE_NAME).call([this](const double& new_speed){ this->onSpeedChanged(new_speed); });
proxy_->uponSignal("TempoChanged").onInterface(INTERFACE_NAME).call([this](const double& new_tempo){ this->onTempoChanged(new_tempo); });
proxy_->uponSignal("PitchChanged").onInterface(INTERFACE_NAME).call([this](const double& new_pitch){ this->onPitchChanged(new_pitch); });
proxy_->uponSignal("PauseChanged").onInterface(INTERFACE_NAME).call([this](const bool& now_paused){ this->onPauseChanged(now_paused); });
proxy_->uponSignal("Stopped").onInterface(INTERFACE_NAME).call([this](){ this->onStopped(); });
proxy_->uponSignal("ErrorOccurred").onInterface(INTERFACE_NAME).call([this](const std::string& error_desc, const std::string& error_type){ this->onErrorOccurred(error_desc, error_type); });
proxy_->uponSignal("Seeked").onInterface(INTERFACE_NAME).call([this](const double& to_position){ this->onSeeked(to_position); });
proxy_->uponSignal("FileChanged").onInterface(INTERFACE_NAME).call([this](const std::string& path, const std::string& title){ this->onFileChanged(path, title); });
}
looper_proxy(const looper_proxy&) = delete;
looper_proxy& operator=(const looper_proxy&) = delete;
looper_proxy(looper_proxy&&) = default;
looper_proxy& operator=(looper_proxy&&) = default;
~looper_proxy() = default;
virtual void onPlaybackEngineStarted() = 0;
virtual void onSpeedChanged(const double& new_speed) = 0;
virtual void onTempoChanged(const double& new_tempo) = 0;
virtual void onPitchChanged(const double& new_pitch) = 0;
virtual void onPauseChanged(const bool& now_paused) = 0;
virtual void onStopped() = 0;
virtual void onErrorOccurred(const std::string& error_desc, const std::string& error_type) = 0;
virtual void onSeeked(const double& to_position) = 0;
virtual void onFileChanged(const std::string& path, const std::string& title) = 0;
public:
std::string CreateHandle()
{
std::string result;
proxy_->callMethod("CreateHandle").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
void ClearHandle(const std::string& handle)
{
proxy_->callMethod("ClearHandle").onInterface(INTERFACE_NAME).withArguments(handle);
}
void Start(const std::string& path, const bool& isUri)
{
proxy_->callMethod("Start").onInterface(INTERFACE_NAME).withArguments(path, isUri);
}
void StartWithStreamIndex(const std::string& path, const bool& isUri, const uint32_t& streamIndex)
{
proxy_->callMethod("StartWithStreamIndex").onInterface(INTERFACE_NAME).withArguments(path, isUri, streamIndex);
}
void Load(const std::string& path, const bool& isUri)
{
proxy_->callMethod("Load").onInterface(INTERFACE_NAME).withArguments(path, isUri);
}
void Quit()
{
proxy_->callMethod("Quit").onInterface(INTERFACE_NAME);
}
void Stop()
{
proxy_->callMethod("Stop").onInterface(INTERFACE_NAME);
}
void TogglePause()
{
proxy_->callMethod("TogglePause").onInterface(INTERFACE_NAME);
}
std::vector<sdbus::Struct<double, std::string, int32_t>> GetStreams()
{
std::vector<sdbus::Struct<double, std::string, int32_t>> result;
proxy_->callMethod("GetStreams").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
void PlayStream(const uint32_t& idx)
{
proxy_->callMethod("PlayStream").onInterface(INTERFACE_NAME).withArguments(idx);
}
public:
std::string FilePath()
{
return proxy_->getProperty("FilePath").onInterface(INTERFACE_NAME);
}
std::string FileTitle()
{
return proxy_->getProperty("FileTitle").onInterface(INTERFACE_NAME);
}
double Position()
{
return proxy_->getProperty("Position").onInterface(INTERFACE_NAME);
}
void Position(const double& value)
{
proxy_->setProperty("Position").onInterface(INTERFACE_NAME).toValue(value);
}
double Length()
{
return proxy_->getProperty("Length").onInterface(INTERFACE_NAME);
}
double Speed()
{
return proxy_->getProperty("Speed").onInterface(INTERFACE_NAME);
}
void Speed(const double& value)
{
proxy_->setProperty("Speed").onInterface(INTERFACE_NAME).toValue(value);
}
double Tempo()
{
return proxy_->getProperty("Tempo").onInterface(INTERFACE_NAME);
}
void Tempo(const double& value)
{
proxy_->setProperty("Tempo").onInterface(INTERFACE_NAME).toValue(value);
}
double Pitch()
{
return proxy_->getProperty("Pitch").onInterface(INTERFACE_NAME);
}
void Pitch(const double& value)
{
proxy_->setProperty("Pitch").onInterface(INTERFACE_NAME).toValue(value);
}
double Volume()
{
return proxy_->getProperty("Volume").onInterface(INTERFACE_NAME);
}
void Volume(const double& value)
{
proxy_->setProperty("Volume").onInterface(INTERFACE_NAME).toValue(value);
}
bool Paused()
{
return proxy_->getProperty("Paused").onInterface(INTERFACE_NAME);
}
void Paused(const bool& value)
{
proxy_->setProperty("Paused").onInterface(INTERFACE_NAME).toValue(value);
}
bool IsStopped()
{
return proxy_->getProperty("IsStopped").onInterface(INTERFACE_NAME);
}
bool IsDaemon()
{
return proxy_->getProperty("IsDaemon").onInterface(INTERFACE_NAME);
}
uint32_t StreamIdx()
{
return proxy_->getProperty("StreamIdx").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy* proxy_;
};
}} // namespaces
namespace com {
namespace complecwaft {
namespace looper {
class Errors_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "com.complecwaft.looper.Errors";
protected:
Errors_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
}
Errors_proxy(const Errors_proxy&) = delete;
Errors_proxy& operator=(const Errors_proxy&) = delete;
Errors_proxy(Errors_proxy&&) = default;
Errors_proxy& operator=(Errors_proxy&&) = default;
~Errors_proxy() = default;
public:
std::string PopFront(const std::string& handle)
{
std::string result;
proxy_->callMethod("PopFront").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
std::string PopBack(const std::string& handle)
{
std::string result;
proxy_->callMethod("PopBack").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
std::string PeekFront(const std::string& handle)
{
std::string result;
proxy_->callMethod("PeekFront").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
std::string PeekBack(const std::string& handle)
{
std::string result;
proxy_->callMethod("PeekBack").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
uint32_t GetCount(const std::string& handle)
{
uint32_t result;
proxy_->callMethod("GetCount").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
bool IsEmpty(const std::string& handle)
{
bool result;
proxy_->callMethod("IsEmpty").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
void Clear(const std::string& handle)
{
proxy_->callMethod("Clear").onInterface(INTERFACE_NAME).withArguments(handle);
}
std::vector<std::string> PeekAll(const std::string& handle)
{
std::vector<std::string> result;
proxy_->callMethod("PeekAll").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
std::vector<std::string> GetAllAndClear(const std::string& handle)
{
std::vector<std::string> result;
proxy_->callMethod("GetAllAndClear").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
private:
sdbus::IProxy* proxy_;
};
}}} // namespaces
#endif

BIN
assets/icon-old.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

1049
assets/icon-old.svg Normal file

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 41 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 5 KiB

View file

@ -1,214 +0,0 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__mpris_stub_adaptor_hpp__adaptor__H__
#define __sdbuscpp__mpris_stub_adaptor_hpp__adaptor__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace mpris {
class MediaPlayer2_adaptor
{
public:
static constexpr const char* INTERFACE_NAME = "org.mpris.MediaPlayer2";
protected:
MediaPlayer2_adaptor(sdbus::IObject& object)
: object_(&object)
{
object_->registerMethod("Raise").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Raise(); });
object_->registerMethod("Quit").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Quit(); });
object_->registerProperty("CanRaise").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanRaise(); });
object_->registerProperty("CanQuit").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanQuit(); });
object_->registerProperty("HasTrackList").onInterface(INTERFACE_NAME).withGetter([this](){ return this->HasTrackList(); });
object_->registerProperty("Identity").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Identity(); });
object_->registerProperty("SupportedUriSchemes").onInterface(INTERFACE_NAME).withGetter([this](){ return this->SupportedUriSchemes(); });
object_->registerProperty("SupportedMimeTypes").onInterface(INTERFACE_NAME).withGetter([this](){ return this->SupportedMimeTypes(); });
}
MediaPlayer2_adaptor(const MediaPlayer2_adaptor&) = delete;
MediaPlayer2_adaptor& operator=(const MediaPlayer2_adaptor&) = delete;
MediaPlayer2_adaptor(MediaPlayer2_adaptor&&) = default;
MediaPlayer2_adaptor& operator=(MediaPlayer2_adaptor&&) = default;
~MediaPlayer2_adaptor() = default;
private:
virtual void Raise() = 0;
virtual void Quit() = 0;
private:
virtual bool CanRaise() = 0;
virtual bool CanQuit() = 0;
virtual bool HasTrackList() = 0;
virtual std::string Identity() = 0;
virtual std::vector<std::string> SupportedUriSchemes() = 0;
virtual std::vector<std::string> SupportedMimeTypes() = 0;
private:
sdbus::IObject* object_;
};
}} // namespaces
namespace org {
namespace mpris {
namespace MediaPlayer2 {
class Player_adaptor
{
public:
static constexpr const char* INTERFACE_NAME = "org.mpris.MediaPlayer2.Player";
protected:
Player_adaptor(sdbus::IObject& object)
: object_(&object)
{
object_->registerMethod("Next").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Next(); });
object_->registerMethod("Previous").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Previous(); });
object_->registerMethod("Pause").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Pause(); });
object_->registerMethod("PlayPause").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->PlayPause(); });
object_->registerMethod("Stop").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Stop(); });
object_->registerMethod("Play").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Play(); });
object_->registerMethod("Seek").onInterface(INTERFACE_NAME).withInputParamNames("Offset").implementedAs([this](const int64_t& Offset){ return this->Seek(Offset); });
object_->registerMethod("SetPosition").onInterface(INTERFACE_NAME).withInputParamNames("TrackId", "Position").implementedAs([this](const sdbus::ObjectPath& TrackId, const int64_t& Position){ return this->SetPosition(TrackId, Position); });
object_->registerMethod("OpenUri").onInterface(INTERFACE_NAME).withInputParamNames("Uri").implementedAs([this](const std::string& Uri){ return this->OpenUri(Uri); });
object_->registerSignal("Seeked").onInterface(INTERFACE_NAME).withParameters<int64_t>("Position");
object_->registerProperty("PlaybackStatus").onInterface(INTERFACE_NAME).withGetter([this](){ return this->PlaybackStatus(); });
object_->registerProperty("Rate").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Rate(); }).withSetter([this](const double& value){ this->Rate(value); });
object_->registerProperty("Metadata").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Metadata(); });
object_->registerProperty("Volume").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Volume(); }).withSetter([this](const double& value){ this->Volume(value); });
object_->registerProperty("Position").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Position(); });
object_->registerProperty("MinimumRate").onInterface(INTERFACE_NAME).withGetter([this](){ return this->MinimumRate(); });
object_->registerProperty("MaximumRate").onInterface(INTERFACE_NAME).withGetter([this](){ return this->MaximumRate(); });
object_->registerProperty("CanGoNext").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanGoNext(); });
object_->registerProperty("CanGoPrevious").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanGoPrevious(); });
object_->registerProperty("CanPlay").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanPlay(); });
object_->registerProperty("CanPause").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanPause(); });
object_->registerProperty("CanSeek").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanSeek(); });
object_->registerProperty("CanControl").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanControl(); });
}
Player_adaptor(const Player_adaptor&) = delete;
Player_adaptor& operator=(const Player_adaptor&) = delete;
Player_adaptor(Player_adaptor&&) = default;
Player_adaptor& operator=(Player_adaptor&&) = default;
~Player_adaptor() = default;
public:
void emitSeeked(const int64_t& Position)
{
object_->emitSignal("Seeked").onInterface(INTERFACE_NAME).withArguments(Position);
}
private:
virtual void Next() = 0;
virtual void Previous() = 0;
virtual void Pause() = 0;
virtual void PlayPause() = 0;
virtual void Stop() = 0;
virtual void Play() = 0;
virtual void Seek(const int64_t& Offset) = 0;
virtual void SetPosition(const sdbus::ObjectPath& TrackId, const int64_t& Position) = 0;
virtual void OpenUri(const std::string& Uri) = 0;
private:
virtual std::string PlaybackStatus() = 0;
virtual double Rate() = 0;
virtual void Rate(const double& value) = 0;
virtual std::map<std::string, sdbus::Variant> Metadata() = 0;
virtual double Volume() = 0;
virtual void Volume(const double& value) = 0;
virtual int64_t Position() = 0;
virtual double MinimumRate() = 0;
virtual double MaximumRate() = 0;
virtual bool CanGoNext() = 0;
virtual bool CanGoPrevious() = 0;
virtual bool CanPlay() = 0;
virtual bool CanPause() = 0;
virtual bool CanSeek() = 0;
virtual bool CanControl() = 0;
private:
sdbus::IObject* object_;
};
}}} // namespaces
namespace org {
namespace mpris {
namespace MediaPlayer2 {
class TrackList_adaptor
{
public:
static constexpr const char* INTERFACE_NAME = "org.mpris.MediaPlayer2.TrackList";
protected:
TrackList_adaptor(sdbus::IObject& object)
: object_(&object)
{
object_->registerMethod("GetTracksMetadata").onInterface(INTERFACE_NAME).withInputParamNames("TrackIds").withOutputParamNames("Metadata").implementedAs([this](const std::vector<sdbus::ObjectPath>& TrackIds){ return this->GetTracksMetadata(TrackIds); });
object_->registerMethod("AddTrack").onInterface(INTERFACE_NAME).withInputParamNames("Uri", "AfterTrack", "SetAsCurrent").implementedAs([this](const std::string& Uri, const sdbus::ObjectPath& AfterTrack, const bool& SetAsCurrent){ return this->AddTrack(Uri, AfterTrack, SetAsCurrent); });
object_->registerMethod("RemoveTrack").onInterface(INTERFACE_NAME).withInputParamNames("TrackId").implementedAs([this](const sdbus::ObjectPath& TrackId){ return this->RemoveTrack(TrackId); });
object_->registerMethod("GoTo").onInterface(INTERFACE_NAME).withInputParamNames("TrackId").implementedAs([this](const sdbus::ObjectPath& TrackId){ return this->GoTo(TrackId); });
object_->registerSignal("TrackListReplaced").onInterface(INTERFACE_NAME).withParameters<std::vector<sdbus::ObjectPath>, sdbus::ObjectPath>("Tracks", "CurrentTrack");
object_->registerSignal("TrackAdded").onInterface(INTERFACE_NAME).withParameters<std::map<std::string, sdbus::Variant>, sdbus::ObjectPath>("Metadata", "AfterTrack");
object_->registerSignal("TrackRemoved").onInterface(INTERFACE_NAME).withParameters<sdbus::ObjectPath>("TrackId");
object_->registerSignal("TrackMetadataChanged").onInterface(INTERFACE_NAME).withParameters<sdbus::ObjectPath, std::map<std::string, sdbus::Variant>>("TrackId", "Metadata");
object_->registerProperty("Tracks").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Tracks(); });
object_->registerProperty("CanEditTracks").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanEditTracks(); });
}
TrackList_adaptor(const TrackList_adaptor&) = delete;
TrackList_adaptor& operator=(const TrackList_adaptor&) = delete;
TrackList_adaptor(TrackList_adaptor&&) = default;
TrackList_adaptor& operator=(TrackList_adaptor&&) = default;
~TrackList_adaptor() = default;
public:
void emitTrackListReplaced(const std::vector<sdbus::ObjectPath>& Tracks, const sdbus::ObjectPath& CurrentTrack)
{
object_->emitSignal("TrackListReplaced").onInterface(INTERFACE_NAME).withArguments(Tracks, CurrentTrack);
}
void emitTrackAdded(const std::map<std::string, sdbus::Variant>& Metadata, const sdbus::ObjectPath& AfterTrack)
{
object_->emitSignal("TrackAdded").onInterface(INTERFACE_NAME).withArguments(Metadata, AfterTrack);
}
void emitTrackRemoved(const sdbus::ObjectPath& TrackId)
{
object_->emitSignal("TrackRemoved").onInterface(INTERFACE_NAME).withArguments(TrackId);
}
void emitTrackMetadataChanged(const sdbus::ObjectPath& TrackId, const std::map<std::string, sdbus::Variant>& Metadata)
{
object_->emitSignal("TrackMetadataChanged").onInterface(INTERFACE_NAME).withArguments(TrackId, Metadata);
}
private:
virtual std::vector<std::map<std::string, sdbus::Variant>> GetTracksMetadata(const std::vector<sdbus::ObjectPath>& TrackIds) = 0;
virtual void AddTrack(const std::string& Uri, const sdbus::ObjectPath& AfterTrack, const bool& SetAsCurrent) = 0;
virtual void RemoveTrack(const sdbus::ObjectPath& TrackId) = 0;
virtual void GoTo(const sdbus::ObjectPath& TrackId) = 0;
private:
virtual std::vector<sdbus::ObjectPath> Tracks() = 0;
virtual bool CanEditTracks() = 0;
private:
sdbus::IObject* object_;
};
}}} // namespaces
#endif

View file

@ -1,306 +0,0 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__mpris_stub_proxy_hpp__proxy__H__
#define __sdbuscpp__mpris_stub_proxy_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace mpris {
class MediaPlayer2_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.mpris.MediaPlayer2";
protected:
MediaPlayer2_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
}
MediaPlayer2_proxy(const MediaPlayer2_proxy&) = delete;
MediaPlayer2_proxy& operator=(const MediaPlayer2_proxy&) = delete;
MediaPlayer2_proxy(MediaPlayer2_proxy&&) = default;
MediaPlayer2_proxy& operator=(MediaPlayer2_proxy&&) = default;
~MediaPlayer2_proxy() = default;
public:
void Raise()
{
proxy_->callMethod("Raise").onInterface(INTERFACE_NAME);
}
void Quit()
{
proxy_->callMethod("Quit").onInterface(INTERFACE_NAME);
}
public:
bool CanRaise()
{
return proxy_->getProperty("CanRaise").onInterface(INTERFACE_NAME);
}
bool CanQuit()
{
return proxy_->getProperty("CanQuit").onInterface(INTERFACE_NAME);
}
bool HasTrackList()
{
return proxy_->getProperty("HasTrackList").onInterface(INTERFACE_NAME);
}
std::string Identity()
{
return proxy_->getProperty("Identity").onInterface(INTERFACE_NAME);
}
std::vector<std::string> SupportedUriSchemes()
{
return proxy_->getProperty("SupportedUriSchemes").onInterface(INTERFACE_NAME);
}
std::vector<std::string> SupportedMimeTypes()
{
return proxy_->getProperty("SupportedMimeTypes").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy* proxy_;
};
}} // namespaces
namespace org {
namespace mpris {
namespace MediaPlayer2 {
class Player_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.mpris.MediaPlayer2.Player";
protected:
Player_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
proxy_->uponSignal("Seeked").onInterface(INTERFACE_NAME).call([this](const int64_t& Position){ this->onSeeked(Position); });
}
Player_proxy(const Player_proxy&) = delete;
Player_proxy& operator=(const Player_proxy&) = delete;
Player_proxy(Player_proxy&&) = default;
Player_proxy& operator=(Player_proxy&&) = default;
~Player_proxy() = default;
virtual void onSeeked(const int64_t& Position) = 0;
public:
void Next()
{
proxy_->callMethod("Next").onInterface(INTERFACE_NAME);
}
void Previous()
{
proxy_->callMethod("Previous").onInterface(INTERFACE_NAME);
}
void Pause()
{
proxy_->callMethod("Pause").onInterface(INTERFACE_NAME);
}
void PlayPause()
{
proxy_->callMethod("PlayPause").onInterface(INTERFACE_NAME);
}
void Stop()
{
proxy_->callMethod("Stop").onInterface(INTERFACE_NAME);
}
void Play()
{
proxy_->callMethod("Play").onInterface(INTERFACE_NAME);
}
void Seek(const int64_t& Offset)
{
proxy_->callMethod("Seek").onInterface(INTERFACE_NAME).withArguments(Offset);
}
void SetPosition(const sdbus::ObjectPath& TrackId, const int64_t& Position)
{
proxy_->callMethod("SetPosition").onInterface(INTERFACE_NAME).withArguments(TrackId, Position);
}
void OpenUri(const std::string& Uri)
{
proxy_->callMethod("OpenUri").onInterface(INTERFACE_NAME).withArguments(Uri);
}
public:
std::string PlaybackStatus()
{
return proxy_->getProperty("PlaybackStatus").onInterface(INTERFACE_NAME);
}
double Rate()
{
return proxy_->getProperty("Rate").onInterface(INTERFACE_NAME);
}
void Rate(const double& value)
{
proxy_->setProperty("Rate").onInterface(INTERFACE_NAME).toValue(value);
}
std::map<std::string, sdbus::Variant> Metadata()
{
return proxy_->getProperty("Metadata").onInterface(INTERFACE_NAME);
}
double Volume()
{
return proxy_->getProperty("Volume").onInterface(INTERFACE_NAME);
}
void Volume(const double& value)
{
proxy_->setProperty("Volume").onInterface(INTERFACE_NAME).toValue(value);
}
int64_t Position()
{
return proxy_->getProperty("Position").onInterface(INTERFACE_NAME);
}
double MinimumRate()
{
return proxy_->getProperty("MinimumRate").onInterface(INTERFACE_NAME);
}
double MaximumRate()
{
return proxy_->getProperty("MaximumRate").onInterface(INTERFACE_NAME);
}
bool CanGoNext()
{
return proxy_->getProperty("CanGoNext").onInterface(INTERFACE_NAME);
}
bool CanGoPrevious()
{
return proxy_->getProperty("CanGoPrevious").onInterface(INTERFACE_NAME);
}
bool CanPlay()
{
return proxy_->getProperty("CanPlay").onInterface(INTERFACE_NAME);
}
bool CanPause()
{
return proxy_->getProperty("CanPause").onInterface(INTERFACE_NAME);
}
bool CanSeek()
{
return proxy_->getProperty("CanSeek").onInterface(INTERFACE_NAME);
}
bool CanControl()
{
return proxy_->getProperty("CanControl").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy* proxy_;
};
}}} // namespaces
namespace org {
namespace mpris {
namespace MediaPlayer2 {
class TrackList_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.mpris.MediaPlayer2.TrackList";
protected:
TrackList_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
proxy_->uponSignal("TrackListReplaced").onInterface(INTERFACE_NAME).call([this](const std::vector<sdbus::ObjectPath>& Tracks, const sdbus::ObjectPath& CurrentTrack){ this->onTrackListReplaced(Tracks, CurrentTrack); });
proxy_->uponSignal("TrackAdded").onInterface(INTERFACE_NAME).call([this](const std::map<std::string, sdbus::Variant>& Metadata, const sdbus::ObjectPath& AfterTrack){ this->onTrackAdded(Metadata, AfterTrack); });
proxy_->uponSignal("TrackRemoved").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& TrackId){ this->onTrackRemoved(TrackId); });
proxy_->uponSignal("TrackMetadataChanged").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& TrackId, const std::map<std::string, sdbus::Variant>& Metadata){ this->onTrackMetadataChanged(TrackId, Metadata); });
}
TrackList_proxy(const TrackList_proxy&) = delete;
TrackList_proxy& operator=(const TrackList_proxy&) = delete;
TrackList_proxy(TrackList_proxy&&) = default;
TrackList_proxy& operator=(TrackList_proxy&&) = default;
~TrackList_proxy() = default;
virtual void onTrackListReplaced(const std::vector<sdbus::ObjectPath>& Tracks, const sdbus::ObjectPath& CurrentTrack) = 0;
virtual void onTrackAdded(const std::map<std::string, sdbus::Variant>& Metadata, const sdbus::ObjectPath& AfterTrack) = 0;
virtual void onTrackRemoved(const sdbus::ObjectPath& TrackId) = 0;
virtual void onTrackMetadataChanged(const sdbus::ObjectPath& TrackId, const std::map<std::string, sdbus::Variant>& Metadata) = 0;
public:
std::vector<std::map<std::string, sdbus::Variant>> GetTracksMetadata(const std::vector<sdbus::ObjectPath>& TrackIds)
{
std::vector<std::map<std::string, sdbus::Variant>> result;
proxy_->callMethod("GetTracksMetadata").onInterface(INTERFACE_NAME).withArguments(TrackIds).storeResultsTo(result);
return result;
}
void AddTrack(const std::string& Uri, const sdbus::ObjectPath& AfterTrack, const bool& SetAsCurrent)
{
proxy_->callMethod("AddTrack").onInterface(INTERFACE_NAME).withArguments(Uri, AfterTrack, SetAsCurrent);
}
void RemoveTrack(const sdbus::ObjectPath& TrackId)
{
proxy_->callMethod("RemoveTrack").onInterface(INTERFACE_NAME).withArguments(TrackId);
}
void GoTo(const sdbus::ObjectPath& TrackId)
{
proxy_->callMethod("GoTo").onInterface(INTERFACE_NAME).withArguments(TrackId);
}
public:
std::vector<sdbus::ObjectPath> Tracks()
{
return proxy_->getProperty("Tracks").onInterface(INTERFACE_NAME);
}
bool CanEditTracks()
{
return proxy_->getProperty("CanEditTracks").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy* proxy_;
};
}}} // namespaces
#endif

View file

@ -44,7 +44,7 @@ def add_font(input: str, output: str|None = None):
def add_graphic(input: str):
GRAPHIC=path.basename(input).removesuffix(".png").lower().replace('-', '_')
print("Adding graphic '%s' from file '%s'" % (GRAPHIC, input))
add_base85(input, GRAPHIC)
add_basic(input, GRAPHIC)
def add_license(input: str, output: str):
LICENSE = output + "_license"
print("Adding license '%s' (C identifier: '%s') from file '%s'" % (output, LICENSE, input))
@ -72,6 +72,7 @@ for i in glob("Noto_Sans_JP/*.ttf"):
add_font(i)
add_font("ForkAwesome/fonts/forkawesome-webfont.ttf", "forkawesome")
add_graphic("icon.png")
add_graphic("catoc.png")
add_license("Noto_Sans/OFL.txt", "notosans")
add_license("Noto_Sans_JP/OFL.txt", "notosansjp")
add_license("../LICENSE.MIT", "looper_mit")

View file

@ -3,5 +3,7 @@ set(BACKEND_FLUIDSYNTH_INC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DI
add_playback_backend(fluidsynth_backend ${BACKEND_FLUIDSYNTH_SRC})
target_include_directories(fluidsynth_backend PRIVATE ${BACKEND_FLUIDSYNTH_INC})
find_package(OpenMP)
find_package(FluidSynth)
target_link_libraries(fluidsynth_backend PUBLIC OpenMP::OpenMP_CXX FluidSynth::libfluidsynth)
find_package(PkgConfig)
pkg_check_modules(fluidsynth fluidsynth IMPORTED_TARGET)
target_link_libraries(fluidsynth_backend PUBLIC OpenMP::OpenMP_CXX PkgConfig::fluidsynth)

View file

@ -8,6 +8,23 @@
#include <string.h>
#include <file_backend.hpp>
#include <util.hpp>
#include <filesystem>
#include <limits.h>
#include <log.hpp>
namespace fs = std::filesystem;
using Looper::Log::LogStream;
static void log_fn(int level, const char *msg, void *data) {
LogStream *stream;
switch (level) {
case FLUID_ERR:
case FLUID_PANIC: stream = &ERROR; break;
case FLUID_WARN: stream = &WARNING; break;
default:
case FLUID_INFO: stream = &INFO; break;
case FLUID_DBG: stream = &DEBUG; break;
}
stream->writeln(msg);
}
void FluidSynthBackend::fluidsynth_get_property_list_wrapper(void *udata, const char *name, int type) {
((FluidSynthBackend*)udata)->fluidsynth_get_property_list(name, type);
}
@ -16,53 +33,238 @@ void FluidSynthBackend::fluidsynth_get_property_list(const char *name, int type)
property.set_path(fmt::format("fluidsynth/{0}", name));
property.set_name(name);
switch (type) {
case FLUID_NO_TYPE:
case FLUID_NUM_TYPE: {
property.set_type(PropertyType::Double);
double min, max;
if (fluid_settings_getnum_range(settings, name, &min, &max) == FLUID_OK) {
auto *range = property.mutable_hint()->mutable_range();
range->set_min(min);
range->set_max(max);
}
} break;
case FLUID_INT_TYPE: {
property.set_type(PropertyType::Int);
int min, max;
if (fluid_settings_getint_range(settings, name, &min, &max) == FLUID_OK) {
auto *range = property.mutable_hint()->mutable_range();
range->set_min(min);
range->set_max(max);
}
} break;
case FLUID_STR_TYPE: {
property.set_type(PropertyType::String);
} break;
default: {
throw std::exception();
} break;
}
property.set_id(PropertyId::BackendSpecific);
fluidsynth_properties.push_back(property);
}
static bool log_fn_registered = false;
std::vector<Property> FluidSynthBackend::get_property_list() {
return fluidsynth_properties;
return fluidsynth_properties;
}
void FluidSynthBackend::load(const char *filename) {
memset(&spec, 0, sizeof(spec));
memset(&spec, 0, sizeof(spec));
if (!log_fn_registered) {
fluid_set_log_function(FLUID_PANIC, &log_fn, nullptr);
fluid_set_log_function(FLUID_ERR, &log_fn, nullptr);
fluid_set_log_function(FLUID_WARN, &log_fn, nullptr);
fluid_set_log_function(FLUID_INFO, &log_fn, nullptr);
fluid_set_log_function(FLUID_DBG, &log_fn, nullptr);
log_fn_registered = true;
}
current_file = filename;
spec.format = AUDIO_F32SYS;
spec.samples = 100;
spec.channels = 2;
spec.freq = 48000;
spec.freq = 96000;
spec.size = 100 * 2 * sizeof(int16_t);
file = open_file(filename);
this->settings = new_fluid_settings();
fluid_settings_foreach(settings, (void*)this, &fluidsynth_get_property_list_wrapper);
File *file = open_file(filename);
settings = new_fluid_settings();
fluid_settings_setnum(settings, "synth.sample-rate", 96000.0);
fluid_settings_setnum(settings, "synth.gain", 0.5);
fluid_settings_foreach(settings, (void*)this, &fluidsynth_get_property_list_wrapper);
synth = new_fluid_synth(settings);
player = new_fluid_player(synth);
fs::path fpath(filename);
fpath = fpath.parent_path() / fpath.stem();
std::string fpath_str = fpath.string();
std::vector<std::string> try_paths = std::vector<std::string>({
fpath_str + ".sf2", fpath_str + ".dls"
});
bool found = false;
for (auto &path : try_paths) {
const char *path_c = path.c_str();
if (fluid_is_soundfont(path_c)) {
fluid_synth_sfload(synth, path_c, 1);
found = true;
break;
}
}
if (!found) {
WARNING.writeln("Could not find a valid companion file to use as a sound font. Make sure the extension is all-lowercase, if you're on a case sensitive filesystem (Such as on Linux).");
throw std::exception();
}
file_data = malloc(file->get_len());
file_len = file->read(file_data, 1, file->get_len());
delete file;
file_data = realloc(file_data, file_len);
fluid_player_add_mem(player, file_data, file_len);
fluid_player_set_loop(player, -1);
fluid_player_play(player);
size_t prev_pos = 0;
size_t cur_pos = 0;
size_t samples = 0;
sample_positions.push_back({0, 0});
float fakebuf;
fluid_player_seek(player, 0);
while (prev_pos <= cur_pos) {
size_t samples_to_add = std::floor((96000.0 * 60.0) / fluid_player_get_bpm(player));
fluid_synth_write_float(synth, samples_to_add, &fakebuf, 0, 0, &fakebuf, 0, 0);
samples += samples_to_add;
prev_pos = cur_pos;
while (prev_pos == cur_pos) {
cur_pos = fluid_player_get_current_tick(player);
if (prev_pos == cur_pos) {
fluid_synth_write_float(synth, 1, &fakebuf, 0, 0, &fakebuf, 0, 0);
samples++;
}
}
sample_positions.push_back({samples, cur_pos});
}
this->length = samples;
}
extern SDL_AudioSpec obtained;
void FluidSynthBackend::switch_stream(int idx) {
fluid_player_seek(player, 0);
open = true;
}
void FluidSynthBackend::cleanup() {
delete file;
file = nullptr;
delete_fluid_player(player);
player = nullptr;
delete_fluid_synth(synth);
synth = nullptr;
delete_fluid_settings(settings);
settings = nullptr;
file_len = 0;
free(file_data);
file_data = nullptr;
open = false;
}
size_t FluidSynthBackend::render(void *buf, size_t maxlen) {
size_t sample_type_len = 2;
maxlen /= sample_type_len;
maxlen *= sample_type_len;
return copied;
}
void FluidSynthBackend::seek(double position) {
size_t sample_type_len = sizeof(float);
maxlen /= sample_type_len * 2;
position += maxlen;
maxlen *= sample_type_len * 2;
if (fluid_synth_write_float(synth, maxlen / 2 / sample_type_len, buf, 0, 2, buf, 1, 2) == FLUID_FAILED) {
return 0;
}
if (position >= length) {
int tick = fluid_player_get_current_tick(player);
int prev_sample = 0;
int cur_sample = 0;
for (auto &pair : sample_positions) {
prev_sample = cur_sample;
cur_sample = pair.first;
if (pair.second > tick) {
position = ((double)prev_sample) / 96000.0;
break;
} else if (pair.second == tick) {
position = ((double)cur_sample) / 96000.0;
break;
}
}
}
return maxlen;
}
double FluidSynthBackend::get_position() {
bool FluidSynthBackend::is_fluidsynth_setting(std::string path) {
const char *prefix = "fluidsynth/";
if (path.length() < strlen(prefix)) return false;
return path.substr(0, strlen(prefix)) == std::string(prefix);
}
void FluidSynthBackend::seek_samples(uint64_t position) {
size_t tick = 0;
size_t sample = position;
size_t prev_sample = 0;
size_t next_sample = 0;
for (auto &pair : sample_positions) {
prev_sample = next_sample;
next_sample = pair.first;
if (next_sample > sample) {
tick = pair.second - 1;
this->position = prev_sample;
break;
} else if (next_sample == sample) {
tick = pair.second;
this->position = next_sample;
prev_sample = next_sample;
break;
}
}
fluid_player_seek(player, tick);
float fakebuf;
if (sample > prev_sample) fluid_synth_write_float(synth, sample - prev_sample, &fakebuf, 0, 0, &fakebuf, 0, 0);
this->position = position;
}
uint64_t FluidSynthBackend::get_position_samples() {
return position;
}
int FluidSynthBackend::get_stream_idx() {
return 0;
}
void FluidSynthBackend::set_fluidsynth_property_str(std::string path, std::string val) {
fluid_settings_setstr(settings, path.c_str(), val.c_str());
}
void FluidSynthBackend::set_fluidsynth_property_num(std::string path, double val) {
fluid_settings_setnum(settings, path.c_str(), val);
}
void FluidSynthBackend::set_fluidsynth_property_int(std::string path, int val) {
fluid_settings_setint(settings, path.c_str(), val);
}
std::optional<std::string> FluidSynthBackend::get_fluidsynth_property_str(std::string path) {
char *tmp;
if (fluid_settings_dupstr(settings, path.c_str(), &tmp) == FLUID_OK) {
std::string output = tmp;
free((void*)tmp);
return output;
} else {
return {};
}
}
std::optional<double> FluidSynthBackend::get_fluidsynth_property_num(std::string path) {
double output = NAN;
if (fluid_settings_getnum(settings, path.c_str(), &output) == FLUID_OK) return output;
else return {};
}
std::optional<int> FluidSynthBackend::get_fluidsynth_property_int(std::string path) {
int output = 0;
if (fluid_settings_getint(settings, path.c_str(), &output) == FLUID_OK) return output;
else return {};
}
void FluidSynthBackend::reset_fluidsynth_property(std::string path) {
switch (fluid_settings_get_type(settings, path.c_str())) {
case FLUID_INT_TYPE: {
int output;
if (fluid_settings_getint_default(settings, path.c_str(), &output) == FLUID_OK) {
fluid_settings_setint(settings, path.c_str(), output);
}
} break;
case FLUID_STR_TYPE: {
char *val;
if (fluid_settings_getstr_default(settings, path.c_str(), &val) == FLUID_OK) {
fluid_settings_setstr(settings, path.c_str(), val);
}
} break;
case FLUID_NUM_TYPE: {
double val;
if (fluid_settings_getnum_default(settings, path.c_str(), &val) == FLUID_OK) {
fluid_settings_setnum(settings, path.c_str(), val);
}
} break;
}
}

View file

@ -12,19 +12,114 @@
#include <SDL.h>
#include "file_backend.hpp"
#include <fluidsynth.h>
#include <vector>
class FluidSynthBackend : public PlaybackBackend {
File *file;
void *file_data;
size_t file_len;
static void fluidsynth_get_property_list_wrapper(void *udata, const char *name, int type);
std::vector<Property> fluidsynth_properties;
fluid_settings_t *settings;
fluid_synth_t *synth;
fluid_player_t *player;
std::vector<std::pair<size_t, size_t>> sample_positions;
void fluidsynth_get_property_list(const char *name, int type);
public:
void set_fluidsynth_property_str(std::string path, std::string val);
void set_fluidsynth_property_num(std::string path, double val);
void set_fluidsynth_property_int(std::string path, int val);
std::string get_fluidsynth_property_str(std::string path);
double get_fluidsynth_property_num(std::string path);
int get_fluidsynth_property_int(std::string path);
std::optional<std::string> get_fluidsynth_property_str(std::string path);
std::optional<double> get_fluidsynth_property_num(std::string path);
std::optional<int> get_fluidsynth_property_int(std::string path);
void reset_fluidsynth_property(std::string path);
bool is_fluidsynth_setting(std::string path);
inline std::optional<std::string> get_property_string(google::protobuf::Any value) {
try {
StringProperty * property = resolve_any<StringProperty>(value);
std::string output = property->value();
delete property;
return output;
} catch (std::exception e) {
}
return {};
}
inline std::optional<double> get_property_double(google::protobuf::Any value) {
try {
DoubleProperty *property = resolve_any<DoubleProperty>(value);
double output = property->value();
delete property;
return output;
} catch (std::exception e) {
}
return {};
}
inline std::optional<int> get_property_int(google::protobuf::Any value) {
try {
IntProperty *property = resolve_any<IntProperty>(value);
int output = property->value();
delete property;
return output;
} catch (std::exception e) {
}
return {};
}
inline bool set(std::string path, google::protobuf::Any value) override {
if (!is_fluidsynth_setting(path)) {
return PlaybackBackend::set(path, value);
}
std::string fluidsynth_path = path.substr(strlen("fluidsynth/"));
auto maybe_num = get_property_double(value);
if (maybe_num.has_value()) {
set_fluidsynth_property_num(fluidsynth_path, maybe_num.value());
return true;
}
auto maybe_int = get_property_int(value);
if (maybe_int.has_value()) {
set_fluidsynth_property_int(fluidsynth_path, maybe_int.value());
return true;
}
auto maybe_string = get_property_string(value);
if (maybe_string.has_value()) {
set_fluidsynth_property_str(fluidsynth_path, maybe_string.value());
return true;
}
return false;
}
inline std::optional<google::protobuf::Any> get(std::string path) override {
if (!is_fluidsynth_setting(path)) return PlaybackBackend::get(path);
std::string fluidsynth_path = path.substr(strlen("fluidsynth/"));
google::protobuf::Any output;
switch (fluid_settings_get_type(settings, fluidsynth_path.c_str())) {
case FLUID_INT_TYPE: {
IntProperty prop;
auto val = get_fluidsynth_property_int(fluidsynth_path);
if (!val.has_value()) return {};
prop.set_value(val.value());
output.PackFrom(prop);
} break;
case FLUID_STR_TYPE: {
StringProperty prop;
auto val = get_fluidsynth_property_str(fluidsynth_path);
if (!val.has_value()) return {};
prop.set_value(val.value());
output.PackFrom(prop);
} break;
case FLUID_NUM_TYPE: {
DoubleProperty prop;
auto val = get_fluidsynth_property_num(fluidsynth_path);
if (!val.has_value()) return {};
prop.set_value(val.value());
output.PackFrom(prop);
} break;
default: return {};
}
return output;
}
inline std::optional<google::protobuf::Any> reset(std::string path) override {
if (!is_fluidsynth_setting(path)) return PlaybackBackend::reset(path);
std::string fluidsynth_path = path.substr(strlen("fluidsynth/"));
reset_fluidsynth_property(fluidsynth_path);
return get(path);
}
inline std::string get_id() override {
return "fluidsynth";
}
@ -32,15 +127,12 @@ class FluidSynthBackend : public PlaybackBackend {
return "MIDI player";
}
std::vector<Property> get_property_list() override;
void seek(double position) override;
void seek_samples(uint64_t position) override;
void load(const char *filename) override;
void switch_stream(int idx) override;
void cleanup() override;
int get_stream_idx() override;
size_t render(void *buf, size_t maxlen) override;
double get_position() override;
inline double get_length() override {
return length;
}
uint64_t get_position_samples() override;
inline ~FluidSynthBackend() override { }
};

View file

@ -0,0 +1,7 @@
set(BACKEND_GME_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gme_backend.cpp)
set(BACKEND_GME_INC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
pkg_check_modules(libgme IMPORTED_TARGET libgme)
add_playback_backend(gme_backend ${BACKEND_GME_SRC})
target_include_directories(gme_backend PRIVATE ${BACKEND_GME_INC})
find_package(OpenMP)
target_link_libraries(gme_backend PUBLIC PkgConfig::libgme OpenMP::OpenMP_CXX)

View file

@ -0,0 +1,4 @@
{
"class_name": "GmeBackend",
"include_path": "gme_backend.hpp"
}

View file

@ -0,0 +1,39 @@
# Install script for directory: /boot/home/Desktop/looper/backends/playback/zsm
# Set the install prefix
if(NOT DEFINED CMAKE_INSTALL_PREFIX)
set(CMAKE_INSTALL_PREFIX "/boot/system")
endif()
string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
# Set the install configuration name.
if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
if(BUILD_TYPE)
string(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
else()
set(CMAKE_INSTALL_CONFIG_NAME "RelWithDebInfo")
endif()
message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
endif()
# Set the component getting installed.
if(NOT CMAKE_INSTALL_COMPONENT)
if(COMPONENT)
message(STATUS "Install component: \"${COMPONENT}\"")
set(CMAKE_INSTALL_COMPONENT "${COMPONENT}")
else()
set(CMAKE_INSTALL_COMPONENT)
endif()
endif()
# Is this installation the result of a crosscompile?
if(NOT DEFINED CMAKE_CROSSCOMPILING)
set(CMAKE_CROSSCOMPILING "FALSE")
endif()
# Set path to fallback-tool for dependency-resolution.
if(NOT DEFINED CMAKE_OBJDUMP)
set(CMAKE_OBJDUMP "/system/bin/objdump")
endif()

View file

@ -0,0 +1,193 @@
#include "gme_backend.hpp"
#include <algorithm>
#include <ipc/common.pb.h>
#include <exception>
#include <filesystem>
#include "file_backend.hpp"
#include <stddef.h>
#include <string.h>
#include <file_backend.hpp>
#include <util.hpp>
#include <license.hpp>
#include <assets/assets.h>
#include <limits.h>
#include <gme/gme.h>
std::vector<Property> GmeBackend::get_property_list() {
std::vector<Property> output;
for (auto &kv : voices) {
Property prop;
prop.set_id(PropertyId::BackendSpecific);
prop.set_name(fmt::format("gme/voice/{}/enable", kv.first));
prop.set_type(PropertyType::Boolean);
prop.set_path(prop.name());
output.push_back(prop);
}
return output;
}
std::optional<int> GmeBackend::get_voice_id(std::string path) {
std::string prefix = "gme/voice/";
std::string suffix = "/enable";
if (path.substr(0, prefix.length()) != prefix) {
return {};
}
if (path.substr(path.length() - suffix.length()) != suffix) {
return {};
}
path = path.substr(prefix.length());
path = path.substr(0, path.length() - suffix.length());
if (!voices.contains(path)) {
return {};
}
return voices[path];
}
bool GmeBackend::set(std::string path, google::protobuf::Any value) {
auto maybe_voice_id = get_voice_id(path);
if (maybe_voice_id.has_value()) {
bool enable = resolve_value<bool>(value);
gme_mute_voice(gme_backend, maybe_voice_id.value(), !enable);
properties[path] = value;
return true;
}
return false;
}
std::optional<google::protobuf::Any> GmeBackend::get(std::string path) {
auto maybe_voice_id = get_voice_id(path);
if (maybe_voice_id.has_value()) {
if (properties.contains(path)) {
return properties[path];
} else {
return reset(path);
}
} else {
return {};
}
}
std::optional<google::protobuf::Any> GmeBackend::reset(std::string path) {
auto maybe_voice_id = get_voice_id(path);
if (maybe_voice_id.has_value()) {
google::protobuf::Any prop;
BooleanProperty bool_prop;
bool_prop.set_value(true);
prop.PackFrom(bool_prop);
properties[path] = prop;
gme_mute_voice(gme_backend, maybe_voice_id.value(), false);
return prop;
} else {
return {};
}
}
void GmeBackend::load(const char *filename) {
memset(&spec, 0, sizeof(spec));
current_file = filename;
spec.format = AUDIO_S16SYS;
spec.samples = 100;
spec.channels = 2;
spec.freq = 48000;
spec.size = 100 * 2 * sizeof(int16_t);
gme_open_file(filename, &gme_backend, spec.freq);
if (gme_backend == NULL) {
throw std::exception();
}
int track_count = gme_track_count(gme_backend);
streams.clear();
for (int i = 0; i < track_count; i++) {
gme_info_t *info;
gme_track_info(gme_backend, &info, i);
PlaybackStream stream;
if (info->song[0] == '\0') {
stream.name = fmt::format("Song {}", i);
} else {
stream.name = info->song;
}
stream.length = ((double)info->length) / 1000.0;
if (stream.length < 0.0) stream.length = 0.0;
stream.id = i;
streams.push_back(stream);
gme_free_info(info);
}
}
extern SDL_AudioSpec obtained;
void GmeBackend::switch_stream(int idx) {
if (gme_backend == NULL) {
throw std::exception();
}
if (idx > streams.size()) {
throw std::exception();
}
gme_start_track(gme_backend, idx);
gme_info_t *info;
gme_track_info(gme_backend, &info, idx);
if (info->length < 0) {
info->length = 0;
}
uint64_t tmp = info->length;
this->loop_len = info->loop_length;
this->loop_start = info->intro_length;
if (tmp == 0) tmp = this->loop_len + this->loop_start;
tmp *= spec.freq;
tmp /= 1000;
this->length = tmp;
if (info->song[0] == '\0') {
this->current_title = {};
} else {
this->current_title = info->song;
}
int voice_count = gme_voice_count(gme_backend);
for (int i = 0; i < voice_count; i++) {
const char *voice_name = gme_voice_name(gme_backend, i);
voices[voice_name] = i;
}
gme_free_info(info);
gme_ignore_silence(gme_backend, true);
open = true;
}
void GmeBackend::cleanup() {
gme_delete(gme_backend);
gme_backend = NULL;
streams.clear();
}
size_t GmeBackend::render(void *buf, size_t maxlen) {
if (gme_backend == NULL) return 0;
const char *err;
size_t sample_type_len = 4;
maxlen /= sample_type_len;
if (err = gme_play(gme_backend, maxlen * 2, (short*)buf)) {
ERROR.writefln("Failed to play audio: %s", err);
return 0;
}
int pos_samples = gme_tell_samples(gme_backend);
int loop_end = loop_len + loop_start;
uint64_t loop_end_samples = loop_end;
loop_end_samples *= spec.freq;
loop_end_samples /= 1000;
uint64_t loop_start_samples = loop_start;
loop_start_samples *= spec.freq;
loop_start_samples /= 1000;
if (pos_samples >= loop_end_samples || gme_track_ended(gme_backend)) {
gme_seek_samples(gme_backend, loop_start_samples + (pos_samples - loop_end_samples));
}
maxlen *= sample_type_len;
return maxlen;
}
uint64_t GmeBackend::get_min_samples() {
return spec.size;
}
std::optional<uint64_t> GmeBackend::get_max_samples() {
return get_min_samples();
}
void GmeBackend::seek_samples(uint64_t position) {
gme_seek_samples(gme_backend, position);
}
uint64_t GmeBackend::get_position_samples() {
return gme_tell_samples(gme_backend);
}
int GmeBackend::get_stream_idx() {
return streamidx;
}
void GmeBackend::add_licenses() {
auto &license_data = get_license_data();
auto gme = LicenseData("gme", "lgpl-2.1");
LOAD_LICENSE(gme, lgpl_2_1);
license_data.insert(gme);
}

View file

@ -0,0 +1,48 @@
#pragma once
#include "playback_backend.hpp"
#include <omp.h>
#include <cstdint>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <streambuf>
#include <vector>
#include <util.hpp>
#include <SDL.h>
#include <gme/gme.h>
#include "file_backend.hpp"
class GmeBackend : public PlaybackBackend {
File *file;
Fifo<int16_t> audio_buf;
Music_Emu *gme_backend;
int streamidx;
int loop_len, loop_start;
std::map<std::string, int> voices;
std::optional<int> get_voice_id(std::string path);
public:
uint64_t get_min_samples() override;
std::optional<uint64_t> get_max_samples() override;
inline std::string get_id() override {
return "gme";
}
inline std::string get_name() override {
return "Game Music Emu";
}
void add_licenses() override;
std::vector<Property> get_property_list() override;
bool set(std::string path, google::protobuf::Any value) override;
std::optional<google::protobuf::Any> get(std::string path) override;
std::optional<google::protobuf::Any> reset(std::string path) override;
void seek_samples(uint64_t position) override;
void load(const char *filename) override;
void switch_stream(int idx) override;
void cleanup() override;
int get_stream_idx() override;
size_t render(void *buf, size_t maxlen) override;
uint64_t get_position_samples() override;
inline uint64_t get_length_samples() override {
return length;
}
inline ~GmeBackend() override { }
};

View file

@ -0,0 +1,10 @@
#define BOOL_PROPERTY(name, default_value) _PROPERTY(name, bool, default_value)
#define DOUBLE_PROPERTY(name, default_value) _PROPERTY(name, double, default_value)
BOOL_PROPERTY(pcm_enable, true)
BOOL_PROPERTY(psg_enable, true)
BOOL_PROPERTY(fm_enable, true)
DOUBLE_PROPERTY(tempo, 1.0)
DOUBLE_PROPERTY(psg_volume, 1.0)
DOUBLE_PROPERTY(fm_volume, 1.0)
#undef BOOL_PROPERTY
#undef DOUBLE_PROPERTY

View file

@ -62,10 +62,7 @@ void SDLMixerXBackend::load(const char *filename) {
current_file = std::string(filename);
const char *title_tag = Mix_GetMusicTitleTag(output);
// Check for an empty string, which indicates there's no title tag.
if (title_tag[0] == '\0') {
std::filesystem::path path(current_file);
current_title = path.stem().string();
} else {
if (title_tag[0] != '\0') {
current_title = std::string(title_tag);
}
this->music = output;
@ -73,9 +70,9 @@ void SDLMixerXBackend::load(const char *filename) {
PlaybackStream stream;
stream.id = 0;
stream.length = Mix_MusicDuration(output);
stream.name = current_title;
streams.push_back(stream);
open = true;
stream.name = get_title().value();
streams.push_back(stream);
initial = true;
}
void SDLMixerXBackend::switch_stream(int idx) {
@ -113,7 +110,7 @@ size_t SDLMixerXBackend::render(void *buf, size_t maxlen) {
}
return i;
}
double SDLMixerXBackend::get_position() {
double SDLMixerXBackend::get_position_time() {
if (music == nullptr || file == nullptr) {
open = false;
return 0.0;

View file

@ -7,6 +7,7 @@ class SDLMixerXBackend : public PlaybackBackend {
Mix_CommonMixer_t mixer;
File *file;
bool initial = false;
double length;
public:
inline std::string get_id() override {
return "sdl_mixer_x";
@ -15,8 +16,20 @@ class SDLMixerXBackend : public PlaybackBackend {
return "SDL Mixer X";
}
std::optional<uint64_t> get_max_samples() override;
inline double get_length() override {
return length;
}
inline uint64_t get_length_samples() override {
return time_to_samples(get_length());
}
void seek(double position) override;
double get_position() override;
inline void seek_samples(uint64_t position) override {
seek(samples_to_time(position));
}
double get_position_time() override;
inline uint64_t get_position_samples() override {
return time_to_samples(get_position_time());
}
void load(const char *filename) override;
void switch_stream(int idx) override;
void cleanup() override;

View file

@ -115,23 +115,23 @@ size_t VgmStreamBackend::render(void *buf, size_t maxlen) {
size_t new_samples = render_vgmstream((sample_t*)(buf), (int)maxlen, vf);
if (maxlen > new_samples) {
reset_vgmstream(vf);
position = 0.0;
position = 0;
} else {
position += (double)new_samples / (double)vf->sample_rate;
position += new_samples;
}
return new_samples * sizeof(sample_t);
}
void VgmStreamBackend::seek(double position) {
void VgmStreamBackend::seek_samples(uint64_t position) {
if (vf == nullptr || sf == nullptr || file == nullptr) {
open = false;
return;
}
auto pos32 = (int32_t)(position * vf->sample_rate);
auto pos32 = (int32_t)(position);
seek_vgmstream(vf, pos32);
this->position = position;
}
double VgmStreamBackend::get_position() {
uint64_t VgmStreamBackend::get_position_samples() {
return position;
}
int VgmStreamBackend::get_stream_idx() {

View file

@ -17,13 +17,13 @@ class VgmStreamBackend : public PlaybackBackend {
inline std::string get_name() override {
return "VGMStream";
}
void seek(double position) override;
void seek_samples(uint64_t position) override;
void load(const char *filename) override;
void switch_stream(int idx) override;
void cleanup() override;
int get_stream_idx() override;
size_t render(void *buf, size_t maxlen) override;
double get_position() override;
uint64_t get_position_samples() override;
void add_licenses() override;
inline ~VgmStreamBackend() override { }
};

View file

@ -40,7 +40,7 @@ std::vector<Property> ZsmBackend::get_property_list() {
return properties;
}
void ZsmBackend::load(const char *filename) {
memset(&spec, 0, sizeof(spec));
memset(&spec, 0, sizeof(spec));
current_file = filename;
spec.format = AUDIO_S16SYS;
spec.samples = 100;
@ -106,31 +106,31 @@ void ZsmBackend::load(const char *filename) {
}
file->seek(music_data_start, SeekType::SET);
this->loop_point = std::max(this->loop_point, (uint32_t)music_data_start);
double prev_time = 0.0;
double time = 0.0;
uint64_t prev_time = 0;
uint64_t time = 0;
double tmpDelayTicks = 0.0;
loop_pos = -1.0;
loop_pos = UINT64_MAX;
uint32_t prev_pos = music_data_start;
while (true) {
tmpDelayTicks -= get_delay_per_frame();
if (tmpDelayTicks < 0.0) {
ZsmCommand cmd = get_command();
size_t cur_pos = file->get_pos();
if (cur_pos >= this->loop_point && this->loop_pos < 0) {
if (cur_pos >= this->loop_point && this->loop_pos == UINT64_MAX) {
loop_pos = time;
this->loop_point = cur_pos;
}
if (cmd.id == ZsmEOF) {
break;
} else if (cmd.id == Delay) {
time += ((double)cmd.delay) / ((double)(tick_rate));
time += (((double)cmd.delay) / ((double)(tick_rate))) * PSG_FREQ;
tmpDelayTicks += cmd.delay;
}
prev_pos = file->get_pos();
prev_time = time;
}
}
if (this->loop_pos < 0.0) {
if (this->loop_pos == UINT64_MAX) {
this->loop_pos = 0.0;
this->loop_point = music_data_start;
}
@ -157,6 +157,7 @@ void ZsmBackend::load(const char *filename) {
property_defaults[#name] = value; \
}
#include "properties.inc"
PlaybackStream stream;
}
extern SDL_AudioSpec obtained;
void ZsmBackend::switch_stream(int idx) {
@ -172,6 +173,7 @@ void ZsmBackend::switch_stream(int idx) {
this->cpuClocks = 0.0;
this->delayTicks = 0.0;
this->ticks = 0.0;
open = true;
}
void ZsmBackend::cleanup() {
delete file;
@ -223,7 +225,7 @@ void ZsmBackend::tick(bool step) {
} break;
case Delay: {
delayTicks += cmd.delay;
position += ((double)cmd.delay) / ((double)(tick_rate));
position += (((double)cmd.delay) / ((double)(tick_rate))) * spec.freq;
} break;
case ExtCmd: {
//cmd.extcmd.channel
@ -384,16 +386,14 @@ ZsmCommand::~ZsmCommand() {
}
}
}
void ZsmBackend::seek_internal(double position, bool loop) {
this->position = std::floor(this->position * PSG_FREQ) / PSG_FREQ;
position = std::floor(position * PSG_FREQ) / PSG_FREQ;
void ZsmBackend::seek_internal(uint64_t position, bool loop) {;
if (this->position > position) {
switch_stream(0);
file->seek(music_data_start, SeekType::SET);
this->cpuClocks = 0.0;
this->delayTicks = 0;
this->ticks = 0.0;
this->position = 0.0;
this->position = 0;
} else if (this->position == position) {
audio_buf.clear();
return;
@ -408,7 +408,7 @@ void ZsmBackend::seek_internal(double position, bool loop) {
this->cpuClocks = 0.0;
this->delayTicks = 0;
this->ticks = 0.0;
this->position = 0.0;
this->position = 0;
audio_buf.clear();
return;
}
@ -419,15 +419,18 @@ void ZsmBackend::seek_internal(double position, bool loop) {
}
this->position = position;
}
void ZsmBackend::seek(double position) {
void ZsmBackend::seek_samples(uint64_t position) {
seek_internal(position, false);
}
double ZsmBackend::get_position() {
uint64_t ZsmBackend::get_position_samples() {
return position;
}
int ZsmBackend::get_stream_idx() {
return 0;
}
uint64_t ZsmBackend::get_loop_start_samples() {
return loop_start;
}
void ZsmBackend::audio_step(size_t samples) {
if (samples == 0) return;

View file

@ -108,7 +108,7 @@ class ZsmBackend : public PlaybackBackend {
return audio_buf.pop((int16_t*)buf, len);
}
uint32_t loop_point;
double loop_pos = 0.0;
uint64_t loop_pos = 0;
uint32_t pcm_offset;
uint8_t fm_mask;
uint16_t psg_channel_mask;
@ -123,7 +123,7 @@ class ZsmBackend : public PlaybackBackend {
return 1.0;
}
void tick(bool step = true);\
void seek_internal(double position, bool loop = true);
void seek_internal(uint64_t position, bool loop = true);
ZsmCommand get_command();
public:
uint64_t get_min_samples() override;
@ -136,15 +136,16 @@ class ZsmBackend : public PlaybackBackend {
}
void add_licenses() override;
std::vector<Property> get_property_list() override;
void seek(double position) override;
void seek_samples(uint64_t position) override;
void load(const char *filename) override;
void switch_stream(int idx) override;
void cleanup() override;
int get_stream_idx() override;
size_t render(void *buf, size_t maxlen) override;
double get_position() override;
inline double get_length() override {
uint64_t get_position_samples() override;
inline uint64_t get_length_samples() override {
return length;
}
uint64_t get_loop_start_samples() override;
inline ~ZsmBackend() override { }
};

View file

@ -1,4 +1,4 @@
set(BACKEND_HAIKU_SRC_BASE main.cpp main_window.cpp prefs.cpp slider.cpp slider.h main.h main_window.h aboutwindow.h aboutwindow.cpp prefs.h)
set(BACKEND_HAIKU_SRC_BASE main.cpp main_window.cpp prefs.cpp slider.cpp slider.h main.h main_window.h aboutwindow.h aboutwindow.cpp prefs.h image_view.cpp image_view.h)
set(BACKEND_HAIKU_SRC )
foreach(SRC IN ITEMS ${BACKEND_HAIKU_SRC_BASE})
set(BACKEND_HAIKU_SRC ${BACKEND_HAIKU_SRC} ${CMAKE_CURRENT_SOURCE_DIR}/${SRC})

View file

@ -0,0 +1,98 @@
#include "image_view.h"
#include <stdio.h>
LImageView::LImageView(const char *name, BBitmap *bitmap, BAlignment align) : BView(name, B_SUPPORTS_LAYOUT|B_FRAME_EVENTS|B_WILL_DRAW)
{
SetBitmap(bitmap);
SetAlignment(align);
}
void LImageView::SetBitmap(BBitmap *bitmap) {
this->bitmap = bitmap;
Invalidate();
}
void LImageView::SetAlignment(BAlignment align) {
this->align = align;
Invalidate();
}
BBitmap *LImageView::Bitmap() {
return bitmap;
}
BAlignment LImageView::Alignment() {
return align;
}
void LImageView::FrameResized(float newW, float newH) {
Invalidate();
}
void LImageView::Draw(BRect updateRect) {
AdoptSystemColors();
SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
SetDrawingMode(B_OP_ALPHA);
FillRect(img_bounds, B_SOLID_LOW);
if (bitmap == NULL) return; // Handle null image.
int x = 0, y = 0;
int mw, mh;
int bw, bh;
bw = bitmap->Bounds().Width();
bh = bitmap->Bounds().Height();
int iw = bw, ih = bh;
mw = Bounds().Width();
mh = Bounds().Height();
float wr = ((float)mw) / ((float)bw);
float hr = ((float)mh) / ((float)bh);
if (wr > 1.0f) wr = 1.0f;
if (hr > 1.0f) hr = 1.0f;
if (wr < hr) {
iw *= wr;
ih *= wr;
} else {
iw *= hr;
ih *= hr;
}
switch (align.Horizontal()) {
case B_ALIGN_LEFT: {
x = 0;
} break;
case B_ALIGN_HORIZONTAL_CENTER: {
x = (mw - iw) / 2;
} break;
case B_ALIGN_RIGHT: {
x = mw - iw;
} break;
}
switch (align.Vertical()) {
case B_ALIGN_TOP: {
y = 0;
} break;
case B_ALIGN_VERTICAL_CENTER: {
y = (mh - ih) / 2;
} break;
case B_ALIGN_BOTTOM: {
y = mh - ih;
} break;
}
BRect bitmap_rect(0, 0, bw, bh);
BRect draw_rect(x, y, iw + x, ih + y);
int left = updateRect.left;
int right = updateRect.right;
int top = updateRect.top;
int bottom = updateRect.bottom;
int x2 = x + bw;
int y2 = y + bh;
FillRect(draw_rect, B_SOLID_LOW);
DrawBitmap(bitmap, bitmap_rect, draw_rect);
img_bounds = draw_rect;
}
void LImageView::GetPreferredSize(float *w, float *h) {
if (!bitmap) {
*w = 0;
*h = 0;
return;
}
*w = bitmap->Bounds().Width();
*h = bitmap->Bounds().Height();
}

View file

@ -0,0 +1,21 @@
#pragma once
#include <View.h>
#include <Bitmap.h>
#include <Alignment.h>
#include <Rect.h>
class LImageView : public BView {
BBitmap *bitmap;
BAlignment align;
BRect img_bounds;
public:
void GetPreferredSize(float *w, float *h) override;
void SetBitmap(BBitmap *bitmap);
BBitmap *Bitmap();
void SetAlignment(BAlignment align);
BAlignment Alignment();
void Draw(BRect updateRect) override;
void FrameResized(float newW, float newH) override;
LImageView(const char *name, BBitmap *bitmap, BAlignment align);
};

View file

@ -17,7 +17,10 @@
#include <cmath>
#include <fmt/core.h>
#include <fmt/format.h>
#include <LayoutItem.h>
#include <TranslationUtils.h>
#include <SDL.h>
#include <cats.hpp>
#include "icons.h"
#include "utils.h"
#include "aboutwindow.h"
@ -60,6 +63,7 @@ void HaikuLooperWindow::UpdateViewFlags(BLayout *layout) {
}
UpdateViewFlags(owner);
}
HaikuLooperWindow::HaikuLooperWindow(Playback *playback) : BWindow(BRect(100, 100, 500, 100), "Looper", B_TITLED_WINDOW, 0) {
pause_bitmap = load_icon(ICON_PAUSE);
resume_bitmap = load_icon(ICON_PLAY);
@ -93,7 +97,7 @@ HaikuLooperWindow::HaikuLooperWindow(Playback *playback) : BWindow(BRect(100, 10
menu_bar->AddItem(file_menu);
menu_bar->AddItem(help_menu);
layout->AddView(menu_bar);
BView *spacer = new BView("spacer", B_SUPPORTS_LAYOUT|B_FRAME_EVENTS);
spacer = new LImageView("spacer", NULL, BAlignment(B_ALIGN_RIGHT, B_ALIGN_BOTTOM));
spacer->SetExplicitPreferredSize(BSize(0, 0));
spacer->SetExplicitMinSize(BSize(0, 0));
layout->AddView(spacer);
@ -107,10 +111,22 @@ HaikuLooperWindow::HaikuLooperWindow(Playback *playback) : BWindow(BRect(100, 10
restart_btn = new BButton(NULL, new BMessage(CMD_RESTART));
restart_btn->SetTarget(this);
top_row->AddChild(restart_btn);
BView *slider_parent = new BView("main:slider_parent", B_SUPPORTS_LAYOUT);
BGroupLayout *slider_parent_layout = new BGroupLayout(B_HORIZONTAL, 0);
slider_parent_layout->SetInsets(0);
slider_parent->SetLayout(slider_parent_layout);
slider = new BSlider("seek_slider", "Seek", make_slider_msg(CMD_SEEK, false), 0, INT32_MAX, B_HORIZONTAL);
slider->SetModificationMessage(make_slider_msg(CMD_SEEK, true));
slider->SetTarget(this);
top_row->AddChild(slider);
auto *slider_item = slider_parent_layout->AddView(slider);
slider_item->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT));
position_label = new BStringView("main:position_label", "Stopped");
auto *pos_label_item = slider_parent_layout->AddView(position_label);
pos_label_item->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_VERTICAL_CENTER));
position_label->Show();
auto *slider_parent_item = top_row->GroupLayout()->AddView(slider_parent);
slider_parent_item->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT));
slider->Hide();
stop_btn = new BButton(NULL, new BMessage(CMD_STOP));
stop_btn->SetTarget(this);
top_row->AddChild(stop_btn);
@ -148,6 +164,32 @@ HaikuLooperWindow::HaikuLooperWindow(Playback *playback) : BWindow(BRect(100, 10
MessageReceived(msg);
delete msg;
}
void HaikuLooperWindow::UpdateCat(BBitmap *cat) {
LockLooper();
if (cat != NULL) {
auto bounds = cat->Bounds();
auto w = bounds.Width(), h = bounds.Height();
auto sb = spacer->Bounds();
auto sw = sb.Width(), sh = sb.Height();
BRect src(0, 0, w, h);
if (w > sw || h > sh) {
float aspect = w / h;
if (w > sw) {
w = sw;
h = sw / aspect;
}
if (h > sh) {
h = sh;
w = sh * aspect;
}
}
BRect dst(sw - w, sh - h, w, h);
spacer->SetBitmap(cat);
} else {
spacer->SetBitmap(NULL);
}
UnlockLooper();
}
HaikuLooperWindow::~HaikuLooperWindow() {
delete ref_handler;
delete pause_bitmap;
@ -232,6 +274,13 @@ void HaikuLooperWindow::MessageReceived(BMessage *msg) {
case CMD_PREFS: {
prefs_subwindow->Show();
} break;
case CMD_SET_CAT: {
BBitmap *cat = NULL;
if (msg->FindPointer("cat", (void**)&cat) != B_OK) {
cat = NULL;
}
UpdateCat(cat);
}
case CMD_UPDATE_LABEL_SETTING: {
volume_slider->MessageReceived(msg);
speed_slider->MessageReceived(msg);
@ -284,18 +333,29 @@ void HaikuLooperWindow::Pulse() {
auto len = playback->GetLength();
auto pos = playback->GetPosition();
auto pos_milliseconds = pos * 1000.0;
int32_t pos_int32 = (int32_t)((((int64_t)pos_milliseconds) * INT32_MAX) / 1000 / len);
int64_t pos_int64 = (((int64_t)pos_milliseconds) * INT32_MAX) / 1000 / len;
if (pos_int64 > INT32_MAX) pos_int64 = INT32_MAX;
int32_t pos_int32 = (int32_t)pos_int64;
if (!seek_clicked) slider->SetValue(pos_int32);
auto component_count = TimeToComponentCount(len);
bool enable_ui = !playback->IsStopped();
if (enable_ui) {
slider->SetLabel(fmt::format("Position: {}", TimeToString(pos, component_count)).c_str());
slider->SetLimitLabels(TimeToString(0, component_count).c_str(), TimeToString(len, component_count).c_str());
if (len <= 0.0) {
position_label->SetText(fmt::format("Position: {} units", (int)pos).c_str());
slider->Hide();
position_label->Show();
} else {
slider->SetLabel(fmt::format("Position: {}", TimeToString(pos, component_count)).c_str());
slider->SetLimitLabels(TimeToString(0, component_count).c_str(), TimeToString(len, component_count).c_str());
position_label->Hide();
slider->Show();
}
if (show_icons) pause_resume_btn->SetIcon(playback->IsPaused() ? resume_bitmap : pause_bitmap);
if (show_labels) pause_resume_btn->SetLabel(playback->IsPaused() ? "Resume" : "Pause");
} else {
slider->SetLabel("Position");
slider->SetLimitLabels("N/A", "N/A");
position_label->SetText("Stopped.");
slider->Hide();
position_label->Show();
if (show_icons) pause_resume_btn->SetIcon(pause_bitmap);
if (show_labels) pause_resume_btn->SetLabel("Pause");
}

View file

@ -3,6 +3,7 @@
#include <Application.h>
#include <GroupLayout.h>
#include <Slider.h>
#include <StringView.h>
#include <playback.h>
#include <thread>
#include <FilePanel.h>
@ -13,6 +14,7 @@
#include "aboutwindow.h"
#include "slider.h"
#include "prefs.h"
#include "image_view.h"
#include <atomic>
#include <util.hpp>
#define CMD_UPDATE_LABEL_SETTING 0x1000
@ -42,6 +44,7 @@ class Subwindow {
class HaikuLooperRefHandler;
class HaikuLooperWindow : public BWindow {
BSlider *slider;
BStringView *position_label;
BBitmap *pause_bitmap;
BBitmap *resume_bitmap;
BBitmap *refresh_bitmap;
@ -57,6 +60,8 @@ class HaikuLooperWindow : public BWindow {
BButton *restart_btn;
BButton *stop_btn;
BButton *pause_resume_btn;
LImageView *spacer;
void UpdateCat(BBitmap *cat);
bool volume_clicked = false;
bool speed_clicked = false;
bool pitch_clicked = false;

View file

@ -7,16 +7,50 @@
#include <Box.h>
#include <map>
#include "main_window.h"
#include <cats.hpp>
#include <TranslationUtils.h>
#include "image_view.h"
using namespace Looper::Options;
#define CMD_UPDATE_LABEL_SETTING 0x1000
#define CMD_FRONTEND 0x1001
#define CMD_SET_SETTING 0x1002
#define CMD_REVERT 0x1003
#define CMD_APPLY 0x1004
#define CMD_SET_SETTING_CHECKBOX 0x1005
bool show_icons, show_labels;
void HaikuPrefsWindow::AddCat(std::string name, BBitmap *bitmap) {
if (bitmap == NULL) return;
if (cats.contains(name)) {
delete cats[name];
}
cats[name] = bitmap;
}
BBitmap *HaikuPrefsWindow::LoadCat(const char *path) {
return BTranslationUtils::GetBitmap(path);
}
BBitmap *HaikuPrefsWindow::LoadCat(const void *ptr, size_t len, const char *name) {
BMemoryIO *io = new BMemoryIO(ptr, len);
BBitmap *output = BTranslationUtils::GetBitmap(io);
delete io;
return output;
}
HaikuPrefsWindow::HaikuPrefsWindow(BLooper *next_handler) : BWindow(BRect(100, 100, 0, 0), "Preferences", B_TITLED_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS) {
this->next_handler = next_handler;
for (auto &cat : get_cat_data()) {
switch (cat.get_type()) {
case CatDataType::Memory: {
MemoryCat mem_cat = cat.get_memory_cat();
AddCat(cat.get_name(), LoadCat(mem_cat.get_ptr(), mem_cat.get_len(), cat.get_path().c_str()));
} break;
case CatDataType::File: {
fs::path cat_path = cat.get_path();
AddCat(cat.get_name(), LoadCat(cat_path.c_str()));
} break;
}
}
auto *root_layout = new BGroupLayout(B_VERTICAL);
SetLayout(root_layout);
root_layout->SetSpacing(0.0);
@ -40,6 +74,7 @@ HaikuPrefsWindow::HaikuPrefsWindow(BLooper *next_handler) : BWindow(BRect(100, 1
BBox *box = new BBox("prefs:label_settings_box");
box->SetLabel("Labels and Icons");
auto *label_settings_group = new BGroupView(B_VERTICAL);
box->AddChild(label_settings_group);
BGroupLayout *label_settings_layout = label_settings_group->GroupLayout();
BMessage *labels_only_msg = new BMessage(CMD_SET_SETTING);
labels_only_msg->AddString("pref_path", "ui.haiku.label_setting");
@ -56,8 +91,49 @@ HaikuPrefsWindow::HaikuPrefsWindow(BLooper *next_handler) : BWindow(BRect(100, 1
label_settings_layout->AddView(labels_only);
label_settings_layout->AddView(icons_only);
label_settings_layout->AddView(both_labels_icons);
box->AddChild(label_settings_group);
BMessage *cat_enable_msg = new BMessage(CMD_SET_SETTING_CHECKBOX);
cat_enable_msg->AddString("pref_path", "ui.enable_cat");
cat_enable = new BCheckBox("Enable Cat", cat_enable_msg);
BGroupView *cat_enable_view = new BGroupView(B_HORIZONTAL);
cat_enable_view->AddChild(cat_enable);
BBox *cat_box = new BBox("prefs:cat_options");
cat_box->SetLabel("Cat Options");
BGroupView *cat_group = new BGroupView(B_VERTICAL);
BGroupLayout *cat_layout = cat_group->GroupLayout();
for (auto &kv : cats) {
BMessage *msg = new BMessage(CMD_SET_SETTING);
msg->AddString("pref_path", "ui.cat");
msg->AddString("pref_value", kv.first.c_str());
BGroupView *cat_row = new BGroupView(B_HORIZONTAL);
BRadioButton *cat_radio = new BRadioButton(kv.first.c_str(), msg);
cat_btns[kv.first] = cat_radio;
BBitmap *cat_bmp = kv.second;
BRect src = cat_bmp->Bounds();
float w = src.Width(), h = src.Height();
float rw = cat_radio->Bounds().Width(), rh = cat_radio->Bounds().Height();
if (w > rw || h > rh) {
float aspect = w / h;
if (h > rh) {
h = rh;
w = h * aspect;
}
if (w > rw) {
w = rw;
h = h / aspect;
}
}
BRect dst(rw - w, (rh - h) / 2, rw, rh);
LImageView *img_view = new LImageView("prefs:cat:preview", cat_bmp, BAlignment(B_ALIGN_RIGHT, B_ALIGN_BOTTOM));
img_view->SetExplicitMinSize(BSize(16, 16));
img_view->SetExplicitPreferredSize(BSize(16, 16));
cat_row->AddChild(cat_radio);
cat_row->AddChild(img_view);
cat_layout->AddView(cat_row);
}
root_layout->AddView(box, 3.0);
cat_box->AddChild(cat_group);
root_layout->AddView(cat_enable_view, 1.0);
root_layout->AddView(cat_box, 3.0);
BGroupView *btn_view = new BGroupView(B_HORIZONTAL);
BGroupLayout *btn_box = btn_view->GroupLayout();
revert_btn = new BButton("prefs:revert", "Revert", new BMessage(CMD_REVERT));
@ -102,10 +178,17 @@ void HaikuPrefsWindow::MessageReceived(BMessage *msg) {
else restart_warning->Hide();
set_option<std::string>("ui.haiku.label_setting", new_label_setting);
update_label_setting();
set_option<std::string>("ui.cat", cat_id);
set_option<bool>("ui.enable_cat", enable_cat);
next_handler->PostMessage(CMD_UPDATE_LABEL_SETTING);
BMessage *cat_msg = new BMessage(CMD_SET_CAT);
if (enable_cat) cat_msg->AddPointer("cat", cats[cat_id]);
next_handler->PostMessage(cat_msg);
save_options();
} break;
case CMD_REVERT: {
set_options_changed(false);
load_options();
new_label_setting = get_option<std::string>("ui.haiku.label_setting", "icons");
new_frontend = get_option<std::string>("ui.frontend", "haiku");
if (new_frontend != "haiku") restart_warning->Show();
@ -117,8 +200,16 @@ void HaikuPrefsWindow::MessageReceived(BMessage *msg) {
break;
}
}
if (cat_btns.contains(cat_id)) cat_btns[cat_id]->SetValue(B_CONTROL_OFF);
cat_id = get_option<std::string>("ui.cat", cats.empty() ? "" : cats.begin()->first);
if (cat_btns.contains(cat_id)) cat_btns[cat_id]->SetValue(B_CONTROL_ON);
enable_cat = get_option<bool>("ui.enable_cat", false);
cat_enable->SetValue(enable_cat ? B_CONTROL_ON : B_CONTROL_OFF);
update_label_setting();
next_handler->PostMessage(CMD_UPDATE_LABEL_SETTING);
BMessage *cat_msg = new BMessage(CMD_SET_CAT);
if (enable_cat) cat_msg->AddPointer("cat", cats[cat_id]);
next_handler->PostMessage(cat_msg);
} break;
case CMD_FRONTEND: {
auto option = msg->GetInt32("be:value", cur_option);
@ -136,6 +227,23 @@ void HaikuPrefsWindow::MessageReceived(BMessage *msg) {
if (std::string(setting) == "ui.haiku.label_setting") {
new_label_setting = setting_value;
set_options_changed(true);
} else if (std::string(setting) == "ui.cat") {
if (cat_btns.contains(cat_id)) cat_btns[cat_id]->SetValue(B_CONTROL_OFF);
cat_id = setting_value;
if (cat_btns.contains(cat_id)) cat_btns[cat_id]->SetValue(B_CONTROL_ON);
set_options_changed(true);
}
}
}
} break;
case CMD_SET_SETTING_CHECKBOX: {
const char *setting;
if (msg->FindString("pref_path", &setting) == B_OK) {
int32 setting_value_int32;
if (msg->FindInt32("be:value", &setting_value_int32) == B_OK) {
if (std::string(setting) == "ui.enable_cat") {
enable_cat = setting_value_int32 == B_CONTROL_ON;
set_options_changed(true);
}
}
}

View file

@ -13,8 +13,17 @@
#include <vector>
#include <string>
#include <map>
#define CMD_SET_CAT 0x1010
class HaikuPrefsWindow : public BWindow {
std::vector<std::string> backend_ids;
std::string cat_id;
bool enable_cat;
std::map<std::string, BBitmap*> cats;
std::map<std::string, BRadioButton*> cat_btns;
BCheckBox *cat_enable;
void AddCat(std::string name, BBitmap* img);
BBitmap *LoadCat(const char *path);
BBitmap *LoadCat(const void *ptr, size_t len, const char *name);
int32 cur_option = 0;
BLooper *next_handler;
BStringView *restart_warning;

View file

@ -17,6 +17,8 @@
#include <log.hpp>
#include <options.hpp>
#include <web_functions.hpp>
#include <fmt/core.h>
#include <fmt/format.h>
using std::vector;
using namespace Looper::Options;
void RendererBackend::on_resize() {
@ -151,13 +153,23 @@ void RendererBackend::LoopFunction() {
ImGui::NewFrame();
// Run the GUI
GuiFunction();
if (!main_menu_bar_used) {
BeginMainMenuBar();
EndMainMenuBar();
}
// Rendering
ImGui::Render();
SDL_SetRenderDrawColor(rend, (Uint8)(clear_color.x * clear_color.w * 255), (Uint8)(clear_color.y * clear_color.w * 255), (Uint8)(clear_color.z * clear_color.w * 255), (Uint8)(clear_color.w * 255));
SDL_RenderClear(rend);
// Tell ImGui to render.
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), rend);
SDL_Rect rect;
SDL_GetWindowSize(window, &rect.w, &rect.h);
rect.x = 0;
rect.y = 0;
ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Border];
SDL_SetRenderDrawColor(rend, color.x * 255, color.y * 255, color.z * 255, color.w * 255);
SDL_RenderDrawRect(rend, &rect);
// Swap the buffers, and do VSync if enabled.
SDL_RenderPresent(rend);
@ -212,7 +224,8 @@ RendererBackend::~RendererBackend() {
renderer_backend = nullptr;
}
void RendererBackend::SetWindowTitle(const char *title) {
SDL_SetWindowTitle(window, title);
this->title_text = title;
update_real_title();
}
void RendererBackend::GuiFunction() {
// Do nothing by default.
@ -291,6 +304,10 @@ static EM_BOOL resize_callback(int event_type, const EmscriptenUiEvent *event, v
return EM_FALSE;
}
#endif
static SDL_HitTestResult hit_test(SDL_Window *window, const SDL_Point *area, void *data) {
return ((RendererBackend*)data)->HitTest(window, area);
}
void RendererBackend::BackendInit() {
setup_locale("neko_player");
DEBUG.writefln("Loaded locale '%s' from '%s'...", CURRENT_LANGUAGE, LOCALE_DIR);
@ -319,7 +336,7 @@ void RendererBackend::BackendInit() {
#ifdef SDL_HINT_IME_SHOW_UI
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
#endif
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN | SDL_WINDOW_BORDERLESS);
SDL_CreateWindowAndRenderer(window_width, window_height, window_flags, &window, &rend);
#ifndef __ANDROID__
@ -329,8 +346,8 @@ void RendererBackend::BackendInit() {
}
#endif
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
const vector<unsigned char> icon_data = DecodeBase85(icon_compressed_data_base85);
SDL_Surface* icon = IMG_Load_RW(SDL_RWFromConstMem(icon_data.data(), icon_data.size()), 1);
SDL_Surface* icon = IMG_Load_RW(SDL_RWFromConstMem(icon_data, icon_size), 1);
icon_texture = SDL_CreateTextureFromSurface(rend, icon);
SDL_SetWindowIcon(window, icon);
// Setup Dear ImGui context
@ -390,10 +407,139 @@ void RendererBackend::BackendInit() {
SDL_RenderSetVSync(rend, vsync ? 1 : 0);
#endif
theme->Apply(accent_color, (float)scale);
SDL_SetWindowHitTest(window, &hit_test, this);
Init();
SDL_ShowWindow(window);
started = true;
}
bool RendererBackend::UsingSystemTitlebar() {
return enable_system_title_bar;
}
void RendererBackend::EnableSystemTitlebar(bool enabled) {
enable_system_title_bar = enabled;
SDL_SetWindowBordered(window, enable_system_title_bar ? SDL_TRUE : SDL_FALSE);
}
bool RendererBackend::BeginMainMenuBar() {
main_menu_bar_used = true;
if (ImGui::BeginMainMenuBar()) {
if (!enable_system_title_bar) {
ImVec2 winsize = ImGui::GetWindowSize();
float texsize = winsize.y;
ImGui::SetCursorPosX(0);
ImGui::SetCursorPosY(0);
ImGui::Image((ImTextureID)(icon_texture), ImVec2(texsize, texsize));
ImGui::TextUnformatted(title_text.c_str());
}
menubar_start = ImGui::GetCursorPosX();
return true;
} else {
return false;
}
}
bool RendererBackend::is_fullscreen() {
return SDL_GetWindowFlags(window) & (SDL_WINDOW_FULLSCREEN|SDL_WINDOW_FULLSCREEN_DESKTOP);
}
bool RendererBackend::is_maximized() {
return SDL_GetWindowFlags(window) & SDL_WINDOW_MAXIMIZED;
}
SDL_HitTestResult RendererBackend::HitTest(SDL_Window *window, const SDL_Point *area) {
int w, h;
bool rtop, rbottom, rleft, rright;
if (enable_system_title_bar) return SDL_HITTEST_NORMAL;
SDL_GetWindowSize(window, &w, &h);
rtop = area->y <= 4;
rleft = area->x <= 4;
rright = area->x >= w-5;
rbottom = area->y >= h-5;
if (is_fullscreen()) return SDL_HITTEST_NORMAL;
if (!is_maximized()) {
if (rtop) {
if (rright) {
return SDL_HITTEST_RESIZE_TOPRIGHT;
} else if (rleft) {
return SDL_HITTEST_RESIZE_TOPLEFT;
} else {
return SDL_HITTEST_RESIZE_TOP;
}
} else if (rbottom) {
if (rright) {
return SDL_HITTEST_RESIZE_BOTTOMRIGHT;
} else if (rleft) {
return SDL_HITTEST_RESIZE_BOTTOMLEFT;
} else {
return SDL_HITTEST_RESIZE_BOTTOM;
}
} else if (rleft) {
return SDL_HITTEST_RESIZE_LEFT;
} else if (rright) {
return SDL_HITTEST_RESIZE_RIGHT;
}
}
if (area->y < (16 * this->scale) && (area->x < menubar_start || (area->x > menubar_end && area->x < title_btn_start))) {
return SDL_HITTEST_DRAGGABLE;
} else {
return SDL_HITTEST_NORMAL;
}
}
void RendererBackend::SetSubtitle(const char *subtitle) {
this->subtitle = subtitle;
update_real_title();
}
void RendererBackend::update_real_title() {
if (subtitle == "") {
SDL_SetWindowTitle(window, title_text.c_str());
} else {
SDL_SetWindowTitle(window, fmt::format("{} - {}", subtitle, title_text).c_str());
}
}
void RendererBackend::EndMainMenuBar() {
#ifndef __EMSCRIPTEN__
if (!enable_system_title_bar) {
menubar_end = ImGui::GetCursorPosX();
ImVec2 size = ImGui::GetWindowSize();
if (subtitle != "") {
ImVec4 text_color = Theme::GetColor(LooperCol_Subtitle);
ImGui::PushStyleColor(ImGuiCol_Text, text_color);
ImGui::TextUnformatted(subtitle.c_str());
ImGui::PopStyleColor();
}
float btnw = size.y;
int btn_count = 3;
ImVec2 btn_size(btnw, btnw);
ImGui::SetCursorPosY(0);
static const size_t titlebar_btn_count = 3;
const char *titlebar_icons[titlebar_btn_count] = {
ICON_FK_WINDOW_MINIMIZE,
is_maximized() ? ICON_FK_WINDOW_RESTORE : ICON_FK_WINDOW_MAXIMIZE,
ICON_FK_WINDOW_CLOSE
};
float tmp = size.x;
float padding = ImGui::GetStyle().FramePadding.x;
float spacing = ImGui::GetStyle().ItemSpacing.x;
for (size_t i = 0; i < titlebar_btn_count; i++) {
tmp -= padding * 2.0f;
// No need to use the correct index, as long as this is hit only once.
if (i == 0) tmp -= spacing / 2.0f;
else tmp -= spacing;
tmp -= ImGui::CalcTextSize(titlebar_icons[i]).x;
}
title_btn_start = std::ceil(tmp);
ImGui::SetCursorPosX(title_btn_start);
if (ImGui::MenuItem(titlebar_icons[0])) {
SDL_MinimizeWindow(window);
}
if (ImGui::MenuItem(titlebar_icons[1])) {
if (is_maximized()) SDL_RestoreWindow(window);
else SDL_MaximizeWindow(window);
}
if (ImGui::MenuItem(titlebar_icons[2])) {
done = true;
}
}
#endif
ImGui::EndMainMenuBar();
}
int RendererBackend::Run() {
framerate = 60;
started = false;
@ -403,7 +549,8 @@ int RendererBackend::Run() {
#else
try {
BackendInit();
} catch (std::exception) {
} catch (std::exception &e) {
ERROR.writefln("Error occurred during initialization: %s", e.what());
return -1;
}
started = true;

View file

@ -25,7 +25,20 @@ class RendererBackend {
bool resize_needed = true;
void on_resize();
bool update_scale = false;
SDL_Texture *icon_texture;
bool main_menu_bar_used = false;
int menubar_start;
int menubar_end;
int title_btn_start;
std::string subtitle;
std::string title_text;
bool enable_system_title_bar;
void update_real_title();
public:
bool is_fullscreen();
bool is_maximized();
int window_border_radius = 8;
SDL_HitTestResult HitTest(SDL_Window *window, const SDL_Point *area);
std::optional<bool> touchScreenModeOverride;
std::optional<double> scaleOverride;
bool isTouchScreenMode();
@ -46,7 +59,13 @@ class RendererBackend {
const char *prefPath;
ImVec4 accent_color = ImVec4(0.75, 1.0, 1.0, 1.0);
int Run();
void SetSubtitle(const char *subtitle);
inline void ClearSubtitle() {
SetSubtitle("");
}
void SetWindowTitle(const char *title);
bool UsingSystemTitlebar();
void EnableSystemTitlebar(bool enabled);
virtual void Init();
virtual void GuiFunction();
virtual void Deinit();
@ -57,6 +76,8 @@ class RendererBackend {
void AddFonts();
void SetWindowSize(int w, int h);
void GetWindowsize(int *w, int *h);
bool BeginMainMenuBar();
void EndMainMenuBar();
RendererBackend();
virtual ~RendererBackend();
friend void main_loop();

View file

@ -7,7 +7,9 @@
#include "imgui/imgui.h"
#include "ui_backend.hpp"
#include "thirdparty/CLI11.hpp"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include <web_functions.hpp>
#include <cats.hpp>
using namespace Looper::Options;
void MainLoop::Init() {
#ifdef PORTALS
@ -27,6 +29,25 @@ void MainLoop::Init() {
theme_editor = false;
stopped = true;
about_window = false;
for (auto &cat : get_cat_data()) {
switch (cat.get_type()) {
case CatDataType::Memory: {
auto mem_cat = cat.get_memory_cat();
AddCat(cat.get_name(), LoadCatFromMemory(mem_cat.get_ptr(), mem_cat.get_len(), cat.get_path().c_str()));
} break;
case CatDataType::File: {
fs::path cat_path = cat.get_path();
try {
AddCat(cat.get_name(), LoadCat(cat_path.string()));
} catch (std::exception &e) {
WARNING.writefln("Failed to load cat %s at path %s: %s", cat_path.c_str(), cat.get_name().c_str(), e.what());
}
} break;
default: {
WARNING.writefln("Invalid cat type with numerical value: %d", cat.get_type());
} break;
}
}
string lang;
{
Json::Value config;
@ -102,6 +123,11 @@ void MainLoop::Init() {
accent_color.w = (float)get_option<double>("ui.imgui.accent_color.a", accent_color.w);
debug_mode = get_option<bool>("ui.imgui.debug_mode", false);
}
{
enable_cat = get_option<bool>("ui.enable_cat", false);
cat_setting = get_option<std::string>("ui.cat", cats.empty() ? "" : cats.begin()->first);
if (cats.contains(cat_setting)) cat = cats[cat_setting];
}
Theme::updateAvailableThemes();
if (Theme::availableThemes.empty()) {
path lightPath = Theme::themeDir / "light.toml";
@ -127,24 +153,87 @@ void MainLoop::Init() {
theme = new Theme(darkPath);
}
}
EnableSystemTitlebar(get_option<bool>("ui.imgui.enable_system_titlebar", false));
theme->Apply(accent_color, (float)scale);
SetWindowTitle("Looper");
FileLoaded();
}
void MainLoop::Drop(std::string file) {
LoadFile(file);
}
SDL_Texture *MainLoop::LoadCat(File *file) {
size_t pos = file->get_pos();
file->seek(0, SeekType::SET);
std::string fname = file->name;
SDL_RWops *rwops = get_sdl_file(file);
const char *ext = std::filesystem::path(fname).extension().c_str();
DEBUG.writefln("Extension: %s\n", ext);
if (ext[0] == '.') ext = ext + 1;
SDL_Texture *tex = IMG_LoadTextureTyped_RW(rend, rwops, 0, ext);
delete rwops;
if (!tex) throw CustomException(fmt::format("Failed to load cat {}: {}", fname, IMG_GetError()));
file->seek(pos, SeekType::SET);
return tex;
}
SDL_Texture *MainLoop::LoadCat(std::string path) {
std::string fname = path;
SDL_RWops *rwops = SDL_RWFromFile(path.c_str(), "rb");
SDL_Texture *tex = IMG_LoadTexture_RW(rend, rwops, 1);
if (!tex) throw CustomException(fmt::format("Failed to load cat {}: {}", fname, IMG_GetError()));
return tex;
}
SDL_Texture *MainLoop::LoadCatFromMemory(const void *ptr, size_t len, const char *name) {
std::string fname = name;
SDL_RWops *rwops = SDL_RWFromConstMem(ptr, len);
SDL_Texture *tex = IMG_LoadTexture_RW(rend, rwops, 1);
if (!tex) throw CustomException(fmt::format("Failed to load cat {}: {}", fname, IMG_GetError()));
return tex;
}
void MainLoop::AddCat(std::string name, SDL_Texture *tex) {
if (cats.contains(name)) {
SDL_DestroyTexture(cats[name]);
}
cats[name] = tex;
}
void MainLoop::FileLoaded() {
auto file_maybe = playback->get_current_title();
if (file_maybe.has_value()) {
auto name = file_maybe.value();
SetWindowTitle((name + std::string(" - Looper")).c_str());
SetSubtitle(name.c_str());
} else {
SetWindowTitle("Looper");
ClearSubtitle();
}
streams = playback->get_streams();
properties = playback->get_property_list();
boolean_properties.clear();
double_properties.clear();
int_properties.clear();
string_properties.clear();
for (auto &property : properties) {
PropertyType type = property.type();
auto name = property.path();
auto val_maybe = playback->get_property(name);
if (!val_maybe.has_value()) continue;
auto val = val_maybe.value();
switch (type) {
case PropertyType::Int: {
int_properties[name] = resolve_value<int>(val);
} break;
case PropertyType::Boolean: {
boolean_properties[name] = resolve_value<bool>(val);
} break;
case PropertyType::String: {
string_properties[name] = resolve_value<std::string>(val);
} break;
case PropertyType::Double: {
double_properties[name] = resolve_value<double>(val);
} break;
}
}
}
void MainLoop::GuiFunction() {
#if defined(__EMSCRIPTEN__)||defined(__ANDROID__)
@ -161,7 +250,7 @@ void MainLoop::GuiFunction() {
// 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).
if (show_demo_window)
ImGui::ShowDemoWindow(&show_demo_window);
if (ImGui::BeginMainMenuBar()) {
if (BeginMainMenuBar()) {
if (ImGui::BeginMenu(_TRI_CTX(ICON_FK_FILE, "Main menu", "File"))) {
if (ImGui::MenuItem(_TRI_CTX(ICON_FK_FOLDER_OPEN, "Main menu | File", "Open"))) {
// Set translatable strings here so that they are in the correct language even when it changes at runtime.
@ -210,11 +299,24 @@ void MainLoop::GuiFunction() {
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
EndMainMenuBar();
}
ImGui::SetNextWindowDockID(dockid);
ImGui::Begin(_TRI_CTX(ICON_FK_PLAY, "Main window title", "Player"), nullptr, 0);
{
ImGui::SetCursorPosY(0);
float y_pos = ImGui::GetWindowHeight() - ImGui::GetFrameHeightWithSpacing() - ImGui::GetFrameHeight() - ImGui::GetStyle().WindowPadding.y;
if (enable_cat && cat) {
int cw, ch;
SDL_QueryTexture(cat, NULL, NULL, &cw, &ch);
float aspect = ((float)cw) / ((float)ch);
float y_size = std::min(y_pos, (float)ch);
float x_size = y_size * aspect;
float x_pos = ImGui::GetWindowWidth() - ImGui::GetStyle().WindowPadding.x - x_size;
ImGui::SetCursorPosX(x_pos);
ImGui::SetCursorPosY(y_pos - y_size);
ImGui::Image((ImTextureID)cat, ImVec2(x_size, y_size));
}
float centerSpace = ImGui::GetWindowHeight() - ImGui::GetFrameHeightWithSpacing() - ImGui::GetFrameHeight() - ImGui::GetStyle().WindowPadding.y;
if (streams.size() > 0) {
static string filter = "";
@ -254,7 +356,7 @@ void MainLoop::GuiFunction() {
}
ImGui::EndChildFrame();
}
ImGui::SetCursorPosY(ImGui::GetWindowHeight() - ImGui::GetFrameHeightWithSpacing() - ImGui::GetFrameHeight() - ImGui::GetStyle().WindowPadding.y);
ImGui::SetCursorPosY(y_pos);
if (ImGui::Button(playback->IsPaused() ? ICON_FK_PLAY "##Pause" : ICON_FK_PAUSE "##Pause")) {
playback->Pause();
}
@ -312,6 +414,58 @@ void MainLoop::GuiFunction() {
ImGui::PushID(property.path().c_str());
bool valid = false;
switch (property.type()) {
case PropertyType::String: {
std::string value = "";
if (string_properties.contains(property.path())) {
value = string_properties[property.path()];
} else {
auto value_to_resolve = playback->get_property(property.path());
if (value_to_resolve.has_value()) {
value = resolve_value<std::string>(value_to_resolve.value());
}
string_properties[property.path()] = value;
}
ImGui::InputText(property.path().c_str(), &value);
string_properties[property.path()] = value;
valid = true;
} break;
case PropertyType::Int: {
std::optional<double> min;
std::optional<double> max;
int value = 0;
if (int_properties.contains(property.path())) {
value = int_properties[property.path()];
} else {
auto value_to_resolve = playback->get_property(property.path());
if (value_to_resolve.has_value()) {
value = resolve_value<int>(value_to_resolve.value());
}
int_properties[property.path()] = value;
}
if (property.has_hint() && property.hint().has_range()) {
auto range = property.hint().range();
if (range.has_min() && range.has_max()) {
if (ImGui::SliderInt(property.path().c_str(), &value, (int)range.min(), (int)range.max())) {
int_properties[property.path()] = value;
}
valid = true;
} else {
if (range.has_min()) min = range.min();
if (range.has_max()) max = range.max();
}
}
if (!valid) {
ImGui::InputInt(property.path().c_str(), &value);
if (min.has_value() && value < min) {
value = min.value();
}
if (max.has_value() && value > max) {
value = max.value();
}
int_properties[property.path()] = value;
valid = true;
}
} break;
case PropertyType::Double: {
std::optional<double> min;
std::optional<double> max;
@ -371,6 +525,20 @@ void MainLoop::GuiFunction() {
ImGui::SameLine();
if (ImGui::Button("Set")) {
switch (property.type()) {
case PropertyType::String: {
StringProperty property_s;
property_s.set_value(string_properties[property.path()]);
google::protobuf::Any value;
value.PackFrom(property_s);
playback->set_property(property.path(), value);
} break;
case PropertyType::Int: {
IntProperty property_i;
property_i.set_value(int_properties[property.path()]);
google::protobuf::Any value;
value.PackFrom(property_i);
playback->set_property(property.path(), value);
} break;
case PropertyType::Double: {
DoubleProperty property_d;
property_d.set_value(double_properties[property.path()]);
@ -429,6 +597,11 @@ void MainLoop::GuiFunction() {
if (ImGui::Checkbox(_TR_CTX("Preference | Debug menu enable", "Enable debug menu in release builds"), &debug_mode)) {
set_option<bool>("ui.imgui.debug_mode", debug_mode);
}
bool tmp_enable_system_title_bar = UsingSystemTitlebar();
if (ImGui::Checkbox(_TR_CTX("Preference | System title bar", "Enable system title bar"), &tmp_enable_system_title_bar)) {
EnableSystemTitlebar(tmp_enable_system_title_bar);
set_option<bool>("ui.imgui.enable_system_titlebar", tmp_enable_system_title_bar);
}
if (ImGui::Button(_TRI_CTX(ICON_FK_MAGIC, "Preference | Related non-preference button", "Theme Editor"), ImVec2(ImGui::GetWindowWidth() - (ImGui::GetStyle().WindowPadding.x * 2.0f), 0))) {
theme_editor = true;
}
@ -450,6 +623,54 @@ void MainLoop::GuiFunction() {
set_option<std::string>("ui.imgui.lang", lang);
}
}
static SDL_Texture *disp_cat = nullptr;
if (ImGui::Checkbox(_TR_CTX("Preference | cat enable checkbox", "Enable cat"), &enable_cat)) {
disp_cat = nullptr;
set_option<bool>("ui.enable_cat", enable_cat);
}
if (enable_cat) {
ImVec2 TableSize = ImVec2(0, 0);
if (ImGui::BeginTable("##Cats", 2, ImGuiTableFlags_SizingFixedFit|ImGuiTableFlags_NoSavedSettings|ImGuiTableFlags_ScrollY, TableSize)) {
ImGui::TableSetupColumn("Cat Name", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Preview", 0);
for (auto &kv : cats) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
if (ImGui::Selectable(kv.first.c_str(), kv.first == cat_setting, 0)) {
cat_setting = kv.first;
cat = cats[cat_setting];
set_option<std::string>("ui.cat", cat_setting);
}
ImGui::TableSetColumnIndex(1);
int cw, ch;
SDL_QueryTexture(kv.second, NULL, NULL, &cw, &ch);
float aspect = ((float)cw) / ((float)ch);
bool portrait = ch > cw;
ImVec2 size = ImVec2(16.0f * aspect, 16.0f);
if (ImGui::ImageButton(fmt::format("disp_cat_{}", kv.first).c_str(), (ImTextureID)kv.second, size)) {
if (disp_cat == kv.second) {
disp_cat = nullptr;
} else {
disp_cat = kv.second;
}
}
}
ImGui::EndTable();
}
bool show_cat_preview = disp_cat != nullptr;
if (show_cat_preview && ImGui::Begin("Cat Preview", &show_cat_preview, ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize)) {
int cw, ch;
SDL_QueryTexture(disp_cat, NULL, NULL, &cw, &ch);
ImGui::Image((ImTextureID)disp_cat, ImVec2(cw, ch));
ImGui::End();
}
// Handle window close.
if (!show_cat_preview) {
disp_cat = nullptr;
}
}
if (override_lang) {
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - (ImGui::GetFontSize()) - ((ImGui::GetStyle().ItemSpacing.x + (ImGui::GetStyle().FramePadding.x * 2.0f))) - (ImGui::GetStyle().WindowPadding.x));
@ -562,6 +783,8 @@ void MainLoop::GuiFunction() {
ImGui::SetCursorPosX((ImGui::GetWindowWidth() - ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, 0.0f, VER_STRING.c_str()).x) / 2.0f);
ImGui::TextUnformatted(VER_STRING.c_str());
ImGui::NewLine();
ImGui::Text("SDL video driver: %s", SDL_GetCurrentVideoDriver());
ImGui::NewLine();
auto &license_data = get_license_data();
// Left
static LicenseData selected = *license_data.begin();
@ -622,13 +845,18 @@ void MainLoop::LoadFile(std::string file) {
playback->Start(file);
}
void MainLoop::Deinit() {
for (auto kv : cats) {
SDL_DestroyTexture(kv.second);
}
cats.clear();
{
path themePath(theme->file_path);
themePath = themePath.stem();
if (!themePath.empty()) {
set_option<std::string>("ui.imgui.theme", themePath.string());
}
set_option<bool>("ui.enable_cat", enable_cat);
set_option<std::string>("ui.cat", cat_setting);
set_option<double>("ui.imgui.accent_color.h", accent_color.x);
set_option<double>("ui.imgui.accent_color.s", accent_color.y);
set_option<double>("ui.imgui.accent_color.v", accent_color.z);
@ -664,6 +892,10 @@ void ImGuiUIBackend::QuitHandler() {
// Main code
int ImGuiUIBackend::run(std::vector<std::string> realArgs, int argc, char** argv)
{
SDL_setenv("SDL_VIDEO_X11_WMCLASS", "looper", 1);
if (SDL_getenv("WAYLAND_DISPLAY")) {
SDL_setenv("SDL_VIDEODRIVER", "wayland", 0);
}
int possible_error = UIBackend::run(realArgs, argc, argv);
if (possible_error != 0) {
return possible_error;

View file

@ -49,14 +49,24 @@ class MainLoop : public RendererBackend {
bool property_editor = false;
bool restart_needed = false;
bool stopped = true;
bool enable_cat = false;
std::string cat_setting = "__default__";
std::vector<UIBackend*> backends;
UIBackend *cur_backend;
friend class ImGuiUIBackend;
std::atomic_bool exit_flag;
std::map<std::string, bool> boolean_properties;
std::map<std::string, double> double_properties;
std::map<std::string, int> int_properties;
std::map<std::string, std::string> string_properties;
std::vector<Property> properties;
std::vector<PlaybackStream> streams;
std::map<std::string, SDL_Texture*> cats;
SDL_Texture *cat = nullptr;
SDL_Texture *LoadCatFromMemory(const void *ptr, size_t len, const char *name);
SDL_Texture *LoadCat(File *file);
SDL_Texture *LoadCat(std::string path);
void AddCat(std::string name, SDL_Texture *tex);
public:
Playback *playback;
vector<std::string> args;

View file

@ -20,6 +20,7 @@ const char* Theme::prefPath = NULL;
path Theme::themeDir = path();
std::set<path> Theme::availableThemes = std::set<path>();
std::map<path, ThemeStrings> Theme::themeStrings = std::map<path, ThemeStrings>();
Theme *Theme::cur_theme = nullptr;
ImVec4 change_accent_color(ImVec4 in, float hue) {
if (in.x == in.y && in.y == in.z) {
@ -185,9 +186,11 @@ bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_wid
ImGui::BeginChild("##colors", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened);
ImGui::PushItemWidth(-160);
for (int i = 0; i < ImGuiCol_COUNT; i++)
for (int i = 0; i < (int)ImGuiCol_COUNT + (int)LooperCol_COUNT; i++)
{
const char* name = ImGui::GetStyleColorName(i);
const char* name;
if (i < ImGuiCol_COUNT) name = ImGui::GetStyleColorName(i);
else name = theme->GetStyleColorName(i-ImGuiCol_COUNT);
if (!filter.PassFilter(name))
continue;
ImGui::PushID(i);
@ -197,7 +200,7 @@ bool Theme::ShowEditor(bool* open, Theme* &theme, ImGuiID dockid, int window_wid
ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Saturation", "S: "), &colorizer.Saturation); ImGui::SameLine();
ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Value", "V: "), &colorizer.Value); ImGui::SameLine();
ImGui::Checkbox(_TR_CTX("Theme Editor | Colors | (Any color) | recoloring checkbox | Alpha (opacity)", "A: "), &colorizer.Alpha); ImGui::SameLine();
ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags | ImGuiColorEditFlags_DisplayHSV);
ImGui::ColorEdit4("##color", (float*)&(i < ImGuiCol_COUNT ? style.Colors[i] : theme->Colors[i-ImGuiCol_COUNT]), ImGuiColorEditFlags_AlphaBar | alpha_flags | ImGuiColorEditFlags_DisplayHSV);
ImGui::SameLine(0.0f, style.ItemInnerSpacing.x);
ImGui::TextUnformatted(name);
ImGui::PopID();
@ -373,6 +376,7 @@ void Theme::Apply(ImVec4 accent, float scale) {
actual_style.WindowMinSize.y = MAX(actual_style.WindowMinSize.y, 1.0);
actual_style.CurveTessellationTol = MAX(actual_style.CurveTessellationTol, 0.1);
actual_style.CircleTessellationMaxError = MAX(actual_style.CircleTessellationMaxError, 0.1);
cur_theme = this;
}
void Theme::Save(string path) {
INFO.writefln("Saving theme to %s...", path.c_str());
@ -440,11 +444,18 @@ void Theme::Save(string path) {
}
{
toml::table colors;
for (int i = 0; i < ImGuiCol_COUNT; i++)
for (int i = 0; i < (int)ImGuiCol_COUNT + (int)LooperCol_COUNT; i++)
{
const char* name = ImGui::GetStyleColorName(i);
ImVec4 color = style.Colors[i];
toml::table colorValue;
const char *name;
ImVec4 color;
toml::table colorValue;
if (i >= ImGuiCol_COUNT) {
name = GetStyleColorName(i - ImGuiCol_COUNT);
color = this->Colors[i - ImGuiCol_COUNT];
} else {
name = ImGui::GetStyleColorName(i);
color = style.Colors[i];
}
colorValue.insert("r", color.x);
colorValue.insert("g", color.y);
colorValue.insert("b", color.z);
@ -465,6 +476,20 @@ void Theme::Save(string path) {
}
updateAvailableThemes();
}
ImVec4 Theme::GetColor(int color) {
if (cur_theme == nullptr) {
return ImVec4(255, 0, 255, 0);
} else {
return cur_theme->Colors[color];
}
}
const char *Theme::GetStyleColorName(int color) {
switch (color) {
case LooperCol_Subtitle: return "Subtitle";
}
ERROR.writefln("Invalid looper-specific color ID: %d\n", color);
return "";
}
void Theme::Save(path path) {
Save((string)path.string());
}
@ -535,9 +560,16 @@ Theme::Theme(bool dark) : Theme() {
ImGui::StyleColorsLight(&style);
style.FrameBorderSize = 1;
}
for (int i = 0; i < ImGuiCol_COUNT; i++)
this->Colors[LooperCol_Subtitle] = style.Colors[ImGuiCol_Text];
this->Colors[LooperCol_Subtitle].w *= 0.5;
for (int i = 0; i < (int)ImGuiCol_COUNT + (int)LooperCol_COUNT; i++)
{
ImVec4 color = style.Colors[i];
ImVec4 color;
if (i < ImGuiCol_COUNT) {
color = style.Colors[i];
} else {
color = this->Colors[i-ImGuiCol_COUNT];
}
auto colorizer = AccentColorizer();
if (color.x != color.y || color.y != color.z) {
colorizer.Hue = true;
@ -762,14 +794,27 @@ Theme::Theme(string path) : Theme() {
}
if (config.contains("colors")) {
toml::table colors = *config["colors"].as_table();
for (int i = 0; i < ImGuiCol_COUNT; i++)
for (int i = 0; i < (int)ImGuiCol_COUNT + (int)LooperCol_COUNT; i++)
{
const char* name = ImGui::GetStyleColorName(i);
const char* name;
if (i < ImGuiCol_COUNT) name = ImGui::GetStyleColorName(i);
else name = GetStyleColorName(i-ImGuiCol_COUNT);
if (colors.contains(name)) {
toml::table colorValue = *colors[name].as_table();
ImVec4 color = ImVec4((float)**colorValue["r"].as_floating_point(), (float)**colorValue["g"].as_floating_point(), (float)**colorValue["b"].as_floating_point(), (float)**colorValue["a"].as_floating_point());
AccentColorizers[i] = AccentColorizer(*colorValue["accent"].as_table());
style.Colors[i] = color;
if (i < ImGuiCol_COUNT) style.Colors[i] = color;
else this->Colors[i-ImGuiCol_COUNT] = color;
} else {
WARNING.writefln("Missing color upon load: %s\nThis may have a sensible default, but be sure to check the theme colors!", name);
if (i >= ImGuiCol_COUNT) {
switch (i-ImGuiCol_COUNT) {
case LooperCol_Subtitle: {
this->Colors[LooperCol_Subtitle] = style.Colors[ImGuiCol_Text];
this->Colors[LooperCol_Subtitle].w *= 0.5;
} break;
}
}
}
}
}

View file

@ -17,6 +17,12 @@ struct ThemeStrings {
ThemeStrings();
ThemeStrings(toml::table config);
};
enum LooperCol {
LooperCol_Subtitle,
// LooperCol_Title,
// LooperCol_WindowBorder,
LooperCol_COUNT
};
struct AccentColorizer {
/// @brief Whether or not to change the hue.
@ -51,7 +57,11 @@ class Theme {
static const char* prefPath;
static const int MinSchemaVersion = 0;
static const int MaxSchemaVersion = 1;
static Theme *cur_theme;
ImVec4 Colors[LooperCol_COUNT];
string file_path;
static ImVec4 GetColor(int color);
const char *GetStyleColorName(int color);
std::map<string, ThemeStrings> strings;
std::map<int, AccentColorizer> AccentColorizers;
ThemeStrings GetStrings();

View file

@ -4,8 +4,8 @@ foreach(SRC IN ITEMS ${BACKEND_QT_SRC_BASE})
set(BACKEND_QT_SRC ${BACKEND_QT_SRC} ${CMAKE_CURRENT_SOURCE_DIR}/${SRC})
endforeach()
set(BACKEND_QT_INC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
set(CMAKE_AUTOMOC ON)
add_ui_backend(qt_ui ${BACKEND_QT_SRC})
set_target_properties(qt_ui PROPERTIES AUTOMOC ON)
find_package(Qt6 COMPONENTS Core Gui Widgets)
target_link_libraries(qt_ui PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets SDL2::SDL2 fmt::fmt liblooper)
target_include_directories(qt_ui PRIVATE ../../..)

View file

@ -5,7 +5,7 @@ void LooperWindow::Pulse() {
auto len = playback->GetLength();
auto pos = playback->GetPosition();
this->slider->SetLimits(0.0, len);
if (!this->slider->IsPressed()) this->slider->SetValue(pos);
if (!this->slider->IsPressed()) this->slider->SetValueNoSignal(pos);
auto component_count = TimeToComponentCount(len);
bool enable_ui = !playback->IsStopped();
if (enable_ui) {
@ -24,10 +24,10 @@ void LooperWindow::Pulse() {
auto pitch = playback->GetPitch();
auto speed = playback->GetSpeed();
auto tempo = playback->GetTempo();
if (!volume_slider->IsPressed()) volume_slider->SetValue(volume);
if (!pitch_slider->IsPressed()) pitch_slider->SetValue(pitch);
if (!speed_slider->IsPressed()) speed_slider->SetValue(speed);
if (!tempo_slider->IsPressed()) tempo_slider->SetValue(tempo);
if (!volume_slider->IsPressed()) volume_slider->SetValueNoSignal(volume);
if (!pitch_slider->IsPressed()) pitch_slider->SetValueNoSignal(pitch);
if (!speed_slider->IsPressed()) speed_slider->SetValueNoSignal(speed);
if (!tempo_slider->IsPressed()) tempo_slider->SetValueNoSignal(tempo);
volume_slider->SetLabel(fmt::format("Volume: {}%", (int)volume).c_str());
pitch_slider->SetLabel(fmt::format("Pitch {:.02f}x", pitch).c_str());
speed_slider->SetLabel(fmt::format("Speed: {:.02f}x", speed).c_str());
@ -40,6 +40,9 @@ void LooperWindow::Pulse() {
}
}
}
void LooperWindow::resizeEvent(QResizeEvent *event) {
if (cat_pixmap != NULL) update_cat(*cat_pixmap);
}
LooperWindow::LooperWindow(Playback *playback) : QMainWindow() {
labels_visible = false;
icons_visible = true;
@ -80,10 +83,15 @@ LooperWindow::LooperWindow(Playback *playback) : QMainWindow() {
help_menu->addAction(about_item);
bar->addMenu(file_menu);
bar->addMenu(help_menu);
root_layout->addWidget(bar);
QSpacerItem *spacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
root_layout->addSpacerItem(spacer);
QBoxLayout *top_row = new QBoxLayout(QBoxLayout::LeftToRight, this);
cat_disp = new QLabel();
cat_disp->setAlignment(Qt::Alignment::enum_type::AlignRight);
cat_disp->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
root_layout->addWidget(cat_disp);
QWidget *controls_widget = new QWidget(this);
QBoxLayout *controls_layout = new QBoxLayout(QBoxLayout::TopToBottom, controls_widget);
controls_widget->setLayout(controls_layout);
controls_widget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
QBoxLayout *top_row = new QBoxLayout(QBoxLayout::LeftToRight, controls_widget);
pause_resume_btn = new QPushButton("Pause", this);
QObject::connect(pause_resume_btn, &QPushButton::pressed, [=,this]() {
playback->Pause();
@ -113,8 +121,9 @@ LooperWindow::LooperWindow(Playback *playback) : QMainWindow() {
});
top_row->addWidget(volume_slider);
QWidget *top_row_widget = new QWidget(this);
top_row_widget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
top_row_widget->setLayout(top_row);
root_layout->addWidget(top_row_widget);
controls_layout->addWidget(top_row_widget);
QBoxLayout *bottom_row = new QBoxLayout(QBoxLayout::LeftToRight, this);
speed_slider = new LooperSlider("speed", "Speed", 0.25, 4.0, 0.01, true);
pitch_slider = new LooperSlider("pitch", "Pitch", 0.25, 4.0, 0.01, true);
@ -135,8 +144,10 @@ LooperWindow::LooperWindow(Playback *playback) : QMainWindow() {
bottom_row->addWidget(pitch_slider);
bottom_row->addWidget(tempo_slider);
QWidget *bottom_row_widget = new QWidget(this);
bottom_row_widget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
bottom_row_widget->setLayout(bottom_row);
root_layout->addWidget(bottom_row_widget);
controls_layout->addWidget(bottom_row_widget);
root_layout->addWidget(controls_widget);
QTimer *timer = new QTimer(this);
QObject::connect(timer, &QTimer::timeout, [=,this]() {
Pulse();
@ -146,6 +157,9 @@ LooperWindow::LooperWindow(Playback *playback) : QMainWindow() {
timer->setInterval(1);
timer->start();
QObject::connect(prefs_window, &PrefsWindow::settings_changed, this, &LooperWindow::update_label_setting);
QObject::connect(prefs_window, &PrefsWindow::cat_set, this, &LooperWindow::update_cat);
QObject::connect(prefs_window, &PrefsWindow::cat_unset, this, &LooperWindow::clear_cat);
prefs_window->send_cat_signal();
//setLayout(layout);
}
void LooperWindow::update_label_setting(bool labels_visible, bool icons_visible) {
@ -173,3 +187,28 @@ void LooperWindow::update_label_setting(bool labels_visible, bool icons_visible)
}
}
}
void LooperWindow::clear_cat() {
cat_pixmap = NULL;
cat_disp->clear();
}
void LooperWindow::update_cat(QPixmap &img) {
cat_pixmap = &img;
int w = img.width();
int h = img.height();
int mw = cat_disp->width();
int mh = cat_disp->height();
float wr = ((float)mw) / ((float)w);
float hr = ((float)mh) / ((float)h);
if (wr > 1.0f) wr = 1.0f;
if (hr > 1.0f) hr = 1.0f;
if (wr > hr) {
mw *= wr;
mh *= wr;
} else {
mw *= hr;
mh *= hr;
}
cat_disp->setAlignment(Qt::AlignRight | Qt::AlignBottom);
cat_disp->setPixmap(img.scaled(mw, mh, Qt::KeepAspectRatio));
}

View file

@ -23,6 +23,7 @@ class LooperWindow : public QMainWindow {
LooperSlider *speed_slider;
LooperSlider *tempo_slider;
LooperSlider *pitch_slider;
QPixmap *cat_pixmap = NULL;
std::thread *update_thread = nullptr;
bool done = false;
void Pulse();
@ -41,7 +42,12 @@ class LooperWindow : public QMainWindow {
QAction *about_item;
QFileDialog *file_dialog;
QBoxLayout *root_layout;
QLabel *cat_disp;
void update_label_setting(bool labels_visible, bool icons_visible);
void update_cat(QPixmap &img);
void clear_cat();
protected:
void resizeEvent(QResizeEvent *event) override;
public:
AboutWindow *about_window;
PrefsWindow *prefs_window;

View file

@ -1,10 +1,34 @@
#include "preferences.h"
#include <QMenu>
#include <QBoxLayout>
#include <QBuffer>
#include <QImage>
#include <QDataStream>
#include <QButtonGroup>
#include <QGroupBox>
#include <backend.hpp>
#include <options.hpp>
#include <cats.hpp>
#include <log.hpp>
using namespace Looper::Options;
PrefsWindow::PrefsWindow() {
for (auto &cat : get_cat_data()) {
switch (cat.get_type()) {
case CatDataType::Memory: {
auto mem_cat = cat.get_memory_cat();
QPixmap pixmap;
pixmap.loadFromData((const uchar*)mem_cat.get_ptr(), mem_cat.get_len());
cats[cat.get_name()] = pixmap;
} break;
case CatDataType::File: {
try {
cats[cat.get_name()] = QPixmap(cat.get_path().c_str());
} catch (std::exception e) {
WARNING.writefln("Failed to load cat %s at path %s: %s", cat.get_name().c_str(), cat.get_path().c_str(), e.what());
}
} break;
}
}
auto *root_layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
this->setLayout(root_layout);
restart_warning = new QLabel("A restart is needed to apply some changes.", this);
@ -26,8 +50,7 @@ PrefsWindow::PrefsWindow() {
}
frontend_btn->setMenu(frontend_menu);
root_layout->addWidget(frontend_btn);
QFrame *frame = new QFrame(this);
frame->setWindowTitle("Labels and Icons");
QGroupBox *frame = new QGroupBox("Labels and Icons", this);
auto *label_settings_group = new QBoxLayout(QBoxLayout::TopToBottom, this);
frame->setLayout(label_settings_group);
labels_only = new QRadioButton("Labels Only", frame);
@ -49,6 +72,40 @@ PrefsWindow::PrefsWindow() {
label_settings_group->addWidget(icons_only);
label_settings_group->addWidget(both_labels_icons);
root_layout->addWidget(frame);
cat_enable = new QCheckBox("Enable Cat", this);
QObject::connect(cat_enable, &QCheckBox::toggled, [=,this]() {
this->enable_cat = cat_enable->isChecked();
this->set_options_changed(true);
});
root_layout->addWidget(cat_enable);
QGroupBox *catFrame = new QGroupBox("Cat Selection", this);
auto *cat_btns_layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
catFrame->setLayout(cat_btns_layout);
QButtonGroup *cat_btn_group = new QButtonGroup(catFrame);
cat_btn_group->setExclusive(true);
for (auto &kv : cats) {
auto id = kv.first;
auto pixmap = kv.second;
QWidget *cat_view = new QWidget(this);
QBoxLayout *cat_box = new QBoxLayout(QBoxLayout::LeftToRight, this);
QRadioButton *cat_radio = new QRadioButton(id.c_str(), cat_view);
cat_btn_group->addButton(cat_radio);
cat_view->setLayout(cat_box);
cat_box->addWidget(cat_radio);
QLabel *cat_img = new QLabel(cat_radio);
cat_img->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
float w = cat_img->width(), h = cat_img->height();
cat_img->setPixmap(pixmap.scaled(w, h, Qt::KeepAspectRatio));
cat_img->setAlignment(Qt::Alignment::enum_type::AlignRight);
cat_box->addWidget(cat_img);
QObject::connect(cat_radio, &QRadioButton::pressed, [=,this]() {
this->cat_setting = id;
this->set_options_changed(true);
});
cat_btns_layout->addWidget(cat_view);
cat_btns[id] = cat_radio;
}
root_layout->addWidget(catFrame);
QWidget *btn_view = new QWidget(this);
QBoxLayout *btn_box = new QBoxLayout(QBoxLayout::LeftToRight, this);
revert_btn = new QPushButton("Revert", btn_view);
@ -58,6 +115,7 @@ PrefsWindow::PrefsWindow() {
btn_view->setLayout(btn_box);
btn_box->addWidget(revert_btn);
btn_box->addWidget(apply_btn);
btn_view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
root_layout->addWidget(btn_view);
revert();
setWindowTitle("Looper Preferences");
@ -78,13 +136,34 @@ void PrefsWindow::update_label_setting() {
}
void PrefsWindow::revert() {
set_options_changed(false);
load_options();
new_label_setting = get_option<std::string>("ui.label_setting", "icons");
new_frontend = get_option<std::string>("ui.frontend", "qt");
if (new_frontend != "qt") restart_warning->show();
else restart_warning->hide();
frontend_btn->setText(new_frontend.c_str());
enable_cat = get_option<bool>("ui.enable_cat");
cat_enable->setCheckState(enable_cat ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
cat_setting = get_option<std::string>("ui.cat", cats.empty() ? "" : cats.begin()->first);
std::map<QRadioButton*, bool> radio_btn_values = {{labels_only, false}, {icons_only, false}, {both_labels_icons, false}};
if (new_label_setting == "labels") {
radio_btn_values[labels_only] = true;
} else if (new_label_setting == "icons") {
radio_btn_values[icons_only] = true;
} else if (new_label_setting == "both") {
radio_btn_values[both_labels_icons] = true;
}
for (auto &kv : radio_btn_values) {
kv.first->setChecked(kv.second);
}
if (cat_btns.contains(cat_setting)) cat_btns[cat_setting]->setChecked(true);
send_cat_signal();
update_label_setting();
}
void PrefsWindow::send_cat_signal() {
if (enable_cat && cats.contains(cat_setting)) emit(cat_set(cats[cat_setting]));
else emit(cat_unset());
}
void PrefsWindow::apply() {
set_options_changed(false);
set_option<std::string>("ui.label_setting", new_label_setting);
@ -93,4 +172,8 @@ void PrefsWindow::apply() {
else restart_warning->hide();
frontend_btn->setText(new_frontend.c_str());
update_label_setting();
set_option<bool>("ui.enable_cat", enable_cat);
set_option<std::string>("ui.cat", cat_setting);
send_cat_signal();
save_options();
}

View file

@ -18,18 +18,26 @@ class PrefsWindow : public QWidget {
QPushButton *frontend_btn;
QMenu *frontend_menu;
std::vector<QAction*> frontend_options;
std::map<std::string, QPixmap> cats;
std::map<std::string, QRadioButton*> cat_btns;
QCheckBox *menu_icons;
QRadioButton *labels_only;
QRadioButton *icons_only;
QRadioButton *both_labels_icons;
QPushButton *revert_btn;
QPushButton *apply_btn;
QCheckBox *cat_enable;
bool enable_cat;
std::string cat_setting;
void update_label_setting();
void set_options_changed(bool changed);
void revert();
void apply();
public:
void send_cat_signal();
PrefsWindow();
Q_SIGNALS:
void cat_set(QPixmap &img);
void cat_unset();
void settings_changed(bool use_labels, bool use_icons);
};

View file

@ -76,6 +76,11 @@ void LooperSlider::set_value(double value) {
void LooperSlider::SetValue(double value) {
set_value(value);
}
void LooperSlider::SetValueNoSignal(double value) {
this->slider_value_updating = true;
set_value(value);
this->slider_value_updating = false;
}
void LooperSlider::set_min(double min) {
settings_changed = true;
this->min = min;

View file

@ -78,6 +78,7 @@ class LooperSlider : public QWidget {
const char *MaxLabel();
void SetLimitLabels(const char *min, const char *max);
void SetValue(double value);
void SetValueNoSignal(double value);
double Value();
void SetLogarithmic(bool logarithmic);
bool IsLogarithmic();

View file

@ -4,10 +4,11 @@ using std::vector;
static unsigned int DecodeBase85Byte(char c) {
return c >= '\\' ? c-36 : c-35;
}
vector<unsigned char> DecodeBase85(const char *src) {
vector<unsigned char> dst_vec;
dst_vec.resize(((strlen(src) + 4) / 5) * 4);
unsigned char *dst = dst_vec.data();
vector<unsigned char> *DecodeBase85(const char *src) {
vector<unsigned char> *dst_vec = new vector<unsigned char>();
dst_vec->resize(((strlen(src) + 4) / 5) * 4);
unsigned char *dst = dst_vec->data();
memset(dst, 0, dst_vec->size());
size_t dst_size = 0;
while (*src)
{
@ -17,6 +18,6 @@ vector<unsigned char> DecodeBase85(const char *src) {
dst += 4;
dst_size += 4;
}
dst_vec.resize(dst_size);
dst_vec->resize(dst_size);
return dst_vec;
}

View file

@ -3,8 +3,4 @@
#include <cstring>
#include <string>
// Modified from Dear ImGui
std::vector<unsigned char> DecodeBase85(const char *src);
// May not be needed now, but could be useful in the future.
static inline std::vector<unsigned char> DecodeBase85(const std::string src) {
return DecodeBase85(src.c_str());
}
std::vector<unsigned char> *DecodeBase85(const char *src);

View file

@ -79,7 +79,7 @@ def main() -> None:
os.chdir(basedir)
os.makedirs("build", exist_ok=True)
os.chdir("build")
args=["cmake", ".."]
args=["cmake", "..", "-DBUILD_SDL=ON", "-DBUILD_SOUNDTOUCH=ON"]
for definition in p.cmake_vars:
args.append("-D%s" % definition)
ret = call(args);

88
cats.hpp Normal file
View file

@ -0,0 +1,88 @@
#pragma once
#include <stddef.h>
#include <string>
#include <filesystem>
#include <vector>
namespace fs = std::filesystem;
class MemoryCat {
const void *ptr;
size_t len;
bool owned;
public:
inline size_t get_len() {
return len;
}
inline const void *get_ptr() {
return (const void*)ptr;
}
inline ~MemoryCat() {
if (owned) free((void*)ptr);
}
/// \brief Constructor that doesn't take ownership of the pointer
inline MemoryCat(const void *ptr, size_t len) {
this->owned = false;
this->ptr = ptr;
this->len = len;
}
/// \brief Constructor that takes ownership of the pointer
inline MemoryCat(void *ptr, size_t len) {
this->owned = true;
this->ptr = ptr;
this->len = len;
}
};
enum class CatDataType {
Memory,
File
};
class CatData {
std::optional<MemoryCat> mem;
fs::path path;
CatDataType type;
public:
std::string name;
inline std::string get_name() {
return name;
}
inline CatDataType get_type() {
return type;
}
inline fs::path get_path() {
return path;
}
inline MemoryCat get_memory_cat() {
if (type == CatDataType::Memory) {
return mem.value();
} else {
throw std::exception();
}
}
inline MemoryCat as_memory_cat() {
if (mem.has_value()) return mem.value();
if (type == CatDataType::File) {
FILE *file = fopen(path.c_str(), "rb");
fseek(file, 0, SEEK_END);
size_t len = ftell(file);
fseek(file, 0, SEEK_SET);
void *ptr = malloc(len);
len = fread(ptr, 1, len, file);
ptr = realloc(ptr, len);
MemoryCat output(ptr, len);
this->mem = output;
return output;
}
throw std::exception();
}
inline CatData(const void *ptr, size_t len, std::string name, fs::path virtual_path) {
this->type = CatDataType::Memory;
this->name = name;
this->path = virtual_path;
this->mem = MemoryCat(ptr, len);
}
inline CatData(fs::path path) {
this->name = path.stem();
this->type = CatDataType::File;
this->path = path;
}
};
extern std::vector<CatData> &get_cat_data();

View file

@ -2,6 +2,7 @@
#define TAG "@TAG@"
#define LOCALE_DIR "@LOCALE_DIR@"
#cmakedefine01 DEBUG_MODE_VALUE
#cmakedefine01 OLD_SDBUS
#if DEBUG_MODE_VALUE==1
#define DEBUG_MODE
#endif

View file

@ -6,7 +6,7 @@
#include <fmt/format.h>
#ifdef DBUS_ENABLED
MprisAPI::MprisAPI(sdbus::IConnection &connection, std::string objectPath, DBusAPI *dbus_api)
: AdaptorInterfaces(connection, std::move(objectPath))
: AdaptorInterfaces(connection, OBJECT_PATH(objectPath))
, dbus_api(dbus_api)
, connection(connection)
{
@ -57,12 +57,12 @@ void MprisAPI::Rate(const double &value) {
std::map<std::string, sdbus::Variant> MprisAPI::Metadata() {
std::map<std::string, sdbus::Variant> output;
if (!dbus_api->IsStopped()) {
output["mpris:length"] = (int64_t)(dbus_api->Length() * 1000000);
output["mpris:trackid"] = playing_track_id;
output["xesam:title"] = dbus_api->FileTitle();
output["xesam:url"] = dbus_api->FilePath();
output["mpris:length"] = VARIANT((int64_t)(dbus_api->Length() * 1000000));
output["mpris:trackid"] = VARIANT(playing_track_id);
output["xesam:title"] = VARIANT(dbus_api->FileTitle());
output["xesam:url"] = VARIANT(dbus_api->FilePath());
} else {
output["mpris:trackid"] = empty_track_id;
output["mpris:trackid"] = VARIANT(empty_track_id);
}
return output;
}
@ -90,11 +90,11 @@ std::vector<meta_t> MprisAPI::GetTracksMetadata(const std::vector<track_id_t> &T
if (!dbus_api->IsStopped()) {
output.push_back(Metadata());
auto streams = dbus_api->playback->get_streams();
int i = 0;
int i = 0;
for (auto stream : streams) {
std::map<std::string, sdbus::Variant> meta;
meta["mpris:trackid"] = fmt::format("{}{}", streamPrefix, i);
meta["xesam:title"] = stream.name;
meta["mpris:trackid"] = VARIANT(fmt::format("{}{}", streamPrefix, i));
meta["xesam:title"] = VARIANT(stream.name);
i++;
}
}
@ -123,7 +123,7 @@ std::vector<track_id_t> MprisAPI::Tracks() {
auto streams = dbus_api->playback->get_streams();
int i = 0;
for (auto stream : streams) {
output.push_back(fmt::format("{}{}", streamPrefix, i));
output.push_back(OBJECT_PATH(fmt::format("{}{}", streamPrefix, i)));
i++;
}
return output;
@ -143,14 +143,14 @@ DBusAPI::DBusAPI(Playback *playback, bool daemon)
}
#else
DBusAPI::DBusAPI(Playback *playback, sdbus::IConnection &connection, std::string objectPath, bool daemon)
: AdaptorInterfaces(connection, std::move(objectPath))
: AdaptorInterfaces(connection, OBJECT_PATH(objectPath))
, daemon(daemon)
, playback(playback)
, connection(connection)
{
registerAdaptor();
playback->register_handle(this);
auto mprisConnection = sdbus::createSessionBusConnection("org.mpris.MediaPlayer2.Looper");
auto mprisConnection = sdbus::createSessionBusConnection(SERVICE_NAME("org.mpris.MediaPlayer2.Looper"));
auto &mprisConRef = *mprisConnection.release();
mpris = new MprisAPI(mprisConRef, "/org/mpris/MediaPlayer2", this);
threadFunc = std::thread([this]() {
@ -166,7 +166,7 @@ const char *DBusAPI::objectPath = "/com/complecwaft/looper";
const char *DBusAPI::busName = "com.complecwaft.looper";
DBusAPI *DBusAPI::Create(Playback *playback, bool daemon) {
#ifdef DBUS_ENABLED
auto connection = sdbus::createSessionBusConnection(busName);
auto connection = sdbus::createSessionBusConnection(sdbus::ServiceName(busName));
auto &con_ref = *connection.release();
return new DBusAPI(playback, con_ref, objectPath, daemon);
#else
@ -590,7 +590,7 @@ DBusAPISender *DBusAPISender::Create() {
}
#ifdef DBUS_ENABLED
DBusAPISender::DBusAPISender(sdbus::IConnection &connection, std::string busName, std::string objectPath)
: ProxyInterfaces(connection, std::move(busName), std::move(objectPath)) {
: ProxyInterfaces(connection, BUS_NAME(busName), OBJECT_PATH(objectPath)) {
registerProxy();
DEBUG.writeln("Pinging DBus API to check for its existance.");
Ping();

View file

@ -12,6 +12,26 @@
#include <random>
#include <thread>
#ifdef DBUS_ENABLED
#if OLD_SDBUS
#define OBJECT_PATH(path) path
#define VARIANT(value) value
#define BUS_NAME(name) name
#define SERVICE_NAME(name) name
#define MEMBER_NAME(name) name
#define INTERFACE_NAME(name) name
namespace sdbus {
typedef std::string MemberName;
typedef std::string ServiceName;
}
#else
#define OBJECT_PATH(path) sdbus::ObjectPath{path}
#define VARIANT(value) sdbus::Variant{value}
#define BUS_NAME(name) sdbus::BusName{name}
#define SERVICE_NAME(name) sdbus::ServiceName{name}
#define MEMBER_NAME(name) sdbus::MemberName{name}
#define INTERFACE_NAME(name) sdbus::InterfaceName{name}
#endif
class DBusAPI;
class MprisAPI : public sdbus::AdaptorInterfaces<org::mpris::MediaPlayer2_adaptor, org::mpris::MediaPlayer2::Player_adaptor, org::mpris::MediaPlayer2::TrackList_adaptor, sdbus::Properties_adaptor> {
friend class DBusAPI;
@ -23,17 +43,17 @@ class MprisAPI : public sdbus::AdaptorInterfaces<org::mpris::MediaPlayer2_adapto
const std::string playerInterface = "org.mpris.MediaPlayer2.Player";
const std::string trackInterface = "org.mpris.MediaPlayer2.TrackList";
inline void sendPropertiesChanged(const std::string interface, const std::initializer_list<std::string> properties) {
std::vector<std::string> property_vec;
std::vector<sdbus::MemberName> property_vec;
for (auto property : properties) {
property_vec.push_back(property);
}
emitPropertiesChangedSignal(interface, property_vec);
property_vec.push_back(MEMBER_NAME(property));
}
emitPropertiesChangedSignal(INTERFACE_NAME(interface), property_vec);
}
public:
#define meta_t std::map<std::string, sdbus::Variant>
#define track_id_t sdbus::ObjectPath
const sdbus::ObjectPath playing_track_id = "/com/complecwaft/Looper/PlayingTrack";
const sdbus::ObjectPath empty_track_id = "/org/mpris/MediaPlayer2/TrackList/NoTrack";
const sdbus::ObjectPath playing_track_id = OBJECT_PATH("/com/complecwaft/Looper/PlayingTrack");
const sdbus::ObjectPath empty_track_id = OBJECT_PATH("/org/mpris/MediaPlayer2/TrackList/NoTrack");
inline void Raise() override { }
void Quit() override;

23
debian/changelog vendored Normal file
View file

@ -0,0 +1,23 @@
looper (1.dev4) UNRELEASED; urgency=medium
* Add cat support
-- Zachary Hall <catmeow@complecwaft.com> Mon, 23 Dec 2024 13:15:00 -0800
looper (1.dev3) UNRELEASED; urgency=medium
* Set window class in ImGui UI and also only use one StartupWMClass in the
desktop launcher file.
-- Zachary Hall <catmeow@complecwaft.com> Thu, 19 Dec 2024 14:00:00 -0800
looper (1.dev2) UNRELEASED; urgency=medium
* Fix QT UI endlessly setting parameters and seeking
* Fix icon being named incorrectly
* Update icon
-- Zachary Hall <catmeow@complecwaft.com> Thu, 19 Dec 2024 11:38:00 -0800
looper (1.dev) UNRELEASED; urgency=medium
* Initial release.
-- Zachary Hall <catmeow@complecwaft.com> Tue, 17 Dec 2024 11:21:55 -0800

35
debian/control vendored Normal file
View file

@ -0,0 +1,35 @@
Source: looper
Section: x11
Priority: optional
Maintainer: Zachary Hall <catmeow@complecwaft.com>
Rules-Requires-Root: no
Build-Depends:
debhelper-compat (= 13),
cmake,
git,
python3,
libprotobuf-dev,
protobuf-compiler,
libsoundtouch-dev,
libsdl2-dev,
libsdl2-image-dev,
qt6-base-dev,
liburiparser-dev,
libexpat1-dev,
libsdbus-c++-dev,
libsdbus-c++-bin,
build-essential,
libfmt-dev,
Standards-Version: 4.6.2
Homepage: https://complecwaft.com/catmeow/looper
Vcs-Browser: https://complecwaft.com/catmeow/looper
Vcs-Git: https://complecwaft.com/catmeow/looper.git
Package: looper
Architecture: any
Depends:
${shlibs:Depends},
${misc:Depends},
Description: Audio player designed for video game music.
Looper is an audio player, which is capable of handling music not designed to loop end-to-end.
It has multiple UI frontends, which can be modified in the program.

33
debian/copyright vendored Normal file
View file

@ -0,0 +1,33 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Source: https://complecwaft.com/catmeow/looper
Upstream-Name: looper
Upstream-Contact: catmeow@complecwaft.com
Files:
*
Copyright:
2024 Zachary Hall <catmeow@complecwaft.com>
License: GPL-3.0+
Files:
debian/*
Copyright:
2024 Zachary Hall <catmeow@complecwaft.com>
License: GPL-3.0+
License: GPL-3.0+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Comment:
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".

3
debian/looper-docs.docs vendored Normal file
View file

@ -0,0 +1,3 @@
README
README.source
README.Debian

1
debian/looper.debhelper.log vendored Normal file
View file

@ -0,0 +1 @@
dh_auto_configure

20
debian/looper.doc-base.ex vendored Normal file
View file

@ -0,0 +1,20 @@
Document: looper
Title: Debian looper Manual
Author: <insert document author here>
Abstract: This manual describes what looper is
and how it can be used to
manage online manuals on Debian systems.
Section: unknown
Format: debiandoc-sgml
Files: /usr/share/doc/looper/looper.sgml.gz
Format: postscript
Files: /usr/share/doc/looper/looper.ps.gz
Format: text
Files: /usr/share/doc/looper/looper.text.gz
Format: HTML
Index: /usr/share/doc/looper/html/index.html
Files: /usr/share/doc/looper/html/*.html

56
debian/manpage.1.ex vendored Normal file
View file

@ -0,0 +1,56 @@
.\" Hey, EMACS: -*- nroff -*-
.\" (C) Copyright 2024 unknown <catmeow@complecwaft.com>,
.\"
.\" First parameter, NAME, should be all caps
.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
.\" other parameters are allowed: see man(7), man(1)
.TH Looper SECTION "December 17 2024"
.\" Please adjust this date whenever revising the manpage.
.\"
.\" Some roff macros, for reference:
.\" .nh disable hyphenation
.\" .hy enable hyphenation
.\" .ad l left justify
.\" .ad b justify to both left and right margins
.\" .nf disable filling
.\" .fi enable filling
.\" .br insert line break
.\" .sp <n> insert n+1 empty lines
.\" for manpage-specific macros, see man(7)
.SH NAME
looper \- program to do something
.SH SYNOPSIS
.B looper
.RI [ options ] " files" ...
.br
.B bar
.RI [ options ] " files" ...
.SH DESCRIPTION
This manual page documents briefly the
.B looper
and
.B bar
commands.
.PP
.\" TeX users may be more comfortable with the \fB<whatever>\fP and
.\" \fI<whatever>\fP escape sequences to invode bold face and italics,
.\" respectively.
\fBlooper\fP is a program that...
.SH OPTIONS
These programs follow the usual GNU command line syntax, with long
options starting with two dashes ('\-').
A summary of options is included below.
For a complete description, see the Info files.
.TP
.B \-h, \-\-help
Show summary of options.
.TP
.B \-v, \-\-version
Show version of program.
.SH SEE ALSO
.BR bar (1),
.BR baz (1).
.br
The programs are documented fully by
.IR "The Rise and Fall of a Fooish Bar" ,
available via the Info system.

126
debian/manpage.md.ex vendored Normal file
View file

@ -0,0 +1,126 @@
% looper(SECTION) | User Commands
%
% "December 17 2024"
[comment]: # The lines above form a Pandoc metadata block. They must be
[comment]: # the first ones in the file.
[comment]: # See https://pandoc.org/MANUAL.html#metadata-blocks for details.
[comment]: # pandoc -s -f markdown -t man package.md -o package.1
[comment]: #
[comment]: # A manual page package.1 will be generated. You may view the
[comment]: # manual page with: nroff -man package.1 | less. A typical entry
[comment]: # in a Makefile or Makefile.am is:
[comment]: #
[comment]: # package.1: package.md
[comment]: # pandoc --standalone --from=markdown --to=man $< --output=$@
[comment]: #
[comment]: # The pandoc binary is found in the pandoc package. Please remember
[comment]: # that if you create the nroff version in one of the debian/rules
[comment]: # file targets, such as build, you will need to include pandoc in
[comment]: # your Build-Depends control field.
[comment]: # Remove the lines starting with '[comment]:' in this file in order
[comment]: # to avoid warning messages from pandoc.
# NAME
looper - program to do something
# SYNOPSIS
**looper** **-e** _this_ [**\-\-example=that**] [{**-e** | **\-\-example**} _this_]
[{**-e** | **\-\-example**} {_this_ | _that_}]
**looper** [{**-h** | *\-\-help**} | {**-v** | **\-\-version**}]
# DESCRIPTION
This manual page documents briefly the **looper** and **bar** commands.
This manual page was written for the Debian distribution because the
original program does not have a manual page. Instead, it has documentation
in the GNU info(1) format; see below.
**looper** is a program that...
# OPTIONS
The program follows the usual GNU command line syntax, with long options
starting with two dashes ('-'). A summary of options is included below. For
a complete description, see the **info**(1) files.
**-e** _this_, **\-\-example=**_that_
: Does this and that.
**-h**, **\-\-help**
: Show summary of options.
**-v**, **\-\-version**
: Show version of program.
# FILES
/etc/foo.conf
: The system-wide configuration file to control the behaviour of
looper. See **foo.conf**(5) for further details.
${HOME}/.foo.conf
: The per-user configuration file to control the behaviour of
looper. See **foo.conf**(5) for further details.
# ENVIRONMENT
**FOO_CONF**
: If used, the defined file is used as configuration file (see also
the section called FILES).
# DIAGNOSTICS
The following diagnostics may be issued on stderr:
Bad configuration file. Exiting.
: The configuration file seems to contain a broken configuration
line. Use the **\-\-verbose** option, to get more info.
**looper** provides some return codes, that can be used in scripts:
Code Diagnostic
0 Program exited successfully.
1 The configuration file seems to be broken.
# BUGS
The program is currently limited to only work with the foobar library.
The upstream BTS can be found at http://bugzilla.foo.tld.
# SEE ALSO
**bar**(1), **baz**(1), **foo.conf**(5)
The programs are documented fully by The Rise and Fall of a Fooish Bar
available via the **info**(1) system.
# AUTHOR
unknown <catmeow@complecwaft.com>
: Wrote this manpage for the Debian system.
# COPYRIGHT
Copyright © 2007 unknown
This manual page was written for the Debian system (and may be used by
others).
Permission is granted to copy, distribute and/or modify this document under
the terms of the GNU General Public License, Version 2 or (at your option)
any later version published by the Free Software Foundation.
On Debian systems, the complete text of the GNU General Public License
can be found in /usr/share/common-licenses/GPL.
[comment]: # Local Variables:
[comment]: # mode: markdown
[comment]: # End:

154
debian/manpage.sgml.ex vendored Normal file
View file

@ -0,0 +1,154 @@
<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
<!-- Process this file with docbook-to-man to generate an nroff manual
page: 'docbook-to-man manpage.sgml > manpage.1'. You may view
the manual page with: 'docbook-to-man manpage.sgml | nroff -man |
less'. A typical entry in a Makefile or Makefile.am is:
manpage.1: manpage.sgml
docbook-to-man $< > $@
The docbook-to-man binary is found in the docbook-to-man package.
Please remember that if you create the nroff version in one of the
debian/rules file targets (such as build), you will need to include
docbook-to-man in your Build-Depends control field.
-->
<!-- Fill in your name for FIRSTNAME and SURNAME. -->
<!ENTITY dhfirstname "<firstname>FIRSTNAME</firstname>">
<!ENTITY dhsurname "<surname>SURNAME</surname>">
<!-- Please adjust the date whenever revising the manpage. -->
<!ENTITY dhdate "<date>December 17 2024</date>">
<!-- SECTION should be 1-8, maybe w/ subsection other parameters are
allowed: see man(7), man(1). -->
<!ENTITY dhsection "<manvolnum>SECTION</manvolnum>">
<!ENTITY dhemail "<email>catmeow@complecwaft.com</email>">
<!ENTITY dhusername "unknown">
<!ENTITY dhucpackage "<refentrytitle>Looper</refentrytitle>">
<!ENTITY dhpackage "looper">
<!ENTITY debian "<productname>Debian</productname>">
<!ENTITY gnu "<acronym>GNU</acronym>">
<!ENTITY gpl "&gnu; <acronym>GPL</acronym>">
]>
<refentry>
<refentryinfo>
<address>
&dhemail;
</address>
<author>
&dhfirstname;
&dhsurname;
</author>
<copyright>
<year>2003</year>
<holder>&dhusername;</holder>
</copyright>
&dhdate;
</refentryinfo>
<refmeta>
&dhucpackage;
&dhsection;
</refmeta>
<refnamediv>
<refname>&dhpackage;</refname>
<refpurpose>program to do something</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>&dhpackage;</command>
<arg><option>-e <replaceable>this</replaceable></option></arg>
<arg><option>--example <replaceable>that</replaceable></option></arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>DESCRIPTION</title>
<para>This manual page documents briefly the
<command>&dhpackage;</command> and <command>bar</command>
commands.</para>
<para>This manual page was written for the &debian; distribution
because the original program does not have a manual page.
Instead, it has documentation in the &gnu;
<application>Info</application> format; see below.</para>
<para><command>&dhpackage;</command> is a program that...</para>
</refsect1>
<refsect1>
<title>OPTIONS</title>
<para>These programs follow the usual &gnu; command line syntax,
with long options starting with two dashes ('-'). A summary of
options is included below. For a complete description, see the
<application>Info</application> files.</para>
<variablelist>
<varlistentry>
<term><option>-h</option>
<option>--help</option>
</term>
<listitem>
<para>Show summary of options.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-v</option>
<option>--version</option>
</term>
<listitem>
<para>Show version of program.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>SEE ALSO</title>
<para>bar (1), baz (1).</para>
<para>The programs are documented fully by <citetitle>The Rise and
Fall of a Fooish Bar</citetitle> available via the
<application>Info</application> system.</para>
</refsect1>
<refsect1>
<title>AUTHOR</title>
<para>This manual page was written by &dhusername; &dhemail; for
the &debian; system (and may be used by others). Permission is
granted to copy, distribute and/or modify this document under
the terms of the &gnu; General Public License, Version 2 any
later version published by the Free Software Foundation.
</para>
<para>
On Debian systems, the complete text of the GNU General Public
License can be found in /usr/share/common-licenses/GPL.
</para>
</refsect1>
</refentry>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:2
sgml-indent-data:t
sgml-parent-document:nil
sgml-default-dtd-file:nil
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
-->

291
debian/manpage.xml.ex vendored Normal file
View file

@ -0,0 +1,291 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
<!--
`xsltproc -''-nonet \
-''-param man.charmap.use.subset "0" \
-''-param make.year.ranges "1" \
-''-param make.single.year.ranges "1" \
/usr/share/xml/docbook/stylesheet/docbook-xsl/manpages/docbook.xsl \
manpage.xml`
A manual page <package>.<section> will be generated. You may view the
manual page with: nroff -man <package>.<section> | less'. A typical entry
in a Makefile or Makefile.am is:
DB2MAN = /usr/share/sgml/docbook/stylesheet/xsl/docbook-xsl/manpages/docbook.xsl
XP = xsltproc -''-nonet -''-param man.charmap.use.subset "0"
manpage.1: manpage.xml
$(XP) $(DB2MAN) $<
The xsltproc binary is found in the xsltproc package. The XSL files are in
docbook-xsl. A description of the parameters you can use can be found in the
docbook-xsl-doc-* packages. Please remember that if you create the nroff
version in one of the debian/rules file targets (such as build), you will need
to include xsltproc and docbook-xsl in your Build-Depends control field.
Alternatively use the xmlto command/package. That will also automatically
pull in xsltproc and docbook-xsl.
Notes for using docbook2x: docbook2x-man does not automatically create the
AUTHOR(S) and COPYRIGHT sections. In this case, please add them manually as
<refsect1> ... </refsect1>.
To disable the automatic creation of the AUTHOR(S) and COPYRIGHT sections
read /usr/share/doc/docbook-xsl/doc/manpages/authors.html. This file can be
found in the docbook-xsl-doc-html package.
Validation can be done using: `xmllint -''-noout -''-valid manpage.xml`
General documentation about man-pages and man-page-formatting:
man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
-->
<!-- Fill in your name for FIRSTNAME and SURNAME. -->
<!ENTITY dhfirstname "FIRSTNAME">
<!ENTITY dhsurname "SURNAME">
<!-- dhusername could also be set to "&dhfirstname; &dhsurname;". -->
<!ENTITY dhusername "unknown">
<!ENTITY dhemail "catmeow@complecwaft.com">
<!-- SECTION should be 1-8, maybe w/ subsection other parameters are
allowed: see man(7), man(1) and
http://www.tldp.org/HOWTO/Man-Page/q2.html. -->
<!ENTITY dhsection "SECTION">
<!-- TITLE should be something like "User commands" or similar (see
http://www.tldp.org/HOWTO/Man-Page/q2.html). -->
<!ENTITY dhtitle "looper User Manual">
<!ENTITY dhucpackage "Looper">
<!ENTITY dhpackage "looper">
]>
<refentry>
<refentryinfo>
<title>&dhtitle;</title>
<productname>&dhpackage;</productname>
<authorgroup>
<author>
<firstname>&dhfirstname;</firstname>
<surname>&dhsurname;</surname>
<contrib>Wrote this manpage for the Debian system.</contrib>
<address>
<email>&dhemail;</email>
</address>
</author>
</authorgroup>
<copyright>
<year>2007</year>
<holder>&dhusername;</holder>
</copyright>
<legalnotice>
<para>This manual page was written for the Debian system
(and may be used by others).</para>
<para>Permission is granted to copy, distribute and/or modify this
document under the terms of the GNU General Public License,
Version 2 or (at your option) any later version published by
the Free Software Foundation.</para>
<para>On Debian systems, the complete text of the GNU General Public
License can be found in
<filename>/usr/share/common-licenses/GPL</filename>.</para>
</legalnotice>
</refentryinfo>
<refmeta>
<refentrytitle>&dhucpackage;</refentrytitle>
<manvolnum>&dhsection;</manvolnum>
</refmeta>
<refnamediv>
<refname>&dhpackage;</refname>
<refpurpose>program to do something</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>&dhpackage;</command>
<!-- These are several examples, how syntaxes could look -->
<arg choice="plain"><option>-e <replaceable>this</replaceable></option></arg>
<arg choice="opt"><option>--example=<parameter>that</parameter></option></arg>
<arg choice="opt">
<group choice="req">
<arg choice="plain"><option>-e</option></arg>
<arg choice="plain"><option>--example</option></arg>
</group>
<replaceable class="option">this</replaceable>
</arg>
<arg choice="opt">
<group choice="req">
<arg choice="plain"><option>-e</option></arg>
<arg choice="plain"><option>--example</option></arg>
</group>
<group choice="req">
<arg choice="plain"><replaceable>this</replaceable></arg>
<arg choice="plain"><replaceable>that</replaceable></arg>
</group>
</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>&dhpackage;</command>
<!-- Normally the help and version options make the programs stop
right after outputting the requested information. -->
<group choice="opt">
<arg choice="plain">
<group choice="req">
<arg choice="plain"><option>-h</option></arg>
<arg choice="plain"><option>--help</option></arg>
</group>
</arg>
<arg choice="plain">
<group choice="req">
<arg choice="plain"><option>-v</option></arg>
<arg choice="plain"><option>--version</option></arg>
</group>
</arg>
</group>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1 id="description">
<title>DESCRIPTION</title>
<para>This manual page documents briefly the
<command>&dhpackage;</command> and <command>bar</command>
commands.</para>
<para>This manual page was written for the Debian distribution
because the original program does not have a manual page.
Instead, it has documentation in the GNU <citerefentry>
<refentrytitle>info</refentrytitle>
<manvolnum>1</manvolnum>
</citerefentry> format; see below.</para>
<para><command>&dhpackage;</command> is a program that...</para>
</refsect1>
<refsect1 id="options">
<title>OPTIONS</title>
<para>The program follows the usual GNU command line syntax,
with long options starting with two dashes ('-'). A summary of
options is included below. For a complete description, see the
<citerefentry>
<refentrytitle>info</refentrytitle>
<manvolnum>1</manvolnum>
</citerefentry> files.</para>
<variablelist>
<!-- Use the variablelist.term.separator and the
variablelist.term.break.after parameters to
control the term elements. -->
<varlistentry>
<term><option>-e <replaceable>this</replaceable></option></term>
<term><option>--example=<replaceable>that</replaceable></option></term>
<listitem>
<para>Does this and that.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-h</option></term>
<term><option>--help</option></term>
<listitem>
<para>Show summary of options.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-v</option></term>
<term><option>--version</option></term>
<listitem>
<para>Show version of program.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="files">
<title>FILES</title>
<variablelist>
<varlistentry>
<term><filename>/etc/foo.conf</filename></term>
<listitem>
<para>The system-wide configuration file to control the
behaviour of <application>&dhpackage;</application>. See
<citerefentry>
<refentrytitle>foo.conf</refentrytitle>
<manvolnum>5</manvolnum>
</citerefentry> for further details.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><filename>${HOME}/.foo.conf</filename></term>
<listitem>
<para>The per-user configuration file to control the
behaviour of <application>&dhpackage;</application>. See
<citerefentry>
<refentrytitle>foo.conf</refentrytitle>
<manvolnum>5</manvolnum>
</citerefentry> for further details.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="environment">
<title>ENVIRONMENT</title>
<variablelist>
<varlistentry>
<term><envar>FOO_CONF</envar></term>
<listitem>
<para>If used, the defined file is used as configuration
file (see also <xref linkend="files"/>).</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="diagnostics">
<title>DIAGNOSTICS</title>
<para>The following diagnostics may be issued
on <filename class="devicefile">stderr</filename>:</para>
<variablelist>
<varlistentry>
<term><errortext>Bad configuration file. Exiting.</errortext></term>
<listitem>
<para>The configuration file seems to contain a broken configuration
line. Use the <option>--verbose</option> option, to get more info.
</para>
</listitem>
</varlistentry>
</variablelist>
<para><command>&dhpackage;</command> provides some return codes, that can
be used in scripts:</para>
<segmentedlist>
<segtitle>Code</segtitle>
<segtitle>Diagnostic</segtitle>
<seglistitem>
<seg><errorcode>0</errorcode></seg>
<seg>Program exited successfully.</seg>
</seglistitem>
<seglistitem>
<seg><errorcode>1</errorcode></seg>
<seg>The configuration file seems to be broken.</seg>
</seglistitem>
</segmentedlist>
</refsect1>
<refsect1 id="bugs">
<!-- Or use this section to tell about upstream BTS. -->
<title>BUGS</title>
<para>The program is currently limited to only work
with the <package>foobar</package> library.</para>
<para>The upstreams <acronym>BTS</acronym> can be found
at <ulink url="http://bugzilla.foo.tld"/>.</para>
</refsect1>
<refsect1 id="see_also">
<title>SEE ALSO</title>
<!-- In alpabetical order. -->
<para><citerefentry>
<refentrytitle>bar</refentrytitle>
<manvolnum>1</manvolnum>
</citerefentry>, <citerefentry>
<refentrytitle>baz</refentrytitle>
<manvolnum>1</manvolnum>
</citerefentry>, <citerefentry>
<refentrytitle>foo.conf</refentrytitle>
<manvolnum>5</manvolnum>
</citerefentry></para>
<para>The programs are documented fully by <citetitle>The Rise and
Fall of a Fooish Bar</citetitle> available via the <citerefentry>
<refentrytitle>info</refentrytitle>
<manvolnum>1</manvolnum>
</citerefentry> system.</para>
</refsect1>
</refentry>

39
debian/postinst vendored Normal file
View file

@ -0,0 +1,39 @@
#!/bin/sh
# postinst script for looper.
#
# See: dh_installdeb(1).
set -e
# Summary of how this script can be called:
# * <postinst> 'configure' <most-recently-configured-version>
# * <old-postinst> 'abort-upgrade' <new version>
# * <conflictor's-postinst> 'abort-remove' 'in-favour' <package>
# <new-version>
# * <postinst> 'abort-remove'
# * <deconfigured's-postinst> 'abort-deconfigure' 'in-favour'
# <failed-install-package> <version> 'removing'
# <conflicting-package> <version>
# for details, see https://www.debian.org/doc/debian-policy/ or
# the debian-policy package.
case "$1" in
configure)
xdg-mime install /usr/share/mime/audio/x-zsound.xml
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument '$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

37
debian/postrm.ex vendored Normal file
View file

@ -0,0 +1,37 @@
#!/bin/sh
# postrm script for looper.
#
# See: dh_installdeb(1).
set -e
# Summary of how this script can be called:
# * <postrm> 'remove'
# * <postrm> 'purge'
# * <old-postrm> 'upgrade' <new-version>
# * <new-postrm> 'failed-upgrade' <old-version>
# * <new-postrm> 'abort-install'
# * <new-postrm> 'abort-install' <old-version>
# * <new-postrm> 'abort-upgrade' <old-version>
# * <disappearer's-postrm> 'disappear' <overwriter>
# <overwriter-version>
# for details, see https://www.debian.org/doc/debian-policy/ or
# the debian-policy package.
case "$1" in
purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
;;
*)
echo "postrm called with unknown argument '$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

35
debian/preinst.ex vendored Normal file
View file

@ -0,0 +1,35 @@
#!/bin/sh
# preinst script for looper.
#
# See: dh_installdeb(1).
set -e
# Summary of how this script can be called:
# * <new-preinst> 'install'
# * <new-preinst> 'install' <old-version>
# * <new-preinst> 'upgrade' <old-version>
# * <old-preinst> 'abort-upgrade' <new-version>
# for details, see https://www.debian.org/doc/debian-policy/ or
# the debian-policy package.
case "$1" in
install|upgrade)
;;
abort-upgrade)
;;
*)
echo "preinst called with unknown argument '$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

38
debian/prerm vendored Normal file
View file

@ -0,0 +1,38 @@
#!/bin/sh
# prerm script for looper.
#
# See: dh_installdeb(1).
set -e
# Summary of how this script can be called:
# * <prerm> 'remove'
# * <old-prerm> 'upgrade' <new-version>
# * <new-prerm> 'failed-upgrade' <old-version>
# * <conflictor's-prerm> 'remove' 'in-favour' <package> <new-version>
# * <deconfigured's-prerm> 'deconfigure' 'in-favour'
# <package-being-installed> <version> 'removing'
# <conflicting-package> <version>
# for details, see https://www.debian.org/doc/debian-policy/ or
# the debian-policy package.
case "$1" in
remove|upgrade|deconfigure)
xdg-mime uninstall /usr/share/mime/audio/x-zsound.xml
;;
failed-upgrade)
;;
*)
echo "prerm called with unknown argument '$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
#DEBHELPER#
exit 0

33
debian/rules vendored Executable file
View file

@ -0,0 +1,33 @@
#!/usr/bin/make -f
# See debhelper(7) (uncomment to enable).
# Output every command that modifies files on the build system.
#export DH_VERBOSE = 1
# See FEATURE AREAS in dpkg-buildflags(1).
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
# See ENVIRONMENT in dpkg-buildflags(1).
# Package maintainers to append CFLAGS.
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
# Package maintainers to append LDFLAGS.
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
%:
dh $@
# dh_make generated override targets.
# This is an example for Cmake (see <https://bugs.debian.org/641051>).
override_dh_auto_configure:
git submodule update --init --recursive -- backends/ui/imgui/imgui subprojects/vgmstream subprojects/SDL-Mixer-X
dh_auto_configure -- \
-DBUILD_SOUNDTOUCH=OFF \
-DMIXERX_ENABLE_GPL=ON \
-DMIXERX_ENABLE_LGPL=ON \
-DUSE_G719=OFF \
-DUSE_SPEEX=OFF \
-DUSE_ATRAC9=OFF \
-DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH)

11
debian/salsa-ci.yml.ex vendored Normal file
View file

@ -0,0 +1,11 @@
# For more information on what jobs are run see:
# https://salsa.debian.org/salsa-ci-team/pipeline
#
# To enable the jobs, go to your repository (at salsa.debian.org)
# and click over Settings > CI/CD > Expand (in General pipelines).
# In "CI/CD configuration file" write debian/salsa-ci.yml and click
# in "Save Changes". The CI tests will run after the next commit.
---
include:
- https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml
- https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml

1
debian/source/format vendored Normal file
View file

@ -0,0 +1 @@
3.0 (native)

View file

@ -1,6 +1,7 @@
#include "file_backend.hpp"
#include <filesystem>
#include <string.h>
#include <cassert>
File::File() {
}
@ -56,9 +57,18 @@ bool CFile::is_open() {
return file != NULL;
}
MemFile::MemFile() {
memory_owned = false;
ptr = NULL;
len = 0;
}
void MemFile::open_memory(void *ptr, size_t len, const char *name) {
assert(name != NULL);
this->name = strdup(name);
this->ptr = ptr;
this->len = len;
this->pos = 0;
memory_owned = false;
}
void MemFile::open(const char *path) {
CFile file;
file.open(path);
@ -67,9 +77,10 @@ void MemFile::open(const char *path) {
this->len = file.read(this->ptr, 1, file.get_len());
file.close();
this->pos = 0;
memory_owned = true;
}
void MemFile::close() {
free(this->ptr);
if (memory_owned) free(this->ptr);
free((void*)this->name);
this->ptr = NULL;
this->name = NULL;

View file

@ -93,8 +93,10 @@ class MemFile : public File {
void *ptr;
size_t len;
int64_t pos;
bool memory_owned = true;
public:
MemFile();
void open_memory(void *ptr, size_t len, const char *name = "<mem_file>");
void open(const char *path) override;
void close() override;
size_t read(void *ptr, size_t size, size_t len) override;

View file

@ -13,12 +13,6 @@ message RenderResponse {
uint64 len = 302;
bytes data = 303;
};
message Ping {
bytes data = 1;
};
message Pong {
bytes data = 1;
};
message LogMessage {
uint64 timespec = 6000;
uint32 level = 6001;
@ -51,8 +45,6 @@ message RPCCall {
QuitCmd quit = 6;
InitCommand init = 7;
GetPropertyListCommand get_property_list = 8;
Ping = 9;
Pong = 10;
};
};
message PropertyList {
@ -67,7 +59,5 @@ message RPCResponse {
ResetResponse reset = 5;
ErrorResponse err = 6;
PropertyList property_list = 7;
Ping = 8;
Pong = 9;
};
};

114
main.cpp
View file

@ -16,6 +16,9 @@
#include <image.h>
#endif
#include "web_functions.hpp"
#include "cats.hpp"
#include <json/value.h>
#include <chrono>
using namespace Looper;
using namespace Looper::Options;
using namespace Looper::Log;
@ -23,7 +26,13 @@ using namespace Looper::Log;
extern "C" {
void quit();
}
#else
//#include <curl/curl.h>
#endif
std::vector<CatData> &get_cat_data() {
static std::vector<CatData> data;
return data;
}
std::unordered_set<LicenseData> license_data;
std::unordered_set<LicenseData> &get_license_data() {
return license_data;
@ -175,6 +184,108 @@ extern "C" int looper_run_as_executable(std::vector<std::string> args) {
}
DEBUG.writeln("Loading options file...");
load_options();
{
auto &cat_data = get_cat_data();
cat_data.push_back(CatData(catoc_data, catoc_size, "Cat OC (Built-In)", "catoc.png"));
#ifndef __EMSCRIPTEN__
fs::path baseDir = get_prefs_path() / fs::path("looper") / fs::path("cats");
if (!fs::exists(baseDir)) {
fs::create_directories(baseDir);
}
for (auto const&dir_entry : std::filesystem::directory_iterator(baseDir)) {
if (dir_entry.is_regular_file()) {
cat_data.push_back(CatData(dir_entry.path()));
} else if (dir_entry.is_directory()) {
fs::path json_path = dir_entry.path() / fs::path("catpack.json");
if (fs::exists(json_path)) {
std::ifstream stream(json_path);
Json::Value config;
stream >> config;
std::string name = dir_entry.path().stem().string();
if (config.isMember("name")) {
name = config["name"].asString();
}
if (!config.isMember("default")) {
continue;
}
std::string str = config["default"].asString();
if (config.isMember("variants")) {
Json::Value variants = config["variants"];
int prev_score = INT_MAX;
auto now = std::chrono::system_clock::now();
time_t now_tt = std::chrono::system_clock::to_time_t(now);
tm local_tm = *localtime(&now_tt);
auto cur_day = local_tm.tm_mday;
auto cur_month = local_tm.tm_mon;
auto cur_yday = local_tm.tm_yday;
DEBUG.writefln("Current date: %d ([MM/DD] %02d/%02d)", cur_yday, cur_month + 1, cur_day);
for (auto &variant : variants) {
if (variant.isMember("startTime") && variant.isMember("endTime")) {
Json::Value startTime = variant["startTime"];
Json::Value endTime = variant["endTime"];
int score = 0;
int start = 0;
int end = 0;
tm start_tm;
tm end_tm;
if (startTime.isMember("day") && startTime.isMember("month")) {
int day = startTime["day"].asInt();
int month = startTime["month"].asInt();
tm tmp{};
tmp.tm_year = local_tm.tm_year;
tmp.tm_mon = month - 1;
tmp.tm_mday = day;
time_t t = std::mktime(&tmp);
tmp = *std::localtime(&t);
start = tmp.tm_yday;
start_tm = tmp;
}
if (endTime.isMember("day") && endTime.isMember("month")) {
int day = endTime["day"].asInt();
int month = endTime["month"].asInt();
tm tmp{};
tmp.tm_year = local_tm.tm_year;
tmp.tm_mon = month - 1;
tmp.tm_mday = day;
time_t t = std::mktime(&tmp);
tmp = *std::localtime(&t);
end = tmp.tm_yday;
end_tm = tmp;
}
if (variant.isMember("path")) DEBUG.writefln("Variant %s: %d-%d ([MM/DD] %02d/%02d-%02d/%02d)", variant["path"].asCString(), start, end, start_tm.tm_mon+1, start_tm.tm_mday, end_tm.tm_mon+1, end_tm.tm_mday);
if (end < start) {
if (cur_yday > end && cur_yday < start) continue;
score = end - start + 365;
} else {
if (cur_yday > end || cur_yday < start) continue;
score = end - start;
}
DEBUG.writefln("Score: %d (prev: %d)", score, prev_score);
if (variant.isMember("path")) {
if (prev_score > score) {
auto newPath = variant["path"].asString();
if (!fs::exists(dir_entry.path() / fs::path(newPath))) {
continue;
}
str = newPath;
prev_score = score;
}
}
}
}
}
fs::path usedPath = dir_entry.path() / fs::path(str);
if (fs::exists(usedPath)) {
auto data = CatData(usedPath);
data.name = name;
cat_data.push_back(data);
}
}
}
}
#endif
}
std::string backend_id = get_option<std::string>("ui.frontend", "imgui");
UIBackend *backend = UIBackend::get_backend(ui_backend_option).value_or(UIBackend::get_backend(backend_id).value_or(UIBackend::get_first_backend()));
int output = 0;
@ -264,6 +375,9 @@ int main(int argc, char **argv) {
app.add_option("-l, --log-level", LogStream::log_level, "Sets the minimum log level to display in the logs.");
app.allow_extras();
init_logging();
#ifndef __EMSCRIPTEN__
//curl_global_init(CURL_GLOBAL_ALL);
#endif
try {
app.parse(argc, argv);
executable_path = argv[0];

View file

@ -174,6 +174,8 @@ void PlaybackInstance::Load(const char *file, int idx) {
paused = false;
just_started.store(true);
flag_mutex.unlock();
set_signal(PlaybackSignalStarted);
set_signal(PlaybackSignalFileChanged);
}
void PlaybackInstance::Unload() {
if (process == nullptr) return;
@ -185,6 +187,7 @@ void PlaybackInstance::Unload() {
if (buf) free(buf);
buf = nullptr;
UnlockAudioDevice();
set_signal(PlaybackSignalStopped);
}
void PlaybackInstance::UpdateST() {
bool any_changed = false;
@ -465,16 +468,14 @@ PlaybackInstance::PlaybackInstance() {
}
std::optional<std::string> PlaybackInstance::get_current_file() {
current_file_mutex.lock();
std::optional<std::string> output = current_file;
current_file_mutex.unlock();
return output;
if (process == nullptr) return {};
if (!process->process_running()) return {};
return process->get_file_path();
}
std::optional<std::string> PlaybackInstance::get_current_title() {
current_file_mutex.lock();
std::optional<std::string> output = current_title;
current_file_mutex.unlock();
return output;
if (process == nullptr) return {};
if (!process->process_running()) return {};
return process->get_title();
}
PlaybackInstance::~PlaybackInstance() {
Stop();

View file

@ -6,6 +6,7 @@
#include <optional>
#include <SDL.h>
#include <iterator>
#include <limits.h>
#include <limits>
#include "util.hpp"
#include "ipc/internal.pb.h"
@ -20,13 +21,13 @@ class PlaybackBackend {
size_t minSamples;
size_t maxSamples;
protected:
double length;
uint64_t length;
std::vector<PlaybackStream> streams;
std::string current_file;
std::string current_title;
std::optional<std::string> current_title;
bool open;
SDL_AudioSpec spec;
double position;
uint64_t position;
std::map<std::string, google::protobuf::Any> property_defaults;
std::map<std::string, google::protobuf::Any> properties;
size_t min_sample_estimate;
@ -34,8 +35,8 @@ class PlaybackBackend {
size_t max_sample_requirement = std::numeric_limits<size_t>::max();
size_t min_sample_requirement = std::numeric_limits<size_t>::min();
double rate;
double loop_start = 0.0;
double loop_end = -1.0;
uint64_t loop_start = 0;
uint64_t loop_end = UINT64_MAX;
void setMinSamples(size_t samples) {
this->minSamples = samples;
adjustSampleEstimates();
@ -53,6 +54,12 @@ class PlaybackBackend {
max_sample_estimate = (size_t)ceill(tmpMaxSamples * tmpRate);
if (max_sample_estimate > max_sample_requirement) max_sample_estimate = max_sample_requirement;
}
inline double samples_to_time(uint64_t samples) {
return ((double)samples) / ((double)spec.freq);
}
inline uint64_t time_to_samples(double time) {
return time * spec.freq;
}
public:
using map = std::map<std::string, PlaybackBackend*>;
using iterator = map::iterator;
@ -62,8 +69,12 @@ class PlaybackBackend {
inline virtual void add_licenses() { }
inline virtual std::string get_id() {return "";}
inline virtual std::string get_name() {return "";}
inline virtual void seek(double position) { }
inline virtual double get_position() {
inline virtual void seek(double position) { seek_samples(time_to_samples(position)); }
inline virtual void seek_samples(uint64_t position) { }
inline virtual double get_position_time() {
return ((double)get_position_samples()) / ((double)spec.freq);
}
inline virtual uint64_t get_position_samples() {
return position;
}
inline virtual SDL_AudioSpec get_spec() {
@ -102,19 +113,42 @@ class PlaybackBackend {
virtual void cleanup();
virtual size_t render(void *buf, size_t maxlen);
inline virtual double get_length() {
return open ? length : 0.0;
return samples_to_time(get_length_samples());
}
inline virtual uint64_t get_length_samples() {
return open ? length : 0;
}
inline virtual uint64_t get_loop_start_samples() {
return open ? loop_start : 0;
}
inline virtual double get_loop_start() {
return open ? loop_start : 0.0;
return samples_to_time(get_loop_start_samples());
}
inline virtual uint64_t get_loop_end_samples() {
return open ? loop_end == UINT64_MAX ? get_length_samples() : loop_end : 0;
}
inline virtual double get_loop_end() {
return open ? loop_end < 0.0 ? get_length() : loop_end : 0.0;
return samples_to_time(get_loop_end_samples());
}
inline virtual std::optional<std::string> get_current_file() {
return open ? current_file : std::optional<std::string>();
}
inline virtual std::optional<std::string> get_title() {
return open ? current_title : std::optional<std::string>();
if (open) {
if (current_title.has_value()) {
return current_title;
} else {
auto file = get_current_file();
if (file.has_value()) {
std::filesystem::path path(file.value());
return path.stem().string();
} else {
return {};
}
}
} else {
return {};
}
}
inline virtual int get_stream_idx() {return 0;}
inline virtual ~PlaybackBackend() { }

View file

@ -25,6 +25,9 @@
#ifdef __WINDOWS__
#include <windows.h>
#endif
#ifdef __HAIKU__
#include <kernel/scheduler.h>
#endif
#include <google/protobuf/message.h>
#include "util.hpp"
using namespace google::protobuf;
@ -181,7 +184,7 @@ PropertyDataOrError PlaybackProcessServiceImpl::Get(const GetProperty *request)
} break;
case PropertyId::PositionProperty: {
DoubleProperty pos;
pos.set_value(cur_backend->get_position());
pos.set_value(cur_backend->get_position_time());
data->mutable_value()->PackFrom(pos);
} break;
case PropertyId::BackendSpecific: {
@ -335,6 +338,7 @@ MaybeError PlaybackProcessServiceImpl::Init(const InitCommand *cmd) {
}
lock.set(backend.second, false);
DEBUG.writefln("Using backend: %s", backend.second->get_name().c_str());
backend.second->seek(0.0);
break;
}
if (!cur_backend_lock.has_value()) {
@ -396,6 +400,9 @@ PlaybackProcess::PlaybackProcess(PlaybackProcess *parent) {
DEBUG.writeln("Host process address: (in-process)");
}
PlaybackProcess::PlaybackProcess(std::vector<std::string> args) {
#ifdef __HAIKU__
set_thread_priority(find_thread(NULL), suggest_thread_priority(B_LIVE_AUDIO_MANIPULATION));
#endif
SDL_InitSubSystem(SDL_INIT_AUDIO);
done = false;
is_playback_process = true;
@ -413,7 +420,11 @@ PlaybackProcess::PlaybackProcess(std::vector<std::string> args) {
}
PlaybackProcess::PlaybackProcess(std::string filename, int idx) {
// multi_process = Looper::Options::get_option<bool>("playback.multi_process", true);
#ifdef __EMSCRIPTEN__
multi_process = false;
#else
multi_process = true;
#endif
done = false;
this->done = false;
if (multi_process) {
@ -658,6 +669,7 @@ std::optional<google::protobuf::Any> PlaybackProcess::get_property(std::string p
call.mutable_get()->CopyFrom(get_property);
RPCResponse output = SendCommand(&call);
if (output.has_err()) {
ERROR.writefln2("Error occurred getting property: {}", output.err().desc());
return {};
}
return output.data().value();

View file

@ -1,4 +1,5 @@
#!/bin/bash
pushd "$(dirname "$0")/subprojects/vgmstream"
git apply ../../vgmstream.patch
git submodule update --init --recursive
popd

@ -1 +1 @@
Subproject commit 22aed1f6bcfa6912a34d3241edf3bd90498a6bc2
Subproject commit 0f7e551b59372cdad6c7475d50b6d0b8a9ac2765

@ -1 +1 @@
Subproject commit 720da57baba83b3b1829e20133575e57aa1a8a4f
Subproject commit e3ddede6c4ee818825c4e5a6dfa1d384860c27d9

@ -1 +1 @@
Subproject commit d144031940543e15423a25ae5a8a74141044862f
Subproject commit 6dae7eb4a5c3a169f3e298392bff4680224aa94a

@ -1 +1 @@
Subproject commit 8214f717e7c7d361f002b6c3d1b1086ddd096315
Subproject commit dca8a24cf8da1fc61b5cf0422cad03474124196c

View file

@ -519,6 +519,13 @@ inline std::string resolve_value<std::string>(google::protobuf::Any value) {
return output;
}
template<>
inline int resolve_value<int>(google::protobuf::Any value) {
IntProperty *property = resolve_any<IntProperty>(value);
int output = property->value();
delete property;
return output;
}
template<>
inline DynPtr resolve_value<DynPtr>(google::protobuf::Any value) {
BytesProperty *property = resolve_any<BytesProperty>(value);
std::string output = property->value();
@ -527,7 +534,7 @@ inline DynPtr resolve_value<DynPtr>(google::protobuf::Any value) {
}
template<>
inline double resolve_value(google::protobuf::Any value) {
inline double resolve_value<double>(google::protobuf::Any value) {
DoubleProperty *property = resolve_any<DoubleProperty>(value);
double output = property->value();
delete property;