Compare commits

..

61 commits

Author SHA1 Message Date
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 4479 additions and 2317 deletions

View file

@ -39,6 +39,32 @@ jobs:
overwrite: true overwrite: true
include-hidden-files: true include-hidden-files: true
if-no-files-found: error 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: build-appimage:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: get-source-code needs: get-source-code

10
.gitignore vendored
View file

@ -1,4 +1,5 @@
assets/*.h assets/*.h
assets/*.hpp
build* build*
!build-env !build-env
.vscode/settings.json .vscode/settings.json
@ -24,4 +25,11 @@ cmake-build-*/
local.properties local.properties
hs_err_*.log hs_err_*.log
replay_pid*.log replay_pid*.log
assets/btcc 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(DOWNLOAD_AUDIO_CODECS_DEPENDENCY OFF CACHE BOOL "")
set(AUDIO_CODECS_BUILD_LOCAL_SDL2 OFF CACHE BOOL "" FORCE) set(AUDIO_CODECS_BUILD_LOCAL_SDL2 OFF CACHE BOOL "" FORCE)
set(MIXERX_LGPL ON 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_ALT OFF CACHE BOOL "" FORCE)
set(USE_MIDI_NATIVE OFF CACHE BOOL "" FORCE) set(USE_MIDI_NATIVE OFF CACHE BOOL "" FORCE)
set(USE_MIDI_TIMIDITY OFF CACHE BOOL "" FORCE) set(USE_MIDI_TIMIDITY OFF CACHE BOOL "" FORCE)
set(USE_MIDI_FLUIDLITE OFF CACHE BOOL "" FORCE) set(USE_MIDI_FLUIDLITE OFF CACHE BOOL "" FORCE)
set(USE_MIDI_OPNMIDI OFF CACHE BOOL "" FORCE) set(USE_MIDI_OPNMIDI OFF CACHE BOOL "" FORCE)
set(USE_MIDI_ADLMIDI 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 99)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)
set(BUILD_FB2K OFF CACHE BOOL "" FORCE) 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) option(ENABLE_DBUS "Enables DBus support" ON)
endif() endif()
option(BUILD_LIBFMT "Builds libfmt" OFF) 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) if(HAIKU)
set(USE_CELT OFF CACHE BOOL "" FORCE) set(USE_CELT OFF CACHE BOOL "" FORCE)
set(USE_SPEEX OFF CACHE BOOL "" FORCE) set(USE_SPEEX OFF CACHE BOOL "" FORCE)
@ -311,8 +315,8 @@ prefix_all(LIBRARY_SOURCES
base85.h base85.h
) )
run_protoc(OUTDIR ${CMAKE_BINARY_DIR}/google/protobuf SOURCE google/protobuf/any.proto OUTVAR _src) 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) if(FOR_WASMER)
target_compile_definitions(liblooper PUBLIC FOR_WASMER) target_compile_definitions(liblooper PUBLIC FOR_WASMER)
endif() endif()
@ -343,6 +347,7 @@ if(BUILD_JSONCPP)
set(JSONCPP_TARGET jsoncpp_static) set(JSONCPP_TARGET jsoncpp_static)
endif() endif()
if(BUILD_SOUNDTOUCH) if(BUILD_SOUNDTOUCH)
message("Building SoundTouch.")
set(SOUNDSTRETCH OFF CACHE BOOL "" FORCE) set(SOUNDSTRETCH OFF CACHE BOOL "" FORCE)
set(INTEGER_SAMPLES OFF CACHE BOOL "") set(INTEGER_SAMPLES OFF CACHE BOOL "")
add_subdirectory(subprojects/soundtouch) add_subdirectory(subprojects/soundtouch)
@ -362,12 +367,12 @@ if (DEFINED ANDROID_NDK)
target_link_libraries(liblooper PUBLIC oboe) target_link_libraries(liblooper PUBLIC oboe)
endif() endif()
pkg_check_modules(libxspf IMPORTED_TARGET libxspf) pkg_check_modules(libxspf IMPORTED_TARGET libxspf)
include(ExternalProject)
if (NOT libxspf_FOUND) 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}) set(XSPF ${CMAKE_BINARY_DIR}/libxspf-prefix/src/libxspf-build/.libs/${CMAKE_STATIC_LIBRARY_PREFIX}xspf${CMAKE_STATIC_LIBRARY_SUFFIX})
ExternalProject_Add(libxspf ExternalProject_Add(libxspf
URL https://ftp.osuosl.org/pub/xiph/releases/xspf/libxspf-1.2.0.tar.lzma 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 CONFIGURE_COMMAND <SOURCE_DIR>/configure --disable-test --enable-static --disable-shared
BUILD_COMMAND make BUILD_COMMAND make
INSTALL_COMMAND make install DESTDIR=<BINARY_DIR> 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_include_directories(SDL2::SDL2 INTERFACE /boot/system/develop/headers/SDL2 ${sdl2_INCLUDE_DIRS})
target_link_libraries(SDL2::SDL2 INTERFACE ${sdl2_LIBRARIES}) target_link_libraries(SDL2::SDL2 INTERFACE ${sdl2_LIBRARIES})
else() else()
find_package(SDL2 REQUIRED) find_package(SDL2 REQUIRED)
endif() endif()
endif() endif()
if (NOT BUILD_SDL_IMAGE) if (NOT BUILD_SDL_IMAGE)
@ -413,28 +418,34 @@ else()
target_link_libraries(SDL2_image::SDL2_image INTERFACE ${sdl2_image_LIBRARIES}) target_link_libraries(SDL2_image::SDL2_image INTERFACE ${sdl2_image_LIBRARIES})
endif() endif()
endif() endif()
if (WINDOWS)
set(ENABLE_DBUS OFF CACHE BOOL "" FORCE)
endif()
if (ENABLE_DBUS) if (ENABLE_DBUS)
find_package(sdbus-c++) find_package(sdbus-c++ 2.0)
if(NOT ${sdbus-c++_FOUND}) if(${sdbus-c++_FOUND})
set(ENABLE_DBUS OFF) set(OLD_SDBUS OFF)
message("Warning: Dbus support not found - Not enabling DBus") 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()
endif() endif()
set(SDL2_TARGET SDL2::SDL2) set(SDL2_TARGET SDL2::SDL2)
if (TARGET SDL2-static) if (TARGET SDL2-static)
set(SDL2_TARGET SDL2-static) set(SDL2_TARGET SDL2-static)
endif() 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() endif()
if(BUILD_PROTOBUF) if(BUILD_PROTOBUF)
add_subdirectory(subprojects/protobuf) add_subdirectory(subprojects/protobuf)
else() else()
if (CMAKE_SYSTEM_NAME STREQUAL "Haiku")
find_package(Protobuf REQUIRED) find_package(Protobuf REQUIRED)
else()
find_package(protobuf REQUIRED)
find_package(absl CONFIG REQUIRED)
endif()
endif() endif()
if (${ENABLE_DBUS}) if (${ENABLE_DBUS})
@ -444,7 +455,8 @@ endif()
macro(add_ui_backend) macro(add_ui_backend)
set(ARGS ${ARGV}) set(ARGS ${ARGV})
list(POP_FRONT ARGS target) 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} "'...") message("Enabling UI backend '" ${target} "'...")
list(APPEND UI_BACKENDS ${target}) list(APPEND UI_BACKENDS ${target})
set(UI_BACKENDS ${UI_BACKENDS} PARENT_SCOPE) set(UI_BACKENDS ${UI_BACKENDS} PARENT_SCOPE)
@ -479,7 +491,8 @@ endmacro()
macro(add_playback_backend) macro(add_playback_backend)
set(ARGS ${ARGV}) set(ARGS ${ARGV})
list(POP_FRONT ARGS target) 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} "'...") message("Enabling playback backend '" ${target} "'...")
list(APPEND PLAYBACK_BACKENDS ${target}) list(APPEND PLAYBACK_BACKENDS ${target})
set(PLAYBACK_BACKENDS ${PLAYBACK_BACKENDS} PARENT_SCOPE) 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 "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 "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 "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}) 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) 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) 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) copy_to_bindir(assets/ForkAwesome/css/fork-awesome.min.css.map fork-awesome.min.css.map)
endif() endif()
target_link_libraries(${TARGET_NAME} PUBLIC liblooper ${UI_BACKENDS} ${PLAYBACK_BACKENDS}) 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}) if (${BUILD_SDL2})
install(EXPORT SDL2-static SDL2main) install(EXPORT SDL2-static SDL2main)
endif() endif()
if (NOT DEFINED EMSCRIPTEN) 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.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES assets/com.complecwaft.Looper.metainfo.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo) install(FILES assets/com.complecwaft.Looper.metainfo.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
install(DIRECTORY assets/translations/ TYPE LOCALE PATTERN "*" EXCLUDE PATTERN "looper.pot") 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_link_libraries(liblooper PUBLIC gtest_main)
target_compile_definitions(liblooper PUBLIC TESTS) target_compile_definitions(liblooper PUBLIC TESTS)
endif() 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": { "cmakeMinimumRequired": {
"major": 3, "major": 3,
"minor": 23, "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 GenericName=Looping audio player
Exec=looper -n %f Exec=looper -n %f
Icon=looper 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; 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; Categories=Audio;AudioVideo;
Terminal=false 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): def add_graphic(input: str):
GRAPHIC=path.basename(input).removesuffix(".png").lower().replace('-', '_') GRAPHIC=path.basename(input).removesuffix(".png").lower().replace('-', '_')
print("Adding graphic '%s' from file '%s'" % (GRAPHIC, input)) print("Adding graphic '%s' from file '%s'" % (GRAPHIC, input))
add_base85(input, GRAPHIC) add_basic(input, GRAPHIC)
def add_license(input: str, output: str): def add_license(input: str, output: str):
LICENSE = output + "_license" LICENSE = output + "_license"
print("Adding license '%s' (C identifier: '%s') from file '%s'" % (output, LICENSE, input)) 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(i)
add_font("ForkAwesome/fonts/forkawesome-webfont.ttf", "forkawesome") add_font("ForkAwesome/fonts/forkawesome-webfont.ttf", "forkawesome")
add_graphic("icon.png") add_graphic("icon.png")
add_graphic("catoc.png")
add_license("Noto_Sans/OFL.txt", "notosans") add_license("Noto_Sans/OFL.txt", "notosans")
add_license("Noto_Sans_JP/OFL.txt", "notosansjp") add_license("Noto_Sans_JP/OFL.txt", "notosansjp")
add_license("../LICENSE.MIT", "looper_mit") 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}) add_playback_backend(fluidsynth_backend ${BACKEND_FLUIDSYNTH_SRC})
target_include_directories(fluidsynth_backend PRIVATE ${BACKEND_FLUIDSYNTH_INC}) target_include_directories(fluidsynth_backend PRIVATE ${BACKEND_FLUIDSYNTH_INC})
find_package(OpenMP) find_package(OpenMP)
find_package(FluidSynth) find_package(PkgConfig)
target_link_libraries(fluidsynth_backend PUBLIC OpenMP::OpenMP_CXX FluidSynth::libfluidsynth) 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 <string.h>
#include <file_backend.hpp> #include <file_backend.hpp>
#include <util.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) { void FluidSynthBackend::fluidsynth_get_property_list_wrapper(void *udata, const char *name, int type) {
((FluidSynthBackend*)udata)->fluidsynth_get_property_list(name, 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_path(fmt::format("fluidsynth/{0}", name));
property.set_name(name); property.set_name(name);
switch (type) { switch (type) {
case FLUID_NO_TYPE:
case FLUID_NUM_TYPE: { case FLUID_NUM_TYPE: {
property.set_type(PropertyType::Double); 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; } break;
case FLUID_INT_TYPE: { case FLUID_INT_TYPE: {
property.set_type(PropertyType::Int); 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; } break;
case FLUID_STR_TYPE: { case FLUID_STR_TYPE: {
property.set_type(PropertyType::String); property.set_type(PropertyType::String);
} break; } break;
default: {
throw std::exception();
} break;
} }
property.set_id(PropertyId::BackendSpecific); property.set_id(PropertyId::BackendSpecific);
fluidsynth_properties.push_back(property); fluidsynth_properties.push_back(property);
} }
static bool log_fn_registered = false;
std::vector<Property> FluidSynthBackend::get_property_list() { std::vector<Property> FluidSynthBackend::get_property_list() {
return fluidsynth_properties; return fluidsynth_properties;
} }
void FluidSynthBackend::load(const char *filename) { 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; current_file = filename;
spec.format = AUDIO_F32SYS; spec.format = AUDIO_F32SYS;
spec.samples = 100; spec.samples = 100;
spec.channels = 2; spec.channels = 2;
spec.freq = 48000; spec.freq = 96000;
spec.size = 100 * 2 * sizeof(int16_t); spec.size = 100 * 2 * sizeof(int16_t);
file = open_file(filename); File *file = open_file(filename);
this->settings = new_fluid_settings(); settings = new_fluid_settings();
fluid_settings_foreach(settings, (void*)this, &fluidsynth_get_property_list_wrapper); 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; extern SDL_AudioSpec obtained;
void FluidSynthBackend::switch_stream(int idx) { void FluidSynthBackend::switch_stream(int idx) {
fluid_player_seek(player, 0);
open = true;
} }
void FluidSynthBackend::cleanup() { void FluidSynthBackend::cleanup() {
delete file; delete_fluid_player(player);
file = nullptr; 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 FluidSynthBackend::render(void *buf, size_t maxlen) {
size_t sample_type_len = 2; size_t sample_type_len = sizeof(float);
maxlen /= sample_type_len; maxlen /= sample_type_len * 2;
maxlen *= sample_type_len; position += maxlen;
return copied; maxlen *= sample_type_len * 2;
} if (fluid_synth_write_float(synth, maxlen / 2 / sample_type_len, buf, 0, 2, buf, 1, 2) == FLUID_FAILED) {
void FluidSynthBackend::seek(double position) { 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; return position;
} }
int FluidSynthBackend::get_stream_idx() { int FluidSynthBackend::get_stream_idx() {
return 0; 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 <SDL.h>
#include "file_backend.hpp" #include "file_backend.hpp"
#include <fluidsynth.h> #include <fluidsynth.h>
#include <vector>
class FluidSynthBackend : public PlaybackBackend { 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); static void fluidsynth_get_property_list_wrapper(void *udata, const char *name, int type);
std::vector<Property> fluidsynth_properties; std::vector<Property> fluidsynth_properties;
fluid_settings_t *settings; 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); void fluidsynth_get_property_list(const char *name, int type);
public: public:
void set_fluidsynth_property_str(std::string path, std::string val); 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_num(std::string path, double val);
void set_fluidsynth_property_int(std::string path, int val); void set_fluidsynth_property_int(std::string path, int val);
std::string get_fluidsynth_property_str(std::string path); std::optional<std::string> get_fluidsynth_property_str(std::string path);
double get_fluidsynth_property_num(std::string path); std::optional<double> get_fluidsynth_property_num(std::string path);
int get_fluidsynth_property_int(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 { inline std::string get_id() override {
return "fluidsynth"; return "fluidsynth";
} }
@ -32,15 +127,12 @@ class FluidSynthBackend : public PlaybackBackend {
return "MIDI player"; return "MIDI player";
} }
std::vector<Property> get_property_list() 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 load(const char *filename) override;
void switch_stream(int idx) override; void switch_stream(int idx) override;
void cleanup() override; void cleanup() override;
int get_stream_idx() override; int get_stream_idx() override;
size_t render(void *buf, size_t maxlen) override; size_t render(void *buf, size_t maxlen) override;
double get_position() override; uint64_t get_position_samples() override;
inline double get_length() override {
return length;
}
inline ~FluidSynthBackend() 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,123 @@
#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>
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;
tmp *= spec.freq;
tmp /= 1000;
this->length = tmp;
this->loop_len = info->loop_length;
this->loop_start = info->intro_length;
if (info->song[0] == '\0') {
this->current_title = {};
} else {
this->current_title = info->song;
}
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,44 @@
#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;
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;
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(pcm_volume, 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); current_file = std::string(filename);
const char *title_tag = Mix_GetMusicTitleTag(output); const char *title_tag = Mix_GetMusicTitleTag(output);
// Check for an empty string, which indicates there's no title tag. // Check for an empty string, which indicates there's no title tag.
if (title_tag[0] == '\0') { if (title_tag[0] != '\0') {
std::filesystem::path path(current_file);
current_title = path.stem().string();
} else {
current_title = std::string(title_tag); current_title = std::string(title_tag);
} }
this->music = output; this->music = output;
@ -73,9 +70,9 @@ void SDLMixerXBackend::load(const char *filename) {
PlaybackStream stream; PlaybackStream stream;
stream.id = 0; stream.id = 0;
stream.length = Mix_MusicDuration(output); stream.length = Mix_MusicDuration(output);
stream.name = current_title;
streams.push_back(stream);
open = true; open = true;
stream.name = get_title().value();
streams.push_back(stream);
initial = true; initial = true;
} }
void SDLMixerXBackend::switch_stream(int idx) { void SDLMixerXBackend::switch_stream(int idx) {
@ -113,7 +110,7 @@ size_t SDLMixerXBackend::render(void *buf, size_t maxlen) {
} }
return i; return i;
} }
double SDLMixerXBackend::get_position() { double SDLMixerXBackend::get_position_time() {
if (music == nullptr || file == nullptr) { if (music == nullptr || file == nullptr) {
open = false; open = false;
return 0.0; return 0.0;

View file

@ -7,6 +7,7 @@ class SDLMixerXBackend : public PlaybackBackend {
Mix_CommonMixer_t mixer; Mix_CommonMixer_t mixer;
File *file; File *file;
bool initial = false; bool initial = false;
double length;
public: public:
inline std::string get_id() override { inline std::string get_id() override {
return "sdl_mixer_x"; return "sdl_mixer_x";
@ -15,8 +16,20 @@ class SDLMixerXBackend : public PlaybackBackend {
return "SDL Mixer X"; return "SDL Mixer X";
} }
std::optional<uint64_t> get_max_samples() override; 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; 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 load(const char *filename) override;
void switch_stream(int idx) override; void switch_stream(int idx) override;
void cleanup() 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); size_t new_samples = render_vgmstream((sample_t*)(buf), (int)maxlen, vf);
if (maxlen > new_samples) { if (maxlen > new_samples) {
reset_vgmstream(vf); reset_vgmstream(vf);
position = 0.0; position = 0;
} else { } else {
position += (double)new_samples / (double)vf->sample_rate; position += new_samples;
} }
return new_samples * sizeof(sample_t); 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) { if (vf == nullptr || sf == nullptr || file == nullptr) {
open = false; open = false;
return; return;
} }
auto pos32 = (int32_t)(position * vf->sample_rate); auto pos32 = (int32_t)(position);
seek_vgmstream(vf, pos32); seek_vgmstream(vf, pos32);
this->position = position; this->position = position;
} }
double VgmStreamBackend::get_position() { uint64_t VgmStreamBackend::get_position_samples() {
return position; return position;
} }
int VgmStreamBackend::get_stream_idx() { int VgmStreamBackend::get_stream_idx() {

View file

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

View file

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

View file

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

View file

@ -0,0 +1,96 @@
#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 < 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 <cmath>
#include <fmt/core.h> #include <fmt/core.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <LayoutItem.h>
#include <TranslationUtils.h>
#include <SDL.h> #include <SDL.h>
#include <cats.hpp>
#include "icons.h" #include "icons.h"
#include "utils.h" #include "utils.h"
#include "aboutwindow.h" #include "aboutwindow.h"
@ -60,6 +63,7 @@ void HaikuLooperWindow::UpdateViewFlags(BLayout *layout) {
} }
UpdateViewFlags(owner); UpdateViewFlags(owner);
} }
HaikuLooperWindow::HaikuLooperWindow(Playback *playback) : BWindow(BRect(100, 100, 500, 100), "Looper", B_TITLED_WINDOW, 0) { HaikuLooperWindow::HaikuLooperWindow(Playback *playback) : BWindow(BRect(100, 100, 500, 100), "Looper", B_TITLED_WINDOW, 0) {
pause_bitmap = load_icon(ICON_PAUSE); pause_bitmap = load_icon(ICON_PAUSE);
resume_bitmap = load_icon(ICON_PLAY); 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(file_menu);
menu_bar->AddItem(help_menu); menu_bar->AddItem(help_menu);
layout->AddView(menu_bar); 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->SetExplicitPreferredSize(BSize(0, 0));
spacer->SetExplicitMinSize(BSize(0, 0)); spacer->SetExplicitMinSize(BSize(0, 0));
layout->AddView(spacer); 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 = new BButton(NULL, new BMessage(CMD_RESTART));
restart_btn->SetTarget(this); restart_btn->SetTarget(this);
top_row->AddChild(restart_btn); 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 = 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->SetModificationMessage(make_slider_msg(CMD_SEEK, true));
slider->SetTarget(this); 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 = new BButton(NULL, new BMessage(CMD_STOP));
stop_btn->SetTarget(this); stop_btn->SetTarget(this);
top_row->AddChild(stop_btn); top_row->AddChild(stop_btn);
@ -148,6 +164,32 @@ HaikuLooperWindow::HaikuLooperWindow(Playback *playback) : BWindow(BRect(100, 10
MessageReceived(msg); MessageReceived(msg);
delete 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() { HaikuLooperWindow::~HaikuLooperWindow() {
delete ref_handler; delete ref_handler;
delete pause_bitmap; delete pause_bitmap;
@ -232,6 +274,13 @@ void HaikuLooperWindow::MessageReceived(BMessage *msg) {
case CMD_PREFS: { case CMD_PREFS: {
prefs_subwindow->Show(); prefs_subwindow->Show();
} break; } 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: { case CMD_UPDATE_LABEL_SETTING: {
volume_slider->MessageReceived(msg); volume_slider->MessageReceived(msg);
speed_slider->MessageReceived(msg); speed_slider->MessageReceived(msg);
@ -289,13 +338,22 @@ void HaikuLooperWindow::Pulse() {
auto component_count = TimeToComponentCount(len); auto component_count = TimeToComponentCount(len);
bool enable_ui = !playback->IsStopped(); bool enable_ui = !playback->IsStopped();
if (enable_ui) { if (enable_ui) {
slider->SetLabel(fmt::format("Position: {}", TimeToString(pos, component_count)).c_str()); if (len <= 0.0) {
slider->SetLimitLabels(TimeToString(0, component_count).c_str(), TimeToString(len, component_count).c_str()); 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_icons) pause_resume_btn->SetIcon(playback->IsPaused() ? resume_bitmap : pause_bitmap);
if (show_labels) pause_resume_btn->SetLabel(playback->IsPaused() ? "Resume" : "Pause"); if (show_labels) pause_resume_btn->SetLabel(playback->IsPaused() ? "Resume" : "Pause");
} else { } else {
slider->SetLabel("Position"); position_label->SetText("Stopped.");
slider->SetLimitLabels("N/A", "N/A"); slider->Hide();
position_label->Show();
if (show_icons) pause_resume_btn->SetIcon(pause_bitmap); if (show_icons) pause_resume_btn->SetIcon(pause_bitmap);
if (show_labels) pause_resume_btn->SetLabel("Pause"); if (show_labels) pause_resume_btn->SetLabel("Pause");
} }

View file

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

View file

@ -7,16 +7,50 @@
#include <Box.h> #include <Box.h>
#include <map> #include <map>
#include "main_window.h" #include "main_window.h"
#include <cats.hpp>
#include <TranslationUtils.h>
#include "image_view.h"
using namespace Looper::Options; using namespace Looper::Options;
#define CMD_UPDATE_LABEL_SETTING 0x1000 #define CMD_UPDATE_LABEL_SETTING 0x1000
#define CMD_FRONTEND 0x1001 #define CMD_FRONTEND 0x1001
#define CMD_SET_SETTING 0x1002 #define CMD_SET_SETTING 0x1002
#define CMD_REVERT 0x1003 #define CMD_REVERT 0x1003
#define CMD_APPLY 0x1004 #define CMD_APPLY 0x1004
#define CMD_SET_SETTING_CHECKBOX 0x1005
bool show_icons, show_labels; 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) { 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; 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); auto *root_layout = new BGroupLayout(B_VERTICAL);
SetLayout(root_layout); SetLayout(root_layout);
root_layout->SetSpacing(0.0); 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"); BBox *box = new BBox("prefs:label_settings_box");
box->SetLabel("Labels and Icons"); box->SetLabel("Labels and Icons");
auto *label_settings_group = new BGroupView(B_VERTICAL); auto *label_settings_group = new BGroupView(B_VERTICAL);
box->AddChild(label_settings_group);
BGroupLayout *label_settings_layout = label_settings_group->GroupLayout(); BGroupLayout *label_settings_layout = label_settings_group->GroupLayout();
BMessage *labels_only_msg = new BMessage(CMD_SET_SETTING); BMessage *labels_only_msg = new BMessage(CMD_SET_SETTING);
labels_only_msg->AddString("pref_path", "ui.haiku.label_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(labels_only);
label_settings_layout->AddView(icons_only); label_settings_layout->AddView(icons_only);
label_settings_layout->AddView(both_labels_icons); 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); 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); BGroupView *btn_view = new BGroupView(B_HORIZONTAL);
BGroupLayout *btn_box = btn_view->GroupLayout(); BGroupLayout *btn_box = btn_view->GroupLayout();
revert_btn = new BButton("prefs:revert", "Revert", new BMessage(CMD_REVERT)); revert_btn = new BButton("prefs:revert", "Revert", new BMessage(CMD_REVERT));
@ -102,10 +178,17 @@ void HaikuPrefsWindow::MessageReceived(BMessage *msg) {
else restart_warning->Hide(); else restart_warning->Hide();
set_option<std::string>("ui.haiku.label_setting", new_label_setting); set_option<std::string>("ui.haiku.label_setting", new_label_setting);
update_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); 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; } break;
case CMD_REVERT: { case CMD_REVERT: {
set_options_changed(false); set_options_changed(false);
load_options();
new_label_setting = get_option<std::string>("ui.haiku.label_setting", "icons"); new_label_setting = get_option<std::string>("ui.haiku.label_setting", "icons");
new_frontend = get_option<std::string>("ui.frontend", "haiku"); new_frontend = get_option<std::string>("ui.frontend", "haiku");
if (new_frontend != "haiku") restart_warning->Show(); if (new_frontend != "haiku") restart_warning->Show();
@ -117,8 +200,16 @@ void HaikuPrefsWindow::MessageReceived(BMessage *msg) {
break; 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(); update_label_setting();
next_handler->PostMessage(CMD_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; } break;
case CMD_FRONTEND: { case CMD_FRONTEND: {
auto option = msg->GetInt32("be:value", cur_option); 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") { if (std::string(setting) == "ui.haiku.label_setting") {
new_label_setting = setting_value; new_label_setting = setting_value;
set_options_changed(true); 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 <vector>
#include <string> #include <string>
#include <map> #include <map>
#define CMD_SET_CAT 0x1010
class HaikuPrefsWindow : public BWindow { class HaikuPrefsWindow : public BWindow {
std::vector<std::string> backend_ids; 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; int32 cur_option = 0;
BLooper *next_handler; BLooper *next_handler;
BStringView *restart_warning; BStringView *restart_warning;

View file

@ -17,6 +17,8 @@
#include <log.hpp> #include <log.hpp>
#include <options.hpp> #include <options.hpp>
#include <web_functions.hpp> #include <web_functions.hpp>
#include <fmt/core.h>
#include <fmt/format.h>
using std::vector; using std::vector;
using namespace Looper::Options; using namespace Looper::Options;
void RendererBackend::on_resize() { void RendererBackend::on_resize() {
@ -151,13 +153,23 @@ void RendererBackend::LoopFunction() {
ImGui::NewFrame(); ImGui::NewFrame();
// Run the GUI // Run the GUI
GuiFunction(); GuiFunction();
if (!main_menu_bar_used) {
BeginMainMenuBar();
EndMainMenuBar();
}
// Rendering // Rendering
ImGui::Render(); 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_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); SDL_RenderClear(rend);
// Tell ImGui to render. // Tell ImGui to render.
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), rend); 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. // Swap the buffers, and do VSync if enabled.
SDL_RenderPresent(rend); SDL_RenderPresent(rend);
@ -212,7 +224,8 @@ RendererBackend::~RendererBackend() {
renderer_backend = nullptr; renderer_backend = nullptr;
} }
void RendererBackend::SetWindowTitle(const char *title) { void RendererBackend::SetWindowTitle(const char *title) {
SDL_SetWindowTitle(window, title); this->title_text = title;
update_real_title();
} }
void RendererBackend::GuiFunction() { void RendererBackend::GuiFunction() {
// Do nothing by default. // Do nothing by default.
@ -291,6 +304,10 @@ static EM_BOOL resize_callback(int event_type, const EmscriptenUiEvent *event, v
return EM_FALSE; return EM_FALSE;
} }
#endif #endif
static SDL_HitTestResult hit_test(SDL_Window *window, const SDL_Point *area, void *data) {
return ((RendererBackend*)data)->HitTest(window, area);
}
void RendererBackend::BackendInit() { void RendererBackend::BackendInit() {
setup_locale("neko_player"); setup_locale("neko_player");
DEBUG.writefln("Loaded locale '%s' from '%s'...", CURRENT_LANGUAGE, LOCALE_DIR); DEBUG.writefln("Loaded locale '%s' from '%s'...", CURRENT_LANGUAGE, LOCALE_DIR);
@ -319,7 +336,7 @@ void RendererBackend::BackendInit() {
#ifdef SDL_HINT_IME_SHOW_UI #ifdef SDL_HINT_IME_SHOW_UI
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
#endif #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); SDL_CreateWindowAndRenderer(window_width, window_height, window_flags, &window, &rend);
#ifndef __ANDROID__ #ifndef __ANDROID__
@ -329,8 +346,8 @@ void RendererBackend::BackendInit() {
} }
#endif #endif
SDL_EventState(SDL_DROPFILE, SDL_ENABLE); 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, icon_size), 1);
SDL_Surface* icon = IMG_Load_RW(SDL_RWFromConstMem(icon_data.data(), icon_data.size()), 1); icon_texture = SDL_CreateTextureFromSurface(rend, icon);
SDL_SetWindowIcon(window, icon); SDL_SetWindowIcon(window, icon);
// Setup Dear ImGui context // Setup Dear ImGui context
@ -390,10 +407,127 @@ void RendererBackend::BackendInit() {
SDL_RenderSetVSync(rend, vsync ? 1 : 0); SDL_RenderSetVSync(rend, vsync ? 1 : 0);
#endif #endif
theme->Apply(accent_color, (float)scale); theme->Apply(accent_color, (float)scale);
SDL_SetWindowHitTest(window, &hit_test, this);
Init(); Init();
SDL_ShowWindow(window); SDL_ShowWindow(window);
started = true; started = true;
} }
bool RendererBackend::BeginMainMenuBar() {
main_menu_bar_used = true;
if (ImGui::BeginMainMenuBar()) {
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;
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__
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() { int RendererBackend::Run() {
framerate = 60; framerate = 60;
started = false; started = false;
@ -403,7 +537,8 @@ int RendererBackend::Run() {
#else #else
try { try {
BackendInit(); BackendInit();
} catch (std::exception) { } catch (std::exception &e) {
ERROR.writefln("Error occurred during initialization: %s", e.what());
return -1; return -1;
} }
started = true; started = true;

View file

@ -25,7 +25,19 @@ class RendererBackend {
bool resize_needed = true; bool resize_needed = true;
void on_resize(); void on_resize();
bool update_scale = false; 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;
void update_real_title();
public: 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<bool> touchScreenModeOverride;
std::optional<double> scaleOverride; std::optional<double> scaleOverride;
bool isTouchScreenMode(); bool isTouchScreenMode();
@ -46,6 +58,10 @@ class RendererBackend {
const char *prefPath; const char *prefPath;
ImVec4 accent_color = ImVec4(0.75, 1.0, 1.0, 1.0); ImVec4 accent_color = ImVec4(0.75, 1.0, 1.0, 1.0);
int Run(); int Run();
void SetSubtitle(const char *subtitle);
inline void ClearSubtitle() {
SetSubtitle("");
}
void SetWindowTitle(const char *title); void SetWindowTitle(const char *title);
virtual void Init(); virtual void Init();
virtual void GuiFunction(); virtual void GuiFunction();
@ -57,6 +73,8 @@ class RendererBackend {
void AddFonts(); void AddFonts();
void SetWindowSize(int w, int h); void SetWindowSize(int w, int h);
void GetWindowsize(int *w, int *h); void GetWindowsize(int *w, int *h);
bool BeginMainMenuBar();
void EndMainMenuBar();
RendererBackend(); RendererBackend();
virtual ~RendererBackend(); virtual ~RendererBackend();
friend void main_loop(); friend void main_loop();

View file

@ -7,7 +7,9 @@
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include "ui_backend.hpp" #include "ui_backend.hpp"
#include "thirdparty/CLI11.hpp" #include "thirdparty/CLI11.hpp"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include <web_functions.hpp> #include <web_functions.hpp>
#include <cats.hpp>
using namespace Looper::Options; using namespace Looper::Options;
void MainLoop::Init() { void MainLoop::Init() {
#ifdef PORTALS #ifdef PORTALS
@ -27,6 +29,25 @@ void MainLoop::Init() {
theme_editor = false; theme_editor = false;
stopped = true; stopped = true;
about_window = false; 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; string lang;
{ {
Json::Value config; 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); 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); 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(); Theme::updateAvailableThemes();
if (Theme::availableThemes.empty()) { if (Theme::availableThemes.empty()) {
path lightPath = Theme::themeDir / "light.toml"; path lightPath = Theme::themeDir / "light.toml";
@ -128,18 +154,57 @@ void MainLoop::Init() {
} }
} }
theme->Apply(accent_color, (float)scale); theme->Apply(accent_color, (float)scale);
SetWindowTitle("Looper");
FileLoaded(); FileLoaded();
} }
void MainLoop::Drop(std::string file) { void MainLoop::Drop(std::string file) {
LoadFile(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() { void MainLoop::FileLoaded() {
auto file_maybe = playback->get_current_title(); auto file_maybe = playback->get_current_title();
if (file_maybe.has_value()) { if (file_maybe.has_value()) {
auto name = file_maybe.value(); auto name = file_maybe.value();
SetWindowTitle((name + std::string(" - Looper")).c_str()); SetSubtitle(name.c_str());
} else { } else {
SetWindowTitle("Looper"); ClearSubtitle();
} }
streams = playback->get_streams(); streams = playback->get_streams();
properties = playback->get_property_list(); properties = playback->get_property_list();
@ -161,7 +226,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!). // 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) if (show_demo_window)
ImGui::ShowDemoWindow(&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::BeginMenu(_TRI_CTX(ICON_FK_FILE, "Main menu", "File"))) {
if (ImGui::MenuItem(_TRI_CTX(ICON_FK_FOLDER_OPEN, "Main menu | File", "Open"))) { 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. // Set translatable strings here so that they are in the correct language even when it changes at runtime.
@ -210,11 +275,22 @@ void MainLoop::GuiFunction() {
} }
ImGui::EndMenu(); ImGui::EndMenu();
} }
ImGui::EndMainMenuBar(); EndMainMenuBar();
} }
ImGui::SetNextWindowDockID(dockid); ImGui::SetNextWindowDockID(dockid);
ImGui::Begin(_TRI_CTX(ICON_FK_PLAY, "Main window title", "Player"), nullptr, 0); 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 x_size = y_pos * aspect;
float x_pos = ImGui::GetWindowWidth() - ImGui::GetStyle().WindowPadding.x - x_size;
ImGui::SetCursorPosX(x_pos);
ImGui::Image((ImTextureID)cat, ImVec2(x_size, y_pos));
}
float centerSpace = ImGui::GetWindowHeight() - ImGui::GetFrameHeightWithSpacing() - ImGui::GetFrameHeight() - ImGui::GetStyle().WindowPadding.y; float centerSpace = ImGui::GetWindowHeight() - ImGui::GetFrameHeightWithSpacing() - ImGui::GetFrameHeight() - ImGui::GetStyle().WindowPadding.y;
if (streams.size() > 0) { if (streams.size() > 0) {
static string filter = ""; static string filter = "";
@ -254,7 +330,7 @@ void MainLoop::GuiFunction() {
} }
ImGui::EndChildFrame(); 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")) { if (ImGui::Button(playback->IsPaused() ? ICON_FK_PLAY "##Pause" : ICON_FK_PAUSE "##Pause")) {
playback->Pause(); playback->Pause();
} }
@ -312,6 +388,58 @@ void MainLoop::GuiFunction() {
ImGui::PushID(property.path().c_str()); ImGui::PushID(property.path().c_str());
bool valid = false; bool valid = false;
switch (property.type()) { 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: { case PropertyType::Double: {
std::optional<double> min; std::optional<double> min;
std::optional<double> max; std::optional<double> max;
@ -371,6 +499,20 @@ void MainLoop::GuiFunction() {
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Set")) { if (ImGui::Button("Set")) {
switch (property.type()) { 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: { case PropertyType::Double: {
DoubleProperty property_d; DoubleProperty property_d;
property_d.set_value(double_properties[property.path()]); property_d.set_value(double_properties[property.path()]);
@ -450,6 +592,54 @@ void MainLoop::GuiFunction() {
set_option<std::string>("ui.imgui.lang", lang); 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) { if (override_lang) {
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - (ImGui::GetFontSize()) - ((ImGui::GetStyle().ItemSpacing.x + (ImGui::GetStyle().FramePadding.x * 2.0f))) - (ImGui::GetStyle().WindowPadding.x)); ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - ImGui::GetCursorPosX() - (ImGui::GetFontSize()) - ((ImGui::GetStyle().ItemSpacing.x + (ImGui::GetStyle().FramePadding.x * 2.0f))) - (ImGui::GetStyle().WindowPadding.x));
@ -562,6 +752,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::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::TextUnformatted(VER_STRING.c_str());
ImGui::NewLine(); ImGui::NewLine();
ImGui::Text("SDL video driver: %s", SDL_GetCurrentVideoDriver());
ImGui::NewLine();
auto &license_data = get_license_data(); auto &license_data = get_license_data();
// Left // Left
static LicenseData selected = *license_data.begin(); static LicenseData selected = *license_data.begin();
@ -622,13 +814,18 @@ void MainLoop::LoadFile(std::string file) {
playback->Start(file); playback->Start(file);
} }
void MainLoop::Deinit() { void MainLoop::Deinit() {
for (auto kv : cats) {
SDL_DestroyTexture(kv.second);
}
cats.clear();
{ {
path themePath(theme->file_path); path themePath(theme->file_path);
themePath = themePath.stem(); themePath = themePath.stem();
if (!themePath.empty()) { if (!themePath.empty()) {
set_option<std::string>("ui.imgui.theme", themePath.string()); 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.h", accent_color.x);
set_option<double>("ui.imgui.accent_color.s", accent_color.y); set_option<double>("ui.imgui.accent_color.s", accent_color.y);
set_option<double>("ui.imgui.accent_color.v", accent_color.z); set_option<double>("ui.imgui.accent_color.v", accent_color.z);
@ -664,6 +861,10 @@ void ImGuiUIBackend::QuitHandler() {
// Main code // Main code
int ImGuiUIBackend::run(std::vector<std::string> realArgs, int argc, char** argv) 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); int possible_error = UIBackend::run(realArgs, argc, argv);
if (possible_error != 0) { if (possible_error != 0) {
return possible_error; return possible_error;

View file

@ -36,6 +36,7 @@
using namespace std::filesystem; using namespace std::filesystem;
using std::string; using std::string;
#define IMGUI_FRONTEND #define IMGUI_FRONTEND
class MainLoop : public RendererBackend { class MainLoop : public RendererBackend {
bool show_demo_window = false; bool show_demo_window = false;
FileBrowser fileDialog = FileBrowser(false); FileBrowser fileDialog = FileBrowser(false);
@ -49,14 +50,24 @@ class MainLoop : public RendererBackend {
bool property_editor = false; bool property_editor = false;
bool restart_needed = false; bool restart_needed = false;
bool stopped = true; bool stopped = true;
bool enable_cat = false;
std::string cat_setting = "__default__";
std::vector<UIBackend*> backends; std::vector<UIBackend*> backends;
UIBackend *cur_backend; UIBackend *cur_backend;
friend class ImGuiUIBackend; friend class ImGuiUIBackend;
std::atomic_bool exit_flag; std::atomic_bool exit_flag;
std::map<std::string, bool> boolean_properties; std::map<std::string, bool> boolean_properties;
std::map<std::string, double> double_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<Property> properties;
std::vector<PlaybackStream> streams; 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: public:
Playback *playback; Playback *playback;
vector<std::string> args; vector<std::string> args;

View file

@ -20,6 +20,7 @@ const char* Theme::prefPath = NULL;
path Theme::themeDir = path(); path Theme::themeDir = path();
std::set<path> Theme::availableThemes = std::set<path>(); std::set<path> Theme::availableThemes = std::set<path>();
std::map<path, ThemeStrings> Theme::themeStrings = std::map<path, ThemeStrings>(); std::map<path, ThemeStrings> Theme::themeStrings = std::map<path, ThemeStrings>();
Theme *Theme::cur_theme = nullptr;
ImVec4 change_accent_color(ImVec4 in, float hue) { ImVec4 change_accent_color(ImVec4 in, float hue) {
if (in.x == in.y && in.y == in.z) { 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::BeginChild("##colors", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened);
ImGui::PushItemWidth(-160); 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)) if (!filter.PassFilter(name))
continue; continue;
ImGui::PushID(i); 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 | 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 | 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::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::SameLine(0.0f, style.ItemInnerSpacing.x);
ImGui::TextUnformatted(name); ImGui::TextUnformatted(name);
ImGui::PopID(); 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.WindowMinSize.y = MAX(actual_style.WindowMinSize.y, 1.0);
actual_style.CurveTessellationTol = MAX(actual_style.CurveTessellationTol, 0.1); actual_style.CurveTessellationTol = MAX(actual_style.CurveTessellationTol, 0.1);
actual_style.CircleTessellationMaxError = MAX(actual_style.CircleTessellationMaxError, 0.1); actual_style.CircleTessellationMaxError = MAX(actual_style.CircleTessellationMaxError, 0.1);
cur_theme = this;
} }
void Theme::Save(string path) { void Theme::Save(string path) {
INFO.writefln("Saving theme to %s...", path.c_str()); INFO.writefln("Saving theme to %s...", path.c_str());
@ -440,11 +444,18 @@ void Theme::Save(string path) {
} }
{ {
toml::table colors; 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); const char *name;
ImVec4 color = style.Colors[i]; ImVec4 color;
toml::table colorValue; 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("r", color.x);
colorValue.insert("g", color.y); colorValue.insert("g", color.y);
colorValue.insert("b", color.z); colorValue.insert("b", color.z);
@ -465,6 +476,20 @@ void Theme::Save(string path) {
} }
updateAvailableThemes(); 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) { void Theme::Save(path path) {
Save((string)path.string()); Save((string)path.string());
} }
@ -535,9 +560,16 @@ Theme::Theme(bool dark) : Theme() {
ImGui::StyleColorsLight(&style); ImGui::StyleColorsLight(&style);
style.FrameBorderSize = 1; 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(); auto colorizer = AccentColorizer();
if (color.x != color.y || color.y != color.z) { if (color.x != color.y || color.y != color.z) {
colorizer.Hue = true; colorizer.Hue = true;
@ -762,14 +794,27 @@ Theme::Theme(string path) : Theme() {
} }
if (config.contains("colors")) { if (config.contains("colors")) {
toml::table colors = *config["colors"].as_table(); 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)) { if (colors.contains(name)) {
toml::table colorValue = *colors[name].as_table(); 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()); 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()); 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();
ThemeStrings(toml::table config); ThemeStrings(toml::table config);
}; };
enum LooperCol {
LooperCol_Subtitle,
// LooperCol_Title,
// LooperCol_WindowBorder,
LooperCol_COUNT
};
struct AccentColorizer { struct AccentColorizer {
/// @brief Whether or not to change the hue. /// @brief Whether or not to change the hue.
@ -51,7 +57,11 @@ class Theme {
static const char* prefPath; static const char* prefPath;
static const int MinSchemaVersion = 0; static const int MinSchemaVersion = 0;
static const int MaxSchemaVersion = 1; static const int MaxSchemaVersion = 1;
static Theme *cur_theme;
ImVec4 Colors[LooperCol_COUNT];
string file_path; string file_path;
static ImVec4 GetColor(int color);
const char *GetStyleColorName(int color);
std::map<string, ThemeStrings> strings; std::map<string, ThemeStrings> strings;
std::map<int, AccentColorizer> AccentColorizers; std::map<int, AccentColorizer> AccentColorizers;
ThemeStrings GetStrings(); ThemeStrings GetStrings();
@ -63,4 +73,4 @@ class Theme {
Theme(bool dark); Theme(bool dark);
Theme(string path); Theme(string path);
Theme(path path); Theme(path path);
}; };

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}) set(BACKEND_QT_SRC ${BACKEND_QT_SRC} ${CMAKE_CURRENT_SOURCE_DIR}/${SRC})
endforeach() endforeach()
set(BACKEND_QT_INC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) set(BACKEND_QT_INC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
set(CMAKE_AUTOMOC ON)
add_ui_backend(qt_ui ${BACKEND_QT_SRC}) add_ui_backend(qt_ui ${BACKEND_QT_SRC})
set_target_properties(qt_ui PROPERTIES AUTOMOC ON)
find_package(Qt6 COMPONENTS Core Gui Widgets) 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_link_libraries(qt_ui PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets SDL2::SDL2 fmt::fmt liblooper)
target_include_directories(qt_ui PRIVATE ../../..) target_include_directories(qt_ui PRIVATE ../../..)

View file

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

View file

@ -23,6 +23,7 @@ class LooperWindow : public QMainWindow {
LooperSlider *speed_slider; LooperSlider *speed_slider;
LooperSlider *tempo_slider; LooperSlider *tempo_slider;
LooperSlider *pitch_slider; LooperSlider *pitch_slider;
QPixmap *cat_pixmap = NULL;
std::thread *update_thread = nullptr; std::thread *update_thread = nullptr;
bool done = false; bool done = false;
void Pulse(); void Pulse();
@ -41,7 +42,12 @@ class LooperWindow : public QMainWindow {
QAction *about_item; QAction *about_item;
QFileDialog *file_dialog; QFileDialog *file_dialog;
QBoxLayout *root_layout; QBoxLayout *root_layout;
QLabel *cat_disp;
void update_label_setting(bool labels_visible, bool icons_visible); 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: public:
AboutWindow *about_window; AboutWindow *about_window;
PrefsWindow *prefs_window; PrefsWindow *prefs_window;

View file

@ -1,10 +1,34 @@
#include "preferences.h" #include "preferences.h"
#include <QMenu> #include <QMenu>
#include <QBoxLayout> #include <QBoxLayout>
#include <QBuffer>
#include <QImage>
#include <QDataStream>
#include <QButtonGroup>
#include <QGroupBox>
#include <backend.hpp> #include <backend.hpp>
#include <options.hpp> #include <options.hpp>
#include <cats.hpp>
#include <log.hpp>
using namespace Looper::Options; using namespace Looper::Options;
PrefsWindow::PrefsWindow() { 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); auto *root_layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
this->setLayout(root_layout); this->setLayout(root_layout);
restart_warning = new QLabel("A restart is needed to apply some changes.", this); restart_warning = new QLabel("A restart is needed to apply some changes.", this);
@ -26,8 +50,7 @@ PrefsWindow::PrefsWindow() {
} }
frontend_btn->setMenu(frontend_menu); frontend_btn->setMenu(frontend_menu);
root_layout->addWidget(frontend_btn); root_layout->addWidget(frontend_btn);
QFrame *frame = new QFrame(this); QGroupBox *frame = new QGroupBox("Labels and Icons", this);
frame->setWindowTitle("Labels and Icons");
auto *label_settings_group = new QBoxLayout(QBoxLayout::TopToBottom, this); auto *label_settings_group = new QBoxLayout(QBoxLayout::TopToBottom, this);
frame->setLayout(label_settings_group); frame->setLayout(label_settings_group);
labels_only = new QRadioButton("Labels Only", frame); labels_only = new QRadioButton("Labels Only", frame);
@ -49,6 +72,40 @@ PrefsWindow::PrefsWindow() {
label_settings_group->addWidget(icons_only); label_settings_group->addWidget(icons_only);
label_settings_group->addWidget(both_labels_icons); label_settings_group->addWidget(both_labels_icons);
root_layout->addWidget(frame); 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); QWidget *btn_view = new QWidget(this);
QBoxLayout *btn_box = new QBoxLayout(QBoxLayout::LeftToRight, this); QBoxLayout *btn_box = new QBoxLayout(QBoxLayout::LeftToRight, this);
revert_btn = new QPushButton("Revert", btn_view); revert_btn = new QPushButton("Revert", btn_view);
@ -58,6 +115,7 @@ PrefsWindow::PrefsWindow() {
btn_view->setLayout(btn_box); btn_view->setLayout(btn_box);
btn_box->addWidget(revert_btn); btn_box->addWidget(revert_btn);
btn_box->addWidget(apply_btn); btn_box->addWidget(apply_btn);
btn_view->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
root_layout->addWidget(btn_view); root_layout->addWidget(btn_view);
revert(); revert();
setWindowTitle("Looper Preferences"); setWindowTitle("Looper Preferences");
@ -78,13 +136,34 @@ void PrefsWindow::update_label_setting() {
} }
void PrefsWindow::revert() { void PrefsWindow::revert() {
set_options_changed(false); set_options_changed(false);
load_options();
new_label_setting = get_option<std::string>("ui.label_setting", "icons"); new_label_setting = get_option<std::string>("ui.label_setting", "icons");
new_frontend = get_option<std::string>("ui.frontend", "qt"); new_frontend = get_option<std::string>("ui.frontend", "qt");
if (new_frontend != "qt") restart_warning->show(); if (new_frontend != "qt") restart_warning->show();
else restart_warning->hide(); else restart_warning->hide();
frontend_btn->setText(new_frontend.c_str()); 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(); 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() { void PrefsWindow::apply() {
set_options_changed(false); set_options_changed(false);
set_option<std::string>("ui.label_setting", new_label_setting); set_option<std::string>("ui.label_setting", new_label_setting);
@ -93,4 +172,8 @@ void PrefsWindow::apply() {
else restart_warning->hide(); else restart_warning->hide();
frontend_btn->setText(new_frontend.c_str()); frontend_btn->setText(new_frontend.c_str());
update_label_setting(); 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; QPushButton *frontend_btn;
QMenu *frontend_menu; QMenu *frontend_menu;
std::vector<QAction*> frontend_options; std::vector<QAction*> frontend_options;
std::map<std::string, QPixmap> cats;
std::map<std::string, QRadioButton*> cat_btns;
QCheckBox *menu_icons; QCheckBox *menu_icons;
QRadioButton *labels_only; QRadioButton *labels_only;
QRadioButton *icons_only; QRadioButton *icons_only;
QRadioButton *both_labels_icons; QRadioButton *both_labels_icons;
QPushButton *revert_btn; QPushButton *revert_btn;
QPushButton *apply_btn; QPushButton *apply_btn;
QCheckBox *cat_enable;
bool enable_cat;
std::string cat_setting;
void update_label_setting(); void update_label_setting();
void set_options_changed(bool changed); void set_options_changed(bool changed);
void revert(); void revert();
void apply(); void apply();
public: public:
void send_cat_signal();
PrefsWindow(); PrefsWindow();
Q_SIGNALS: Q_SIGNALS:
void cat_set(QPixmap &img);
void cat_unset();
void settings_changed(bool use_labels, bool use_icons); 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) { void LooperSlider::SetValue(double value) {
set_value(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) { void LooperSlider::set_min(double min) {
settings_changed = true; settings_changed = true;
this->min = min; this->min = min;
@ -275,4 +280,4 @@ void LooperSlider::changeEvent(QEvent *event) {
this->slider->setEnabled(isEnabled()); this->slider->setEnabled(isEnabled());
this->text->setEnabled(isEnabled()); this->text->setEnabled(isEnabled());
} }
} }

View file

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

View file

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

View file

@ -3,8 +3,4 @@
#include <cstring> #include <cstring>
#include <string> #include <string>
// Modified from Dear ImGui // Modified from Dear ImGui
std::vector<unsigned char> DecodeBase85(const char *src); 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());
}

View file

@ -79,7 +79,7 @@ def main() -> None:
os.chdir(basedir) os.chdir(basedir)
os.makedirs("build", exist_ok=True) os.makedirs("build", exist_ok=True)
os.chdir("build") os.chdir("build")
args=["cmake", ".."] args=["cmake", "..", "-DBUILD_SDL=ON", "-DBUILD_SOUNDTOUCH=ON"]
for definition in p.cmake_vars: for definition in p.cmake_vars:
args.append("-D%s" % definition) args.append("-D%s" % definition)
ret = call(args); 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,7 +2,8 @@
#define TAG "@TAG@" #define TAG "@TAG@"
#define LOCALE_DIR "@LOCALE_DIR@" #define LOCALE_DIR "@LOCALE_DIR@"
#cmakedefine01 DEBUG_MODE_VALUE #cmakedefine01 DEBUG_MODE_VALUE
#cmakedefine01 OLD_SDBUS
#if DEBUG_MODE_VALUE==1 #if DEBUG_MODE_VALUE==1
#define DEBUG_MODE #define DEBUG_MODE
#endif #endif
#undef DEBUG_MODE_VALUE #undef DEBUG_MODE_VALUE

View file

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

View file

@ -12,6 +12,26 @@
#include <random> #include <random>
#include <thread> #include <thread>
#ifdef DBUS_ENABLED #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 DBusAPI;
class MprisAPI : public sdbus::AdaptorInterfaces<org::mpris::MediaPlayer2_adaptor, org::mpris::MediaPlayer2::Player_adaptor, org::mpris::MediaPlayer2::TrackList_adaptor, sdbus::Properties_adaptor> { 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; 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 playerInterface = "org.mpris.MediaPlayer2.Player";
const std::string trackInterface = "org.mpris.MediaPlayer2.TrackList"; const std::string trackInterface = "org.mpris.MediaPlayer2.TrackList";
inline void sendPropertiesChanged(const std::string interface, const std::initializer_list<std::string> properties) { 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) { for (auto property : properties) {
property_vec.push_back(property); property_vec.push_back(MEMBER_NAME(property));
} }
emitPropertiesChangedSignal(interface, property_vec); emitPropertiesChangedSignal(INTERFACE_NAME(interface), property_vec);
} }
public: public:
#define meta_t std::map<std::string, sdbus::Variant> #define meta_t std::map<std::string, sdbus::Variant>
#define track_id_t sdbus::ObjectPath #define track_id_t sdbus::ObjectPath
const sdbus::ObjectPath playing_track_id = "/com/complecwaft/Looper/PlayingTrack"; const sdbus::ObjectPath playing_track_id = OBJECT_PATH("/com/complecwaft/Looper/PlayingTrack");
const sdbus::ObjectPath empty_track_id = "/org/mpris/MediaPlayer2/TrackList/NoTrack"; const sdbus::ObjectPath empty_track_id = OBJECT_PATH("/org/mpris/MediaPlayer2/TrackList/NoTrack");
inline void Raise() override { } inline void Raise() override { }
void Quit() 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 "file_backend.hpp"
#include <filesystem> #include <filesystem>
#include <string.h> #include <string.h>
#include <cassert>
File::File() { File::File() {
} }
@ -56,9 +57,18 @@ bool CFile::is_open() {
return file != NULL; return file != NULL;
} }
MemFile::MemFile() { MemFile::MemFile() {
memory_owned = false;
ptr = NULL; ptr = NULL;
len = 0; 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) { void MemFile::open(const char *path) {
CFile file; CFile file;
file.open(path); file.open(path);
@ -67,9 +77,10 @@ void MemFile::open(const char *path) {
this->len = file.read(this->ptr, 1, file.get_len()); this->len = file.read(this->ptr, 1, file.get_len());
file.close(); file.close();
this->pos = 0; this->pos = 0;
memory_owned = true;
} }
void MemFile::close() { void MemFile::close() {
free(this->ptr); if (memory_owned) free(this->ptr);
free((void*)this->name); free((void*)this->name);
this->ptr = NULL; this->ptr = NULL;
this->name = NULL; this->name = NULL;

View file

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

View file

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

114
main.cpp
View file

@ -16,6 +16,9 @@
#include <image.h> #include <image.h>
#endif #endif
#include "web_functions.hpp" #include "web_functions.hpp"
#include "cats.hpp"
#include <json/value.h>
#include <chrono>
using namespace Looper; using namespace Looper;
using namespace Looper::Options; using namespace Looper::Options;
using namespace Looper::Log; using namespace Looper::Log;
@ -23,7 +26,13 @@ using namespace Looper::Log;
extern "C" { extern "C" {
void quit(); void quit();
} }
#else
#include <curl/curl.h>
#endif #endif
std::vector<CatData> &get_cat_data() {
static std::vector<CatData> data;
return data;
}
std::unordered_set<LicenseData> license_data; std::unordered_set<LicenseData> license_data;
std::unordered_set<LicenseData> &get_license_data() { std::unordered_set<LicenseData> &get_license_data() {
return 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..."); DEBUG.writeln("Loading options file...");
load_options(); 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"); 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())); UIBackend *backend = UIBackend::get_backend(ui_backend_option).value_or(UIBackend::get_backend(backend_id).value_or(UIBackend::get_first_backend()));
int output = 0; 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.add_option("-l, --log-level", LogStream::log_level, "Sets the minimum log level to display in the logs.");
app.allow_extras(); app.allow_extras();
init_logging(); init_logging();
#ifndef __EMSCRIPTEN__
//curl_global_init(CURL_GLOBAL_ALL);
#endif
try { try {
app.parse(argc, argv); app.parse(argc, argv);
executable_path = argv[0]; executable_path = argv[0];

View file

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

View file

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

View file

@ -25,6 +25,9 @@
#ifdef __WINDOWS__ #ifdef __WINDOWS__
#include <windows.h> #include <windows.h>
#endif #endif
#ifdef __HAIKU__
#include <kernel/scheduler.h>
#endif
#include <google/protobuf/message.h> #include <google/protobuf/message.h>
#include "util.hpp" #include "util.hpp"
using namespace google::protobuf; using namespace google::protobuf;
@ -181,7 +184,7 @@ PropertyDataOrError PlaybackProcessServiceImpl::Get(const GetProperty *request)
} break; } break;
case PropertyId::PositionProperty: { case PropertyId::PositionProperty: {
DoubleProperty pos; DoubleProperty pos;
pos.set_value(cur_backend->get_position()); pos.set_value(cur_backend->get_position_time());
data->mutable_value()->PackFrom(pos); data->mutable_value()->PackFrom(pos);
} break; } break;
case PropertyId::BackendSpecific: { case PropertyId::BackendSpecific: {
@ -335,6 +338,7 @@ MaybeError PlaybackProcessServiceImpl::Init(const InitCommand *cmd) {
} }
lock.set(backend.second, false); lock.set(backend.second, false);
DEBUG.writefln("Using backend: %s", backend.second->get_name().c_str()); DEBUG.writefln("Using backend: %s", backend.second->get_name().c_str());
backend.second->seek(0.0);
break; break;
} }
if (!cur_backend_lock.has_value()) { if (!cur_backend_lock.has_value()) {
@ -396,6 +400,9 @@ PlaybackProcess::PlaybackProcess(PlaybackProcess *parent) {
DEBUG.writeln("Host process address: (in-process)"); DEBUG.writeln("Host process address: (in-process)");
} }
PlaybackProcess::PlaybackProcess(std::vector<std::string> args) { 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); SDL_InitSubSystem(SDL_INIT_AUDIO);
done = false; done = false;
is_playback_process = true; is_playback_process = true;
@ -413,7 +420,11 @@ PlaybackProcess::PlaybackProcess(std::vector<std::string> args) {
} }
PlaybackProcess::PlaybackProcess(std::string filename, int idx) { PlaybackProcess::PlaybackProcess(std::string filename, int idx) {
// multi_process = Looper::Options::get_option<bool>("playback.multi_process", true); // multi_process = Looper::Options::get_option<bool>("playback.multi_process", true);
#ifdef __EMSCRIPTEN__
multi_process = false;
#else
multi_process = true; multi_process = true;
#endif
done = false; done = false;
this->done = false; this->done = false;
if (multi_process) { if (multi_process) {

View file

@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
pushd "$(dirname "$0")/subprojects/vgmstream" pushd "$(dirname "$0")/subprojects/vgmstream"
git apply ../../vgmstream.patch git apply ../../vgmstream.patch
git submodule update --init --recursive
popd 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; return output;
} }
template<> 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) { inline DynPtr resolve_value<DynPtr>(google::protobuf::Any value) {
BytesProperty *property = resolve_any<BytesProperty>(value); BytesProperty *property = resolve_any<BytesProperty>(value);
std::string output = property->value(); std::string output = property->value();
@ -527,7 +534,7 @@ inline DynPtr resolve_value<DynPtr>(google::protobuf::Any value) {
} }
template<> template<>
inline double resolve_value(google::protobuf::Any value) { inline double resolve_value<double>(google::protobuf::Any value) {
DoubleProperty *property = resolve_any<DoubleProperty>(value); DoubleProperty *property = resolve_any<DoubleProperty>(value);
double output = property->value(); double output = property->value();
delete property; delete property;