From b9bcf7086faca60a272b02573c5f81e24c26aacc Mon Sep 17 00:00:00 2001 From: Aether Date: Wed, 28 Oct 2026 01:14:31 +0000 Subject: [PATCH 01/20] feat(ryotunes): add parametric EQ and Focus Mode --- client/App.qml | 32 +++- client/Playback.qml | 2 +- client/chrome/FocusMode.qml | 275 ++++++++++++++++++++++++++++++++++ client/chrome/LyricsPanel.qml | 8 +- client/chrome/NowPlaying.qml | 30 ++++ client/chrome/SoundDialog.qml | 177 +++++++++++++++++++++- crates/core/src/state.rs | 8 + crates/player/src/lib.rs | 64 +++++++- 8 files changed, 586 insertions(+), 10 deletions(-) create mode 100644 client/chrome/FocusMode.qml diff --git a/client/App.qml b/client/App.qml index 8269ea6c..d4d7e023 100644 --- a/client/App.qml +++ b/client/App.qml @@ -37,6 +37,8 @@ Item { // The Sound dialog (tempo / pitch / reverb / bass / width). A modal Loader that only exists while // open; SoundWorker lands chrome/SoundDialog.qml and Playback.soundRequested. property bool soundOpen: false + // Focus Mode is a separate surface; existing Now Playing remains unchanged. + property bool focusOpen: false // The mini player window's visibility. shell.qml's mini PanelWindow binds its visible to this; the // title-bar and player-bar buttons toggle it, MiniPlayer clears it. @@ -52,6 +54,11 @@ Item { app.queueOpen = tab === "queue"; app.lyricsOpen = tab === "lyrics"; } + function openFocus() { + if (!Playback.now) return; + app.soundOpen = false; + app.focusOpen = true; + } function npClose() { app.nowPlayingOpen = false; app.queueOpen = false; @@ -102,6 +109,8 @@ Item { ColumnLayout { id: frame anchors.fill: parent + // Hidden, not destroyed, so playback and navigation state survive returning. + visible: !app.focusOpen spacing: 0 // Spec-9 snapshot blur: a modal captures the whole frame behind it once (layer.live: false) @@ -234,6 +243,7 @@ Item { lyricsOpen: app.lyricsOpen onTabRequested: (tab) => app.npOpenTab(tab) onCloseRequested: app.npClose() + onFocusRequested: app.openFocus() } } @@ -290,6 +300,25 @@ Item { } } + // Ryotunes Focus Mode (Riff-inspired interaction, Ryotunes-native surface). + // A separate listening surface, created only when requested; the daemon and + // existing Now Playing state are preserved when the user exits. + Loader { + id: focusLoader + anchors.fill: parent + active: app.focusOpen + source: "chrome/FocusMode.qml" + } + Connections { + target: focusLoader.item + ignoreUnknownSignals: true + function onCloseRequested(): void { app.focusOpen = false; } + } + Connections { + target: Playback + function onNowChanged(): void { if (!Playback.now) app.focusOpen = false; } + } + // ── foreground layer ──────────────────────────────────────────────────────────────── Toast { } @@ -321,9 +350,10 @@ Item { Shortcut { sequences: ["Escape"] context: Qt.WindowShortcut - enabled: palette.open || app.nowPlayingOpen + enabled: palette.open || app.focusOpen || app.nowPlayingOpen onActivated: { if (palette.open) palette.open = false; + else if (app.focusOpen) app.focusOpen = false; else app.npClose(); } } diff --git a/client/Playback.qml b/client/Playback.qml index d30ccd6f..fc5a7f32 100644 --- a/client/Playback.qml +++ b/client/Playback.qml @@ -155,7 +155,7 @@ Singleton { function setVolume(v) { return Daemon.call("set_volume", { volume: v }); } // Send the Sound dialog's effects to the daemon; its audio-fx echo updates root.audioFx. function setAudioFx(fx) { - return Daemon.call("set_audio_fx", { speed: fx.speed, semitones: fx.semitones, reverb: fx.reverb, bass: fx.bass, width: fx.width }); + return Daemon.call("set_audio_fx", { speed: fx.speed, semitones: fx.semitones, reverb: fx.reverb, bass: fx.bass, width: fx.width, eq: fx.eq }); } // Optimistic: the switch flips the local mirror at once and asks the daemon. The daemon method // lands later, so a failure is swallowed rather than surfaced. diff --git a/client/chrome/FocusMode.qml b/client/chrome/FocusMode.qml new file mode 100644 index 00000000..ef1d8acf --- /dev/null +++ b/client/chrome/FocusMode.qml @@ -0,0 +1,275 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Ryoku.Ui.Singletons +import "../" +import "../components" + +// Ryotunes Focus Mode v2: a native, artwork-led listening space. +// Riff-inspired immersion; Ryotunes layout, tokens, playback and lyrics remain authoritative. +// Instantiated only while the user requests it. There is no secondary audio engine. +Item { + id: root + focus: true + clip: true + + signal closeRequested() + + readonly property var now: Playback.now + readonly property int artSize: Math.max(Style.sp(40), Math.min( + Style.sp(110), Math.round(width * 0.34), Math.round(height * 0.48))) + readonly property bool soundcloud: !!root.now && String(root.now.videoId).startsWith("sc:") + property string coverFlash: "" + + Timer { id: flashTimer; interval: 220; onTriggered: root.coverFlash = "" } + + function togglePlayback() { + root.coverFlash = Playback.paused ? "play" : "pause"; + flashTimer.restart(); + Playback.togglePause(); + } + + Rectangle { anchors.fill: parent; color: Tokens.paper } + + // Reuse the app's existing blurred-cover wash: static between track changes. + Backdrop { + anchors.fill: parent + strength: 0.3 + focusX: 0.28 + focusY: 0.36 + } + Rectangle { + anchors.fill: parent + color: Qt.rgba(Tokens.paper.r, Tokens.paper.g, Tokens.paper.b, + Style.paperDark ? 0.28 : 0.5) + } + + ColumnLayout { + anchors.fill: parent + anchors.leftMargin: Style.sp(10) + anchors.rightMargin: Style.sp(10) + anchors.topMargin: Style.sp(6) + anchors.bottomMargin: Style.sp(7) + spacing: Style.sp(5) + + // The normal title bar and sidebar are hidden by App while Focus Mode is open. + RowLayout { + Layout.fillWidth: true + spacing: Style.sp(3) + + Text { + text: "力 RYOTUNES" + color: Tokens.inkMuted + font.family: Style.fontMono + font.pixelSize: Style.fs.xs + font.letterSpacing: Style.trackMicro + } + Item { Layout.fillWidth: true } + IconButton { + icon: "close" + iconSize: Style.fs.lg + diameter: Style.sp(9) + outlined: true + tip: "Leave Focus Mode" + tipBelow: true + onClicked: root.closeRequested() + } + } + + Hairline { Layout.fillWidth: true } + + RowLayout { + id: listeningArea + Layout.fillWidth: true + Layout.fillHeight: true + spacing: Style.sp(12) + + // Cover and track identity. Keep the original artwork shape; no Riff morph overlays. + ColumnLayout { + Layout.preferredWidth: Math.max(root.artSize, Math.round(root.width * 0.39)) + Layout.fillHeight: true + spacing: Style.sp(3) + + // The stage should start near the editorial header, not float midway down + // an otherwise empty full-height column. Leave the remaining room below. + Item { + Layout.preferredHeight: Math.max(Style.sp(2), + Math.min(Style.sp(16), Math.round(root.height * 0.05))) + } + + Item { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: root.artSize + Layout.preferredHeight: root.artSize + + Artwork { + anchors.fill: parent + px: root.artSize + cornerRadius: Style.radiusCard + url: (root.now && root.now.thumbnail) ? root.now.thumbnail : "" + } + Rectangle { + anchors.fill: parent + radius: Style.radiusCard + color: "#66000000" + opacity: coverArea.containsMouse ? 1 : 0 + Behavior on opacity { + enabled: !Tokens.reduceMotion + NumberAnimation { duration: Style.motion.snap } + } + } + Icon { + anchors.centerIn: parent + name: root.coverFlash !== "" ? root.coverFlash + : (Playback.paused ? "play" : "pause") + size: Style.fs.title + color: "#ffffff" + visible: root.coverFlash !== "" || coverArea.containsMouse + } + MouseArea { + id: coverArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: root.togglePlayback() + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: Math.max(root.artSize, Style.sp(65)) + spacing: Style.sp(1) + + Text { + Layout.fillWidth: true + text: (root.now && root.now.title) ? root.now.title : "Nothing playing" + textFormat: Text.PlainText + color: Tokens.ink + font.family: Style.fontDisplay + font.pixelSize: Style.fs.title + maximumLineCount: 2 + wrapMode: Text.WordWrap + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + } + Text { + Layout.fillWidth: true + text: (root.now && root.now.artists) ? root.now.artists : "" + textFormat: Text.PlainText + color: Tokens.inkMuted + font.family: Style.fontUi + font.pixelSize: Style.fs.md + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + } + Text { + Layout.fillWidth: true + text: root.soundcloud ? "SOUNDCLOUD" : Playback.provider.toUpperCase() + color: Tokens.inkFaint + font.family: Style.fontMono + font.pixelSize: Style.fs.micro + font.letterSpacing: Style.trackMicro + horizontalAlignment: Text.AlignHCenter + } + } + + Item { Layout.fillHeight: true } + } + + // Reuse the existing lyrics component: word timings, seek-to-line, source fallback, + // reduced motion and the visibility-gated timer all remain in one implementation. + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: Style.sp(3) + + // Focus typography is an opt-in display treatment. The lyric data, + // scrolling, network requests and timing remain owned by LyricsPanel. + LyricsPanel { + id: lyricsView + focusMode: true + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + + Hairline { Layout.fillWidth: true } + + // One transport. The daemon remains the only authority for playback and volume. + ColumnLayout { + Layout.fillWidth: true + spacing: Style.sp(3) + + RowLayout { + Layout.fillWidth: true + spacing: Style.sp(3) + Text { + text: Style.fmtTime(Playback.shownPosition) + color: Tokens.inkFaint + font.family: Style.fontMono + font.pixelSize: Style.fs.xs + } + Slider { + Layout.fillWidth: true + from: 0 + to: Math.max(1, Playback.duration) + value: Playback.shownPosition + trackColor: Tokens.lineStrong + fillColor: Style.accent + onMoved: (v) => Playback.seekDrag = v + onCommitted: (v) => { + Playback.seekDrag = NaN; + Playback.seek(v).catch((e) => Playback.toast( + (e && e.message) ? e.message : "Seek failed", "error")); + } + } + Text { + text: Style.fmtTime(Playback.duration) + color: Tokens.inkFaint + font.family: Style.fontMono + font.pixelSize: Style.fs.xs + } + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: Style.sp(4) + IconButton { + icon: "shuffle" + tip: "Shuffle" + active: !!(Playback.queue && Playback.queue.shuffle) + onClicked: Playback.toggleShuffle() + } + IconButton { + icon: "previous" + iconSize: Style.fs.lg + diameter: Style.sp(10) + tip: "Previous track" + onClicked: Playback.prev() + } + IconButton { + icon: Playback.paused ? "play" : "pause" + iconSize: Style.fs.xl + diameter: Style.sp(13) + primary: true + tip: Playback.paused ? "Play" : "Pause" + onClicked: root.togglePlayback() + } + IconButton { + icon: "next" + iconSize: Style.fs.lg + diameter: Style.sp(10) + tip: "Next track" + onClicked: Playback.next() + } + IconButton { + icon: (Playback.queue && Playback.queue.repeat === "one") ? "repeat-one" : "repeat" + tip: "Repeat" + active: !!(Playback.queue && Playback.queue.repeat && Playback.queue.repeat !== "off") + onClicked: Playback.cycleRepeat() + } + } + } + } +} diff --git a/client/chrome/LyricsPanel.qml b/client/chrome/LyricsPanel.qml index ac39b1a1..aa92b647 100644 --- a/client/chrome/LyricsPanel.qml +++ b/client/chrome/LyricsPanel.qml @@ -18,6 +18,8 @@ Item { id: root property bool compact: false + // Display-only typography treatment for the full-window listening stage. + property bool focusMode: false // --- fetched state ---------------------------------------------------------------------- property var lyrics: null @@ -257,7 +259,9 @@ Item { readonly property bool wordMode: lineWrap.isActive && !!(lineWrap.modelData.words && lineWrap.modelData.words.length) readonly property int lineFs: root.compact ? (lineWrap.isActive ? Style.fs.lg : Style.fs.md) - : (lineWrap.isActive ? Style.fs.xl : Style.fs.lg) + : root.focusMode + ? (lineWrap.isActive ? Style.fs.title : Style.fs.xl) + : (lineWrap.isActive ? Style.fs.xl : Style.fs.lg) // The active line reads in ink and slightly larger, upcoming lines in inkMuted and // past lines in inkFaint; opacity stays full so colour carries the state, and the // viewport haze then fades every line by its distance from the reading centre. @@ -529,7 +533,7 @@ Item { Layout.topMargin: Style.sp(2) Layout.bottomMargin: Style.sp(2) spacing: Style.sp(2) - visible: !root.compact && !!root.lyrics && !root.loading + visible: !root.compact && !root.focusMode && !!root.lyrics && !root.loading Text { Layout.fillWidth: true text: { diff --git a/client/chrome/NowPlaying.qml b/client/chrome/NowPlaying.qml index d6265b8a..50c52545 100644 --- a/client/chrome/NowPlaying.qml +++ b/client/chrome/NowPlaying.qml @@ -26,6 +26,7 @@ Item { property bool lyricsOpen: false signal tabRequested(string tab) signal closeRequested() + signal focusRequested() readonly property bool open: root.nowPlayingOpen || root.queueOpen || root.lyricsOpen @@ -97,6 +98,35 @@ Item { font.pixelSize: Style.fs.micro font.letterSpacing: Style.trackMicro } + // First-party Ryotunes entry point to the Riff-inspired immersive stage. + Rectangle { + id: focusPill + implicitWidth: focusRow.implicitWidth + Style.sp(6) + implicitHeight: Style.sp(9) + radius: Style.radius + color: focusHover.hovered ? Tokens.tint10 : Tokens.paperLift + border.width: 1 + border.color: focusHover.hovered ? Tokens.lineStrong : Tokens.line + RowLayout { + id: focusRow + anchors.centerIn: parent + spacing: Style.sp(1.5) + Icon { name: "expand"; size: Style.fs.sm; color: Tokens.inkDim } + Text { + text: "Focus" + color: Tokens.inkDim + font.family: Style.fontUi + font.pixelSize: Style.fs.sm + font.weight: Font.Medium + } + } + HoverHandler { id: focusHover } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.focusRequested() + } + } Rectangle { id: collapsePill implicitWidth: collapseRow.implicitWidth + Style.sp(6) diff --git a/client/chrome/SoundDialog.qml b/client/chrome/SoundDialog.qml index 0dc4fca9..69b5dce3 100644 --- a/client/chrome/SoundDialog.qml +++ b/client/chrome/SoundDialog.qml @@ -22,7 +22,9 @@ Item { // The working copy the sliders edit, seeded from the daemon mirror when the dialog opens. Kept // local (not a live binding to Playback.audioFx) so a drag is not fought by the echo it causes. - property var fx: ({ speed: 1, semitones: 0, reverb: 0, bass: 0, width: 0 }) + property var fx: ({ speed: 1, semitones: 0, reverb: 0, bass: 0, width: 0, eq: ({ enabled: false, bands: [] }) }) + property bool equalizerOpen: false + property int selectedEqBand: 10 // Snapshot state. `snapReady` gates the blur (the frozen frame is drawn once, then blurred); // `shown` reveals the card and scrim. Both flip when the capture completes, so the snapshot is // the frame alone — but a fallback timer flips `shown` regardless, so a build where the capture @@ -40,7 +42,7 @@ Item { Component.onCompleted: { var a = Playback.audioFx; - root.fx = { speed: a.speed, semitones: a.semitones, reverb: a.reverb, bass: a.bass, width: a.width }; + root.fx = { speed: a.speed, semitones: a.semitones, reverb: a.reverb, bass: a.bass, width: a.width, eq: a.eq || root.defaultEq() }; // Snapshot the frame behind us. Capture the frame-level item (the child of the window's // content root), not the root itself — a plain Item renders to a texture, the window root // does not — walking up until the next parent would be that root, whatever the nesting. @@ -55,6 +57,34 @@ Item { Keys.onEscapePressed: (e) => { root.closeRequested(); e.accepted = true; } function clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); } + function defaultEq() { + const hz = [32, 45, 64, 90, 125, 180, 250, 350, 500, 700, + 1000, 1400, 2000, 2800, 4000, 5600, 8000, 11000, 15000, 19000]; + return { enabled: false, bands: hz.map(frequency => ({ frequency: frequency, gain: 0, q: 1 })) }; + } + function activeBand() { + const bands = root.fx.eq && root.fx.eq.bands ? root.fx.eq.bands : []; + return bands[root.selectedEqBand] || { frequency: 1000, gain: 0, q: 1 }; + } + function setEqEnabled(enabled) { + root.patch({ eq: Object.assign({}, root.fx.eq, { enabled: enabled }) }); + } + function setEqBand(index, changes) { + const bands = root.fx.eq.bands.map((band, i) => i === index + ? Object.assign({}, band, changes) : band); + root.patch({ eq: Object.assign({}, root.fx.eq, { bands: bands }) }); + } + function setEqGain(index, value) { + root.setEqBand(index, { gain: root.clamp(Math.round(value * 10) / 10, -12, 12) }); + } + function setEqQ(index, value) { + root.setEqBand(index, { q: root.clamp(Math.round(value * 10) / 10, 0.3, 10) }); + } + function setEqFrequency(index, position) { + const frequency = 20 * Math.pow(1000, root.clamp(position, 0, 1)); + root.setEqBand(index, { frequency: Math.round(frequency) }); + } + function resetEq() { root.patch({ eq: root.defaultEq() }); } // Reassign fx as a whole so the slider bindings (and preset-active state) refresh, then apply // after 80 ms of quiet — a drag coalesces into one daemon call instead of one per frame. function patch(p) { root.fx = Object.assign({}, root.fx, p); apply.restart(); } @@ -63,7 +93,7 @@ Item { function setReverb(v) { root.patch({ reverb: root.clamp(Math.round(v * 100) / 100, 0, 1) }); } function setBass(v) { root.patch({ bass: root.clamp(Math.round(v), -6, 12) }); } function setWidth(v) { root.patch({ width: root.clamp(Math.round(v * 100) / 100, 0, 1) }); } - function applyPreset(p) { root.fx = { speed: p.speed, semitones: p.semitones, reverb: p.reverb, bass: p.bass, width: p.width }; apply.restart(); } + function applyPreset(p) { root.patch(p); } function presetActive(p) { return root.fx.speed === p.speed && root.fx.semitones === p.semitones && root.fx.reverb === p.reverb && root.fx.bass === p.bass && root.fx.width === p.width; @@ -156,6 +186,11 @@ Item { font.pixelSize: Style.fs.xl } } + Chip { + text: root.equalizerOpen ? "EFFECTS" : "20-BAND EQ" + active: root.equalizerOpen + onClicked: root.equalizerOpen = !root.equalizerOpen + } IconButton { Layout.alignment: Qt.AlignTop icon: "close" @@ -166,6 +201,7 @@ Item { // sliders ColumnLayout { + visible: !root.equalizerOpen Layout.fillWidth: true spacing: Style.sp(4) FxRow { @@ -200,10 +236,145 @@ Item { } } + ColumnLayout { + visible: root.equalizerOpen + Layout.fillWidth: true + spacing: Style.sp(3) + + RowLayout { + Layout.fillWidth: true + Chip { + text: root.fx.eq.enabled ? "ENABLED" : "BYPASSED" + active: root.fx.eq.enabled + onClicked: root.setEqEnabled(!root.fx.eq.enabled) + } + Chip { + text: "FLAT" + onClicked: root.resetEq() + } + Item { Layout.fillWidth: true } + Text { + text: "20 PARAMETRIC BANDS" + color: Tokens.inkMuted + font.family: Style.fontMono + font.pixelSize: Style.fs.micro + } + } + + Canvas { + id: eqGraph + Layout.fillWidth: true + Layout.preferredHeight: Style.sp(27) + onPaint: { + const ctx = getContext("2d"); + ctx.clearRect(0, 0, width, height); + ctx.strokeStyle = Tokens.inkMuted; + ctx.globalAlpha = 0.25; + ctx.lineWidth = 1; + for (let i = 0; i < 5; i++) { + const y = height * (i + 1) / 6; + ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(width, y); ctx.stroke(); + } + ctx.globalAlpha = root.fx.eq.enabled ? 1.0 : 0.3; + ctx.strokeStyle = Tokens.ink; + ctx.lineWidth = 2; + ctx.beginPath(); + const bands = root.fx.eq.bands || []; + for (let x = 0; x <= width; x += 2) { + const hz = 20 * Math.pow(1000, x / Math.max(1, width)); + let db = 0; + for (const band of bands) { + const delta = Math.log(hz / band.frequency) / Math.LN2; + const spread = 1 / Math.max(0.3, band.q); + db += band.gain * Math.exp(-0.5 * Math.pow(delta / spread, 2)); + } + const y = height / 2 - Math.max(-18, Math.min(18, db)) * height / 42; + if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); + } + ctx.stroke(); + ctx.globalAlpha = 1.0; + } + Connections { + target: root + function onFxChanged() { eqGraph.requestPaint(); } + } + } + + Flickable { + Layout.fillWidth: true + Layout.preferredHeight: Style.sp(86) + clip: true + contentHeight: eqBands.implicitHeight + boundsBehavior: Flickable.StopAtBounds + ColumnLayout { + id: eqBands + width: parent.width + spacing: Style.sp(2) + Repeater { + // Keep the 20 delegates alive while gain values change. + model: 20 + delegate: RowLayout { + id: bandRow + required property int index + readonly property var bandData: root.fx.eq.bands[bandRow.index] + Layout.fillWidth: true + spacing: Style.sp(2) + Chip { + text: Math.round(bandRow.bandData.frequency) + " Hz" + active: root.selectedEqBand === bandRow.index + onClicked: root.selectedEqBand = bandRow.index + } + Slider { + Layout.fillWidth: true + from: -12 + to: 12 + value: bandRow.bandData.gain + onMoved: (v) => root.setEqGain(bandRow.index, v) + onCommitted: (v) => root.setEqGain(bandRow.index, v) + } + Text { + Layout.preferredWidth: Style.sp(14) + horizontalAlignment: Text.AlignRight + text: (bandRow.bandData.gain > 0 ? "+" : "") + + Number(bandRow.bandData.gain).toFixed(1) + " dB" + color: Tokens.inkMuted + font.family: Style.fontMono + font.pixelSize: Style.fs.xs + } + } + } + } + } + + Text { + text: "BAND " + (root.selectedEqBand + 1) + " · FREQUENCY & Q" + color: Tokens.inkMuted + font.family: Style.fontMono + font.pixelSize: Style.fs.micro + } + FxRow { + label: "Frequency" + value: Math.round(root.activeBand().frequency) + " Hz" + from: 0 + to: 1 + val: Math.log(root.activeBand().frequency / 20) / Math.log(1000) + onMoved: (v) => root.setEqFrequency(root.selectedEqBand, v) + } + FxRow { + label: "Q (width)" + value: Number(root.activeBand().q).toFixed(1) + from: 0.3 + to: 10 + val: root.activeBand().q + onMoved: (v) => root.setEqQ(root.selectedEqBand, v) + } + } + Hairline { Layout.fillWidth: true } // presets ColumnLayout { + visible: !root.equalizerOpen Layout.fillWidth: true spacing: Style.sp(3) Text { diff --git a/crates/core/src/state.rs b/crates/core/src/state.rs index 3dcec6bf..3bcdf8b8 100644 --- a/crates/core/src/state.rs +++ b/crates/core/src/state.rs @@ -4331,6 +4331,14 @@ impl AppState { if !fx.width.is_finite() || !(0.0..=1.0).contains(&fx.width) { return Err("Width must be between 0 and 1.".into()); } + for (index, band) in fx.eq.bands.iter().enumerate() { + if !band.frequency.is_finite() || !(20.0..=20000.0).contains(&band.frequency) + || !band.q.is_finite() || !(0.3..=10.0).contains(&band.q) + || !band.gain.is_finite() || !(-12.0..=12.0).contains(&band.gain) + { + return Err(format!("Invalid equalizer values in band {}.", index + 1)); + } + } self.player.set_fx(&fx).map_err(|e| e.to_string())?; *self.audio_fx.write() = fx; self.emit("audio-fx", fx); diff --git a/crates/player/src/lib.rs b/crates/player/src/lib.rs index bb364c45..1124eeff 100644 --- a/crates/player/src/lib.rs +++ b/crates/player/src/lib.rs @@ -62,6 +62,37 @@ fn friendly_error(e: &libmpv2::Error) -> String { } } +#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct EqBand { + pub frequency: f64, + pub gain: f64, + pub q: f64, +} + +#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ParametricEq { + pub enabled: bool, + pub bands: [EqBand; 20], +} + +impl Default for ParametricEq { + fn default() -> Self { + const HZ: [f64; 20] = [ + 32.0, 45.0, 64.0, 90.0, 125.0, 180.0, 250.0, 350.0, 500.0, 700.0, + 1000.0, 1400.0, 2000.0, 2800.0, 4000.0, 5600.0, 8000.0, 11000.0, + 15000.0, 19000.0, + ]; + Self { + enabled: false, + bands: std::array::from_fn(|i| EqBand { + frequency: HZ[i], + gain: 0.0, + q: 1.0, + }), + } + } +} + /// The user-tunable audio effects the Sound dialog drives (spec section 7). Applied as one mpv /// `af` chain alongside the loudness gain; `speed` is mpv's `speed` property, not part of the /// chain. Serialized `{speed, semitones, reverb, bass, width}` for the socket — `bass_db` is @@ -79,13 +110,15 @@ pub struct AudioFx { pub bass_db: f64, /// Stereo width 0..1; 0 omits the filter. pub width: f64, + #[serde(default)] + pub eq: ParametricEq, } impl Default for AudioFx { /// Everything off: speed 1×, no pitch shift, no effects — the same filterless path playback /// took before the Sound dialog existed. fn default() -> Self { - AudioFx { speed: 1.0, semitones: 0, reverb: 0.0, bass_db: 0.0, width: 0.0 } + AudioFx { speed: 1.0, semitones: 0, reverb: 0.0, bass_db: 0.0, width: 0.0, eq: ParametricEq::default() } } } @@ -356,6 +389,17 @@ fn af_chain(gain_db: Option, fx: &AudioFx) -> String { if let Some(g) = gain_db { chain.push(format!("lavfi=[volume={g}dB]")); } + if fx.eq.enabled { + for band in &fx.eq.bands { + if band.gain.abs() < 0.0001 { + continue; + } + chain.push(format!( + "lavfi=[equalizer=f={}:t=q:w={}:g={}]", + band.frequency, band.q, band.gain + )); + } + } if fx.semitones != 0 { // Semitones → frequency multiplier (equal temperament). chain.push(format!( @@ -576,7 +620,7 @@ mod tests { /// A convenience constructor: every field but the four under test at its neutral value. fn fx(reverb: f64, bass_db: f64, width: f64, semitones: i32) -> AudioFx { - AudioFx { speed: 1.0, semitones, reverb, bass_db, width } + AudioFx { speed: 1.0, semitones, reverb, bass_db, width, eq: Default::default() } } /// Everything above is string-building; this drives a real libmpv and reads `af` back out of @@ -635,7 +679,7 @@ mod tests { // (set_property would error otherwise — this is the only proof aecho/bass/stereowiden // parse on this build). p.set_gain(None).unwrap(); - p.set_fx(&AudioFx { speed: 1.0, semitones: 0, reverb: 1.0, bass_db: 8.0, width: 0.8 }) + p.set_fx(&AudioFx { speed: 1.0, semitones: 0, reverb: 1.0, bass_db: 8.0, width: 0.8, eq: Default::default() }) .unwrap(); let live = af(); assert!(live.contains("aecho"), "reverb missing from live chain: {live}"); @@ -643,6 +687,20 @@ mod tests { assert!(live.contains("stereowiden"), "width missing from live chain: {live}"); } + #[test] + fn parametric_eq_is_neutral_until_enabled_and_gained() { + let mut fx = AudioFx::default(); + assert_eq!(af_chain(None, &fx), ""); + fx.eq.enabled = true; + assert_eq!(af_chain(None, &fx), ""); + fx.eq.bands[10].gain = 3.0; + let chain = af_chain(Some(-4.0), &fx); + assert!(chain.starts_with("lavfi=[volume=-4dB],")); + assert!(chain.contains("lavfi=[equalizer=f=1000:t=q:w=1:g=3]")); + fx.eq.enabled = false; + assert!(!af_chain(None, &fx).contains("equalizer=")); + } + #[test] fn paths_survive_mpvs_command_parser() { // The bug this exists for: a space used to end the argument. From bb215c76858993e16461de5409ee31ef2f58bad8 Mon Sep 17 00:00:00 2001 From: Aether Date: Wed, 28 Oct 2026 21:57:27 +0000 Subject: [PATCH 02/20] feat(ryotunes): integrate Spotify Soloist and Audio Lab --- client/App.qml | 66 ++- client/Daemon.qml | 83 ++++ client/Playback.qml | 189 +++++++- client/Prefs.qml | 3 + client/Style.qml | 4 +- client/chrome/CompactNav.qml | 95 ++++ client/chrome/Sidebar.qml | 93 +--- client/chrome/SoundDialog.qml | 354 ++++++++++----- client/chrome/TitleBar.qml | 22 +- client/components/AudioLabWorkbench.qml | 54 +++ client/components/AudioSpectrumWorkbench.qml | 198 +++++++++ client/components/AudioStereoScope.qml | 165 +++++++ client/components/Chip.qml | 2 +- client/components/HomeListeningHero.qml | 146 ++++++ client/components/MediaCard.qml | 6 + client/components/NowPlayingCard.qml | 2 +- client/components/Pill.qml | 2 +- client/components/Toggle.qml | 2 +- client/pages/HomePage.qml | 127 ++---- client/pages/SettingsPage.qml | 113 +++++ client/tools/audio_scope_bridge.py | 137 ++++++ crates/core/src/lib.rs | 1 + crates/core/src/soloist.rs | 443 +++++++++++++++++++ crates/core/src/state.rs | 113 ++++- crates/ryotunesd/src/main.rs | 19 + crates/ryotunesd/src/methods.rs | 28 ++ 26 files changed, 2147 insertions(+), 320 deletions(-) create mode 100644 client/chrome/CompactNav.qml create mode 100644 client/components/AudioLabWorkbench.qml create mode 100644 client/components/AudioSpectrumWorkbench.qml create mode 100644 client/components/AudioStereoScope.qml create mode 100644 client/components/HomeListeningHero.qml create mode 100644 client/tools/audio_scope_bridge.py create mode 100644 crates/core/src/soloist.rs diff --git a/client/App.qml b/client/App.qml index d4d7e023..01204d83 100644 --- a/client/App.qml +++ b/client/App.qml @@ -32,7 +32,7 @@ Item { readonly property bool panelShown: app.panelOpen && app.width >= Style.sp(275) && !app.nowPlayingOpen // The navigation rail's collapse state, toggled from the title bar. - property bool sidebarOpen: true + property bool sidebarOpen: false // The Sound dialog (tempo / pitch / reverb / bass / width). A modal Loader that only exists while // open; SoundWorker lands chrome/SoundDialog.qml and Playback.soundRequested. @@ -40,6 +40,15 @@ Item { // Focus Mode is a separate surface; existing Now Playing remains unchanged. property bool focusOpen: false + // Audio Lab is global chrome, never part of a routed page's scroll layout. + // A manual hide persists across navigation until reopened from the top bar. + property bool audioLabOpen: Prefs.audiophileMode + Connections { + target: Prefs + function onAudiophileModeChanged(): void { app.audioLabOpen = Prefs.audiophileMode; } + function onShowAudioLabRequested(): void { app.audioLabOpen = true; } + } + // The mini player window's visibility. shell.qml's mini PanelWindow binds its visible to this; the // title-bar and player-bar buttons toggle it, MiniPlayer clears it. property bool miniOpen: false @@ -129,6 +138,8 @@ Item { Layout.fillWidth: true sidebarOpen: app.sidebarOpen panelOpen: app.panelShown + audioLabOpen: app.audioLabOpen + onAudioLabClicked: app.audioLabOpen = !app.audioLabOpen onSidebarToggleClicked: app.sidebarOpen = !app.sidebarOpen onPanelToggleClicked: app.panelOpen = !app.panelOpen onSoundClicked: app.soundOpen = true @@ -147,6 +158,12 @@ Item { open: app.sidebarOpen onCollapseRequested: app.sidebarOpen = false } + // Riff-inspired icon navigation, rendered with Ryoku-native controls. + CompactNav { + Layout.fillHeight: true + visible: !app.sidebarOpen + onExpandRequested: app.sidebarOpen = true + } // The content area: the single cover Backdrop, a paper glass over it so the bloom reads // as a subtle wash, and the routed page inset by the shared padding. The Now Playing stage @@ -168,14 +185,17 @@ Item { color: Qt.rgba(Tokens.paper.r, Tokens.paper.g, Tokens.paper.b, 0.88) } // The provider's light over the glass: red for YouTube Music, green for Spotify. - ProviderGlow { anchors.fill: parent; focusX: 0.82; focusY: 0.1 } + ProviderGlow { anchors.fill: parent; focusX: 0.82; focusY: 0.1; strength: 0.17 } Item { id: pageClip anchors.fill: parent anchors.leftMargin: Style.pagePad anchors.rightMargin: Style.pagePad - anchors.topMargin: Style.sp(6) + // Make space for the fixed top instrument strip instead of + // overlaying or resizing the Library's scrollable children. + anchors.topMargin: audioLabStrip.visible + ? audioLabStrip.height + Style.sp(5) : Style.sp(6) anchors.bottomMargin: Style.pagePad clip: true @@ -199,6 +219,20 @@ Item { return m[pageStack.page] ? Qt.resolvedUrl("pages/" + m[pageStack.page] + ".qml") : ""; } } + Connections { + target: pageLoader.item + ignoreUnknownSignals: true + function onFocusRequested(): void { app.openFocus(); } + } + // Home places Audio Lab in its own feed, after the hero search + // and before the listening card. Other routes use the top strip. + // Binding only targets HomePage; other pages have no such property. + Binding { + target: pageStack.page === "home" ? pageLoader.item : null + property: "audioLabEnabled" + value: Prefs.audiophileMode && app.audioLabOpen + && !app.focusOpen && !app.modalOpen + } Loader { anchors.fill: parent active: pageLoader.status !== Loader.Ready @@ -238,6 +272,9 @@ Item { NowPlaying { anchors.fill: parent + // The stage shares the content region below the Audio Lab. + anchors.topMargin: audioLabStrip.visible + ? audioLabStrip.height + Style.sp(5) : 0 nowPlayingOpen: app.nowPlayingOpen queueOpen: app.queueOpen lyricsOpen: app.lyricsOpen @@ -245,6 +282,29 @@ Item { onCloseRequested: app.npClose() onFocusRequested: app.openFocus() } + + // Audiophile Mode: the original wide, low-profile spectrum layout, + // now fixed to the top of the shared content surface on every route. + // No floating overlay, bottom docking, or debug metadata heading. + // Hiding the strip releases its capture claim and restores page height. + AudioLabWorkbench { + id: audioLabStrip + visible: Prefs.audiophileMode && app.audioLabOpen + && !app.focusOpen && !app.modalOpen + && pageStack.page !== "home" + active: visible + z: 10 + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.leftMargin: Style.pagePad + anchors.rightMargin: Style.pagePad + anchors.topMargin: Style.sp(2) + // Approx. 192-200px on a full-size window; adapt gracefully + // to shorter windows without swallowing the page beneath. + height: Math.min(Style.sp(50), + Math.max(Style.sp(30), parent.height * 0.30)) + } } RightPanel { diff --git a/client/Daemon.qml b/client/Daemon.qml index 1bd2bb40..92647924 100644 --- a/client/Daemon.qml +++ b/client/Daemon.qml @@ -25,6 +25,83 @@ Singleton { // the first subscribe and again after every reconnect so Playback resynchronises each time. signal snapshot(var data) + // Soloist owns playback while Spotify is the selected catalogue. + // The native MPV player remains untouched for every other provider. + property bool soloistMode: false + property bool soloistPaused: true + property bool soloistShuffle: false + property var soloistQueueItems: [] + + function soloistItemId(item) { + if (!item) return ""; + return String(item.video_id || item.videoId || item.uri || item.id || ""); + } + + function soloistRoute(method, params) { + if (!root.soloistMode) return null; + var p = params || {}; + var action = ""; + var args = {}; + var uri = ""; + + if (method === "play") { + uri = root.soloistItemId(p.item); + if (!uri.startsWith("spotify:track:")) + return { error: "Spotify Soloist can play Spotify tracks only" }; + action = "play"; + args = { uri: uri }; + } else if (method === "play_playlist") { + // Prefer native Spotify playlist context, if the caller supplied one. + uri = String(p.spotifyUri || ""); + if (!uri.startsWith("spotify:playlist:") && !uri.startsWith("spotify:album:")) { + var rows = p.items || []; + var idx = p.start === undefined || p.start === null ? 0 : p.start; + uri = root.soloistItemId(rows[idx] || rows[0]); + } + if (!uri.startsWith("spotify:")) + return { error: "No Spotify tracks to play" }; + action = "play"; + args = { uri: uri }; + } else if (method === "play_index") { + uri = root.soloistItemId(root.soloistQueueItems[p.index]); + if (!uri.startsWith("spotify:track:")) + return { error: "Spotify queue item unavailable" }; + action = "play"; + args = { uri: uri }; + } else if (method === "toggle_pause") { + action = root.soloistPaused ? "play" : "pause"; + } else if (method === "next_track") { + action = "skip_next"; + } else if (method === "prev_track") { + action = "skip_prev"; + } else if (method === "seek") { + action = "seek"; + args = { positionMs: Math.max(0, Math.round(Number(p.position) * 1000)) }; + } else if (method === "set_volume") { + action = "volume"; + args = { volume: Math.max(0, Math.min(100, Math.round(Number(p.volume)))) }; + } else if (method === "toggle_shuffle") { + action = "shuffle"; + args = { enabled: !root.soloistShuffle }; + root.soloistShuffle = args.enabled; + } else if (method === "set_repeat") { + action = "repeat"; + args = { mode: p.mode === "one" ? "track" : p.mode === "all" ? "context" : "off" }; + } else if (method === "add_to_queue" || method === "play_next") { + var items = p.items || []; + uri = root.soloistItemId(items[0]); + if (!uri.startsWith("spotify:track:")) + return { error: "Soloist queue accepts Spotify tracks only" }; + action = "add_to_queue"; + args = { uri: uri }; + } else if (["move_in_queue", "remove_from_queue", "clear_queued", "set_stop_after_current"].indexOf(method) !== -1) { + return { error: "This queue operation is not supported by Spotify Soloist" }; + } else { + return null; + } + return { method: "soloist_control", params: { action: action, args: args } }; + } + property int nextId: 1 property var pending: ({}) // Whether a subscription has been asked for; drives the re-subscribe on reconnect. @@ -42,6 +119,12 @@ Singleton { Component.onCompleted: root.reconnect() function call(method, params) { + var routed = root.soloistRoute(method, params); + if (routed) { + if (routed.error) return Promise.reject({code: "soloist_unsupported", message: routed.error}); + method = routed.method; + params = routed.params; + } return new Promise((resolve, reject) => { if (!root.connected) { // The daemon never sees this failure, so it can only be recorded here: a user diff --git a/client/Playback.qml b/client/Playback.qml index fc5a7f32..b1d56697 100644 --- a/client/Playback.qml +++ b/client/Playback.qml @@ -73,10 +73,193 @@ Singleton { // A surface (player-bar tools, track menu) asking the App to open the Sound dialog. signal soundRequested() + // --- Spotify Soloist: use the same player bar, not a second interface --------------------- + // The Rust daemon forwards native Soloist updates as "soloist-event". A sparse snapshot + // poll repairs late attachment and daemon reconnects without depending on event timing. + property bool soloistLoading: false + property string soloistError: "" + + function soloistTrack(n) { + if (!n || !n.videoId) return; + var seconds = Math.max(0, Math.round(Number(n.durationMs || 0) / 1000)); + var minutes = Math.floor(seconds / 60); + var rem = (seconds % 60).toString().padStart(2, "0"); + root.now = { + videoId: n.videoId, + title: n.title || "Unknown track", + artists: n.artists || "", + artist: n.artists || "", + album: n.album || "", + thumbnail: n.thumbnail || "", + duration: minutes + ":" + rem, + rating: "indifferent" + }; + root.duration = Math.max(0, Number(n.durationMs || 0) / 1000); + root.position = PB.clampPosition(root.duration, Number(n.positionMs || 0) / 1000); + root.paused = !!n.paused; + root.volume = Number(n.volume === undefined ? root.volume : n.volume); + Daemon.soloistPaused = root.paused; + root.pendingVideoId = null; + root.lastError = null; + root.soloistError = ""; + root.soloistEnsureQueue(); + } + + function soloistEnsureQueue() { + if (!root.now) return; + var q = root.queue || {}; + var items = q.items || []; + if (!items.length || items[0].video_id !== root.now.videoId) { + root.queue = { + items: [{ video_id: root.now.videoId, title: root.now.title, + artists: root.now.artists, thumbnail: root.now.thumbnail, + duration: root.now.duration }], + currentIndex: 0, shuffle: Daemon.soloistShuffle, + repeat: q.repeat || "off", sourceName: "Spotify" + }; + } + Daemon.soloistQueueItems = root.queue.items; + } + + function soloistReadQueue(source) { + if (!source || root.provider !== "spotify") return; + var rows = source.queue || source.items || source.tracks || source.next_tracks || []; + if (!Array.isArray(rows)) rows = rows.items || rows.tracks || []; + var current = root.now ? [{ video_id: root.now.videoId, + title: root.now.title, artists: root.now.artists, + thumbnail: root.now.thumbnail, duration: root.now.duration }] : []; + for (var i = 0; i < rows.length; i++) { + var item = rows[i].item || rows[i].track || rows[i]; + var id = item.uri || item.video_id || item.videoId || ""; + if (!String(id).startsWith("spotify:track:")) continue; + if (current.length && current[0].video_id === id) continue; + var dec = item.decorations || {}; + var creators = dec.creators || []; + var artists = creators.map(c => (c.entity && c.entity.decorations + && c.entity.decorations.identity || {}).name || "").filter(Boolean).join(", "); + var covers = (dec.visual_identity && dec.visual_identity.cover) || []; + var cover = covers.filter(c => c.size === "large")[0] || covers[covers.length - 1]; + var secs = Math.round(((dec.playback || {}).duration_ms || 0) / 1000); + current.push({ video_id: id, + title: (dec.identity || {}).name || item.title || "Unknown track", + artists: artists || item.artists || "", + thumbnail: (cover && cover.url) || item.thumbnail || "", + duration: Math.floor(secs / 60) + ":" + (secs % 60).toString().padStart(2, "0") }); + } + var old = root.queue || {}; + root.queue = { items: current, currentIndex: 0, shuffle: Daemon.soloistShuffle, + repeat: old.repeat || "off", sourceName: "Spotify" }; + Daemon.soloistQueueItems = current; + } + + function soloistEvent(event) { + if (!event || root.provider !== "spotify") return; + var src = event.source || {}; + if (event.kind === "playback_state" && event.now) { + root.soloistTrack(event.now); + if (src.options) root.soloistOptions(src.options); + } else if (event.kind === "position_sync" && src.position) { + root.position = PB.clampPosition(root.duration, Number(src.position.position_ms || 0) / 1000); + } else if (event.kind === "playback_changed") { + root.paused = src.status !== "playing"; + Daemon.soloistPaused = root.paused; + } else if (event.kind === "volume_changed") { + if (!root.volDrag && typeof src.volume === "number") root.volume = src.volume; + } else if (event.kind === "queue_changed") { + root.soloistReadQueue(src); + } else if (event.kind === "options_changed") { + root.soloistOptions(src.options || src); + } else if (event.kind === "track_changed") { + root.soloistRefresh(); + } + } + + function soloistOptions(o) { + if (!o) return; + var q = root.queue || {}; + if (o.shuffle !== undefined) Daemon.soloistShuffle = !!o.shuffle; + var repeat = o.repeat === "track" || o.repeat === "one" ? "one" + : o.repeat === "context" || o.repeat === "all" ? "all" : "off"; + root.queue = Object.assign({}, q, { shuffle: Daemon.soloistShuffle, repeat: repeat }); + } + + function soloistRefresh() { + if (root.provider !== "spotify" || !Daemon.connected || root.soloistLoading) return; + root.soloistLoading = true; + Daemon.call("soloist_now").then(result => { + if (root.provider !== "spotify") return; + root.soloistTrack(result.now); + if (result.source && result.source.options) root.soloistOptions(result.source.options); + }).catch(e => { + var message = (e && e.message) || "Start Spotify Soloist to play music"; + if (root.soloistError !== message) root.toast(message, "error"); + root.soloistError = message; + }).finally(() => { root.soloistLoading = false; }); + } + + function soloistRestoreMpv() { + if (!Daemon.connected) return; + Daemon.call("get_playback").then(pb => { + if (root.provider === "spotify" || !pb) return; + root.now = pb.now; + root.paused = pb.paused; + root.volume = pb.volume; + root.duration = pb.duration; + root.position = PB.clampPosition(pb.duration, pb.position); + }).catch(() => {}); + Daemon.call("get_queue").then(q => { + if (root.provider !== "spotify") root.queue = q; + }).catch(() => {}); + } + + onProviderChanged: { + var enteringSpotify = root.provider === "spotify"; + // Only one audio engine should remain audible after a provider switch. + if (enteringSpotify && !Daemon.soloistMode && root.now && !root.paused) + Daemon.call("toggle_pause").catch(() => {}); + if (!enteringSpotify && Daemon.soloistMode && !Daemon.soloistPaused) + Daemon.call("soloist_control", { action: "pause", args: {} }).catch(() => {}); + Daemon.soloistMode = enteringSpotify; + if (Daemon.soloistMode) { + root.now = null; + root.queue = { items: [], currentIndex: 0, shuffle: false, repeat: "off" }; + root.soloistRefresh(); + Daemon.call("soloist_queue", { limit: 20 }) + .then(q => root.soloistReadQueue(q)).catch(() => {}); + } else { + root.soloistRestoreMpv(); + } + } + + Timer { + interval: 250 + repeat: true + running: root.provider === "spotify" && !root.paused && root.duration > 0 + onTriggered: root.position = PB.clampPosition(root.duration, root.position + 0.25) + } + Timer { + interval: 8000 + repeat: true + running: root.provider === "spotify" && Daemon.connected + onTriggered: { + root.soloistRefresh(); + Daemon.call("soloist_queue", { limit: 20 }) + .then(q => root.soloistReadQueue(q)).catch(() => {}); + } + } + // --- events + opening snapshot ----------------------------------------------------------- Connections { target: Daemon function onEvent(name, data) { + if (name === "soloist-event") { + root.soloistEvent(data); + return; + } + // Prevent MPV state echoes from overwriting Soloist's visible player. + if (root.provider === "spotify" && ["now-playing", "position", "duration", + "playback-state", "queue-changed", "queue-index", "volume", "rating"] + .indexOf(name) !== -1) return; var fx = PB.applyEvent(root, name, data); if (fx) { if (fx.toast) root.toast(fx.toast, fx.kind); @@ -91,9 +274,10 @@ Singleton { // (and re-sends after every reconnect), the socket equivalent of frontend_ready's resync. function loadSnapshot(snap) { if (!snap) return; - if (snap.queue) root.queue = snap.queue; + if (snap.provider) root.provider = snap.provider; + if (snap.queue && root.provider !== "spotify") root.queue = snap.queue; var pb = snap.playback; - if (pb) { + if (pb && root.provider !== "spotify") { root.volume = pb.volume; // before the now guard: the slider is stale either way if (!root.now) { // a real now-playing event may have beaten the snapshot root.now = pb.now; @@ -107,7 +291,6 @@ Singleton { if (snap.settings) root.settings = snap.settings; if (snap.auth) root.auth = { signedIn: !!snap.auth.signedIn, name: snap.auth.name, avatar: snap.auth.avatar }; if (snap.audioFx) root.audioFx = snap.audioFx; - if (snap.provider) root.provider = snap.provider; if (snap.spotify) root.spotify = { signedIn: !!snap.spotify.signedIn, stored: !!snap.spotify.stored, name: snap.spotify.name, premium: snap.spotify.premium }; diff --git a/client/Prefs.qml b/client/Prefs.qml index 411b7852..b2e4f78b 100644 --- a/client/Prefs.qml +++ b/client/Prefs.qml @@ -8,10 +8,12 @@ import Quickshell.Io // shell.json pattern). Absent or malformed reads as the defaults below. Singleton { id: root + signal showAudioLabRequested() property alias skin: adapter.skin // "system" | a skin id (see Skin.qml, docs/SKINS.md) property alias decor: adapter.decor // "skin" | "rich" | "calm": the decor level, or the skin's own property alias themeMode: adapter.themeMode // "system" | "light" | "dark" + property alias audiophileMode: adapter.audiophileMode // Opt-in advanced UI; does not change playback EQ // The mini widget's offset from the screen's bottom-right corner, logical px. property alias miniRight: adapter.miniRight property alias miniBottom: adapter.miniBottom @@ -31,6 +33,7 @@ Singleton { property string skin: "system" property string decor: "skin" property string themeMode: "system" + property bool audiophileMode: false property int miniRight: 24 property int miniBottom: 24 } diff --git a/client/Style.qml b/client/Style.qml index 440061cf..e08126b4 100644 --- a/client/Style.qml +++ b/client/Style.qml @@ -20,7 +20,9 @@ Singleton { // Geometry (docs/superpowers/specs/2026-09-05-native-client-visual-design.md, section 2). readonly property int radius: Math.round(Skin.shape.radius * root.uiScale) // controls - readonly property int radiusCard: Math.round(Skin.shape.radiusCard * root.uiScale) // cards, hero art + // One restrained corner shape across controls, cards and Audiophile instruments. + // Preserve Style.radiusCard as an alias for existing components/skin consumers. + readonly property int radiusCard: root.radius // matches Btn / IconButton readonly property int rowH: Math.round(52 * root.uiScale) // table rows readonly property int ctlH: Math.round(36 * root.uiScale) // buttons, fields readonly property int heroArt: Math.round(168 * root.uiScale) // page hero art, card diff --git a/client/chrome/CompactNav.qml b/client/chrome/CompactNav.qml new file mode 100644 index 00000000..2a6e8cbb --- /dev/null +++ b/client/chrome/CompactNav.qml @@ -0,0 +1,95 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Ryoku.Ui.Singletons +import "../" +import "../components" + +// Icon-only companion to the existing full Sidebar. Fast, accessible navigation +// while the labelled rail is collapsed, without changing Router or playlists. +Rectangle { + id: root + signal expandRequested() + + implicitWidth: Style.sp(14) + color: Tokens.paper + + Hairline { + anchors.right: parent.right + width: 1 + height: parent.height + } + + ColumnLayout { + anchors.fill: parent + anchors.topMargin: Style.sp(3) + anchors.bottomMargin: Style.sp(3) + spacing: Style.sp(2) + + IconButton { + Layout.alignment: Qt.AlignHCenter + icon: "sidebar" + iconSize: Style.fs.lg + diameter: Style.sp(9) + tip: "Expand navigation" + tipBelow: true + onClicked: root.expandRequested() + } + + Rectangle { + Layout.fillWidth: true + Layout.leftMargin: Style.sp(3) + Layout.rightMargin: Style.sp(3) + Layout.topMargin: Style.sp(2) + Layout.bottomMargin: Style.sp(2) + Layout.preferredHeight: 1 + color: Tokens.lineSoft + } + + IconButton { + Layout.alignment: Qt.AlignHCenter + icon: "home" + tip: "Home" + active: Router.current && Router.current.page === "home" + onClicked: Router.push("home") + } + IconButton { + Layout.alignment: Qt.AlignHCenter + icon: "search" + tip: "Search" + active: Router.current && Router.current.page === "search" + onClicked: Router.push("search") + } + IconButton { + Layout.alignment: Qt.AlignHCenter + icon: "radio" + tip: "Radio" + active: Router.current && Router.current.page === "radio" + onClicked: Router.push("radio") + } + IconButton { + Layout.alignment: Qt.AlignHCenter + icon: "library" + tip: "Library" + active: Router.current && Router.current.page === "library" + onClicked: Router.push("library", { tab: "songs" }) + } + IconButton { + Layout.alignment: Qt.AlignHCenter + icon: "download" + tip: "Downloads" + active: Router.current && Router.current.page === "downloads" + onClicked: Router.push("downloads") + } + + Item { Layout.fillHeight: true } + + IconButton { + Layout.alignment: Qt.AlignHCenter + icon: "settings" + tip: "Settings" + active: Router.current && Router.current.page === "settings" + onClicked: Router.push("settings") + } + } +} diff --git a/client/chrome/Sidebar.qml b/client/chrome/Sidebar.qml index 4cbd1474..81340ef3 100644 --- a/client/chrome/Sidebar.qml +++ b/client/chrome/Sidebar.qml @@ -93,26 +93,15 @@ Rectangle { Accessible.onPressAction: nr.activated() Keys.onReturnPressed: nr.activated() Keys.onSpacePressed: nr.activated() - border.width: activeFocus ? 2 : 0 - border.color: Tokens.ink + border.width: activeFocus ? 2 : nr.current ? 1 : 0 + border.color: activeFocus ? Tokens.ink : Tokens.lineSoft Layout.fillWidth: true implicitHeight: Style.sp(10) radius: Style.radius - color: nr.current ? Tokens.paperLift : navHover.hovered ? Tokens.tint5 : "transparent" + color: nr.current ? Tokens.tint10 : navHover.hovered ? Tokens.tint5 : "transparent" Behavior on color { ColorAnimation { duration: Style.motion.snap } } - // The 2 px accent left border of the active row (state, per the eye-candy brief). - Rectangle { - visible: nr.current - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - width: Style.sp(0.5) - height: parent.height - Style.sp(4) - radius: width / 2 - color: Style.accent - } - RowLayout { anchors.fill: parent anchors.leftMargin: Style.sp(3) @@ -121,7 +110,7 @@ Rectangle { Icon { name: nr.icon size: Style.fs.lg - color: nr.current ? Tokens.ink : Tokens.inkMuted + color: nr.current ? Style.accent : Tokens.inkMuted } Text { Layout.fillWidth: true @@ -167,40 +156,24 @@ Rectangle { } } - // A section register: number, tracked mono micro label, a hairline to the right and (rich) seal. + // Small navigation headings establish hierarchy without numbering or ornamental rules. component SectionLabel: RowLayout { id: sl - property string num: "" + property string num: "" // Kept for existing call-site compatibility. property string label: "" - property string seal: "" + property string seal: "" // Kept for existing call-site compatibility. Layout.fillWidth: true Layout.leftMargin: Style.sp(3) Layout.rightMargin: Style.sp(3) - Layout.topMargin: Style.sp(3) + Layout.topMargin: Style.sp(4) Layout.bottomMargin: Style.sp(1) - spacing: Style.sp(2) - Text { - text: sl.num - color: Tokens.inkFaint - font.family: Style.fontMono - font.pixelSize: Style.fs.micro - font.letterSpacing: Style.trackMicro - } Text { text: sl.label - color: Tokens.inkMuted + color: Tokens.inkFaint font.family: Style.fontMono font.pixelSize: Style.fs.micro font.letterSpacing: Style.trackMicro } - Hairline { Layout.fillWidth: true; soft: true } - Text { - visible: Style.decorRich && sl.seal !== "" - text: sl.seal - color: Tokens.inkFaint - font.family: Tokens.jp - font.pixelSize: Style.fs.sm - } } // An indented Library tab row (32 px), shown only while the Library is the active route. @@ -328,7 +301,7 @@ Rectangle { Rectangle { Layout.fillWidth: true Layout.bottomMargin: Style.sp(2) - implicitHeight: Style.sp(14) + implicitHeight: Style.sp(12) radius: Style.radiusCard color: "transparent" border.width: 1 @@ -369,15 +342,6 @@ Rectangle { font.letterSpacing: 2 elide: Text.ElideRight } - Text { - Layout.fillWidth: true - text: "RYOKU // MUSIC" - color: Tokens.inkFaint - font.family: Style.fontMono - font.pixelSize: Style.fs.micro - font.letterSpacing: Style.trackMicro - elide: Text.ElideRight - } } IconButton { icon: "rail-collapse" @@ -429,10 +393,6 @@ Rectangle { onActivated: if (root.activePage !== "downloads") Router.push("downloads") } - // SYSTEM - SectionLabel { num: "03"; label: "SYSTEM"; seal: "設" } - NavRow { label: "Settings"; icon: "settings"; kana: "設"; current: root.activePage === "settings"; onActivated: if (root.activePage !== "settings") Router.push("settings") } - // PLAYLISTS SectionLabel { num: "04"; label: "PLAYLISTS"; seal: "列" } Btn { @@ -456,37 +416,14 @@ Rectangle { } } } - } - } + // SYSTEM + SectionLabel { num: "03"; label: "SYSTEM"; seal: "設" } + NavRow { label: "Settings"; icon: "settings"; kana: "設"; current: root.activePage === "settings"; onActivated: if (root.activePage !== "settings") Router.push("settings") } - // --- LIVE register -------------------------------------------------------------- - RowLayout { - Layout.fillWidth: true - Layout.topMargin: Style.sp(1) - spacing: Style.sp(2) - Text { - text: "RYOKU // MUSIC" - color: Tokens.inkFaint - font.family: Style.fontMono - font.pixelSize: Style.fs.micro - font.letterSpacing: Style.trackMicro - } - Hairline { Layout.fillWidth: true; soft: true } - Rectangle { - Layout.alignment: Qt.AlignVCenter - Layout.preferredWidth: Style.sp(2) - Layout.preferredHeight: Style.sp(2) - radius: Style.sp(1) - color: Style.ambient ? Style.accent : Tokens.inkFaint - } - Text { - text: "LIVE" - color: Style.ambient ? Tokens.inkDim : Tokens.inkFaint - font.family: Style.fontMono - font.pixelSize: Style.fs.micro - font.letterSpacing: Style.trackMicro } } + + } } } diff --git a/client/chrome/SoundDialog.qml b/client/chrome/SoundDialog.qml index 69b5dce3..d3054954 100644 --- a/client/chrome/SoundDialog.qml +++ b/client/chrome/SoundDialog.qml @@ -141,7 +141,9 @@ Item { Rectangle { id: card anchors.centerIn: parent - width: Style.sp(120) + width: root.equalizerOpen + ? Math.min(Style.sp(236), root.width - Style.sp(12)) + : Style.sp(120) implicitHeight: col.implicitHeight + Style.sp(12) height: implicitHeight radius: Style.radiusCard @@ -172,13 +174,6 @@ Item { ColumnLayout { Layout.fillWidth: true spacing: Style.sp(1) - Text { - text: "SOUND" - color: Tokens.inkMuted - font.family: Style.fontMono - font.pixelSize: Style.fs.micro - font.letterSpacing: Style.trackMicro - } Text { text: "Sound" color: Tokens.ink @@ -241,10 +236,13 @@ Item { Layout.fillWidth: true spacing: Style.sp(3) + // The equalizer is a listening surface, not a scrolling form. RowLayout { Layout.fillWidth: true + spacing: Style.sp(3) + Item { Layout.fillWidth: true } Chip { - text: root.fx.eq.enabled ? "ENABLED" : "BYPASSED" + text: root.fx.eq.enabled ? "ON" : "BYPASS" active: root.fx.eq.enabled onClicked: root.setEqEnabled(!root.fx.eq.enabled) } @@ -252,124 +250,261 @@ Item { text: "FLAT" onClicked: root.resetEq() } - Item { Layout.fillWidth: true } - Text { - text: "20 PARAMETRIC BANDS" - color: Tokens.inkMuted - font.family: Style.fontMono - font.pixelSize: Style.fs.micro - } } - Canvas { - id: eqGraph + // One response drawing; it repaints only when the EQ data changes. + Item { Layout.fillWidth: true - Layout.preferredHeight: Style.sp(27) - onPaint: { - const ctx = getContext("2d"); - ctx.clearRect(0, 0, width, height); - ctx.strokeStyle = Tokens.inkMuted; - ctx.globalAlpha = 0.25; - ctx.lineWidth = 1; - for (let i = 0; i < 5; i++) { - const y = height * (i + 1) / 6; - ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(width, y); ctx.stroke(); - } - ctx.globalAlpha = root.fx.eq.enabled ? 1.0 : 0.3; - ctx.strokeStyle = Tokens.ink; - ctx.lineWidth = 2; - ctx.beginPath(); - const bands = root.fx.eq.bands || []; - for (let x = 0; x <= width; x += 2) { - const hz = 20 * Math.pow(1000, x / Math.max(1, width)); - let db = 0; - for (const band of bands) { - const delta = Math.log(hz / band.frequency) / Math.LN2; - const spread = 1 / Math.max(0.3, band.q); - db += band.gain * Math.exp(-0.5 * Math.pow(delta / spread, 2)); + Layout.preferredHeight: Math.max(Style.sp(24), + Math.min(Style.sp(38), root.height * 0.13)) + + Rectangle { + anchors.fill: parent + color: Tokens.paperLift + radius: Style.radius + } + + Canvas { + id: eqGraph + anchors.fill: parent + anchors.margins: Style.sp(3) + onPaint: { + const ctx = getContext("2d"); + ctx.clearRect(0, 0, width, height); + const bands = root.fx.eq && root.fx.eq.bands ? root.fx.eq.bands : []; + const dbAt = function(hz) { + let db = 0; + for (const band of bands) { + const delta = Math.log(hz / Math.max(1, band.frequency)) / Math.LN2; + const spread = 1 / Math.max(0.3, band.q); + db += band.gain * Math.exp(-0.5 * Math.pow(delta / spread, 2)); + } + return Math.max(-18, Math.min(18, db)); + }; + const yAt = function(db) { + return height / 2 - db * height / 42; + }; + + // Quiet grid, with a slightly stronger zero-gain axis. + ctx.lineWidth = 1; + for (const db of [-12, -6, 0, 6, 12]) { + const y = yAt(db); + ctx.globalAlpha = db === 0 ? 0.38 : 0.16; + ctx.strokeStyle = Tokens.inkMuted; + ctx.beginPath(); + ctx.moveTo(0, y); + ctx.lineTo(width, y); + ctx.stroke(); } - const y = height / 2 - Math.max(-18, Math.min(18, db)) * height / 42; - if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); + for (const hz of [100, 1000, 10000]) { + const x = width * Math.log(hz / 20) / Math.log(1000); + ctx.globalAlpha = 0.16; + ctx.beginPath(); + ctx.moveTo(x, 0); + ctx.lineTo(x, height); + ctx.stroke(); + } + + // Preview of the combined response. The player is authoritative. + ctx.globalAlpha = root.fx.eq.enabled ? 0.95 : 0.28; + ctx.strokeStyle = Style.accent; + ctx.lineWidth = 2; + ctx.beginPath(); + for (let x = 0; x <= width; x += 2) { + const hz = 20 * Math.pow(1000, x / Math.max(1, width)); + const y = yAt(dbAt(hz)); + if (x === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + } + ctx.lineTo(width, yAt(dbAt(20000))); + ctx.stroke(); + + // Selected band's position, so frequency edits have context. + const selected = root.activeBand(); + const sx = Math.max(0, Math.min(width, + width * Math.log(Math.max(20, selected.frequency) / 20) / Math.log(1000))); + const sy = yAt(dbAt(Math.max(20, selected.frequency))); + ctx.globalAlpha = root.fx.eq.enabled ? 0.8 : 0.3; + ctx.strokeStyle = Tokens.inkMuted; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(sx, 0); + ctx.lineTo(sx, height); + ctx.stroke(); + ctx.globalAlpha = 1; + ctx.fillStyle = Style.accent; + ctx.beginPath(); + ctx.arc(sx, sy, Style.sp(1.1), 0, Math.PI * 2); + ctx.fill(); + } + Connections { + target: root + function onFxChanged() { eqGraph.requestPaint(); } + function onSelectedEqBandChanged() { eqGraph.requestPaint(); } } - ctx.stroke(); - ctx.globalAlpha = 1.0; - } - Connections { - target: root - function onFxChanged() { eqGraph.requestPaint(); } } } - Flickable { + // Every band stays instantiated during dragging. No Flickable stealing + // pointer gestures and no delegate recreation on gain updates. + Row { + id: faderRow Layout.fillWidth: true - Layout.preferredHeight: Style.sp(86) - clip: true - contentHeight: eqBands.implicitHeight - boundsBehavior: Flickable.StopAtBounds - ColumnLayout { - id: eqBands - width: parent.width - spacing: Style.sp(2) - Repeater { - // Keep the 20 delegates alive while gain values change. - model: 20 - delegate: RowLayout { - id: bandRow - required property int index - readonly property var bandData: root.fx.eq.bands[bandRow.index] - Layout.fillWidth: true - spacing: Style.sp(2) - Chip { - text: Math.round(bandRow.bandData.frequency) + " Hz" - active: root.selectedEqBand === bandRow.index - onClicked: root.selectedEqBand = bandRow.index + Layout.preferredHeight: Math.max(Style.sp(34), + Math.min(Style.sp(47), root.height * 0.19)) + spacing: 0 + + Repeater { + model: 20 + delegate: Item { + id: fader + required property int index + readonly property var band: root.fx.eq.bands[fader.index] + readonly property bool selected: root.selectedEqBand === fader.index + readonly property real gain: fader.band ? fader.band.gain : 0 + width: faderRow.width / 20 + height: faderRow.height + + function gainAt(y) { + if (gainTrack.height <= 0) return; + const fraction = Math.max(0, Math.min(1, y / gainTrack.height)); + root.setEqGain(fader.index, 12 - 24 * fraction); + } + + Rectangle { + anchors.fill: parent + anchors.margins: 1 + radius: Style.radius + color: Tokens.paperLift + opacity: fader.selected ? 1 : 0 + } + Text { + anchors.top: parent.top + anchors.topMargin: Style.sp(1) + width: parent.width + horizontalAlignment: Text.AlignHCenter + text: (fader.gain > 0 ? "+" : "") + + Number(fader.gain).toFixed(1) + color: fader.selected ? Tokens.ink : Tokens.inkFaint + font.family: Style.fontMono + font.pixelSize: Style.fs.micro + visible: fader.selected || Math.abs(fader.gain) > 0.05 + } + Item { + id: gainTrack + anchors.top: parent.top + anchors.topMargin: Style.sp(7) + anchors.bottom: freqLabel.top + anchors.bottomMargin: Style.sp(3) + anchors.horizontalCenter: parent.horizontalCenter + width: Math.max(Style.sp(5), fader.width - Style.sp(1)) + + Rectangle { + anchors.centerIn: parent + width: 2 + height: parent.height + radius: 1 + color: Tokens.lineStrong + } + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + y: Math.min(parent.height / 2, + (12 - fader.gain) / 24 * parent.height) + width: 3 + height: Math.max(0.5, Math.abs(parent.height / 2 - + (12 - fader.gain) / 24 * parent.height)) + radius: 1 + color: Style.accent + opacity: root.fx.eq.enabled ? 1 : 0.35 + } + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + y: Math.max(0, Math.min(parent.height - height, + (12 - fader.gain) / 24 * parent.height - height / 2)) + width: Math.max(Style.sp(3), fader.width - Style.sp(2)) + height: Style.sp(1.3) + radius: height / 2 + color: fader.selected ? Style.accent : Tokens.inkMuted } - Slider { - Layout.fillWidth: true - from: -12 - to: 12 - value: bandRow.bandData.gain - onMoved: (v) => root.setEqGain(bandRow.index, v) - onCommitted: (v) => root.setEqGain(bandRow.index, v) + MouseArea { + id: gainMouse + anchors.fill: parent + preventStealing: true + cursorShape: Qt.SizeVerCursor + onPressed: (mouse) => { + root.selectedEqBand = fader.index; + fader.gainAt(mouse.y); + } + onPositionChanged: (mouse) => { + if (gainMouse.pressed) fader.gainAt(mouse.y); + } + onReleased: (mouse) => fader.gainAt(mouse.y) } - Text { - Layout.preferredWidth: Style.sp(14) - horizontalAlignment: Text.AlignRight - text: (bandRow.bandData.gain > 0 ? "+" : "") - + Number(bandRow.bandData.gain).toFixed(1) + " dB" - color: Tokens.inkMuted - font.family: Style.fontMono - font.pixelSize: Style.fs.xs + } + Text { + id: freqLabel + anchors.bottom: parent.bottom + anchors.bottomMargin: Style.sp(1) + width: parent.width + horizontalAlignment: Text.AlignHCenter + text: { + const hz = fader.band ? fader.band.frequency : 0; + return hz >= 1000 + ? (hz / 1000).toFixed(hz % 1000 === 0 ? 0 : 1) + "k" + : String(Math.round(hz)); } + color: fader.selected ? Tokens.ink : Tokens.inkMuted + font.family: Style.fontMono + font.pixelSize: Style.fs.micro + font.weight: fader.selected ? Font.DemiBold : Font.Normal } } } } - Text { - text: "BAND " + (root.selectedEqBand + 1) + " · FREQUENCY & Q" - color: Tokens.inkMuted - font.family: Style.fontMono - font.pixelSize: Style.fs.micro - } - FxRow { - label: "Frequency" - value: Math.round(root.activeBand().frequency) + " Hz" - from: 0 - to: 1 - val: Math.log(root.activeBand().frequency / 20) / Math.log(1000) - onMoved: (v) => root.setEqFrequency(root.selectedEqBand, v) - } - FxRow { - label: "Q (width)" - value: Number(root.activeBand().q).toFixed(1) - from: 0.3 - to: 10 - val: root.activeBand().q - onMoved: (v) => root.setEqQ(root.selectedEqBand, v) + // Precision controls are always scoped to the selected band. + RowLayout { + Layout.fillWidth: true + spacing: Style.sp(4) + ColumnLayout { + Layout.preferredWidth: Style.sp(27) + spacing: Style.sp(1) + Text { + text: "BAND " + (root.selectedEqBand + 1) + " / 20" + color: Tokens.inkMuted + font.family: Style.fontMono + font.pixelSize: Style.fs.micro + } + Text { + text: (root.activeBand().gain > 0 ? "+" : "") + + Number(root.activeBand().gain).toFixed(1) + " dB" + color: Tokens.ink + font.family: Style.fontDisplay + font.pixelSize: Style.fs.lg + } + } + FxRow { + Layout.fillWidth: true + label: "Frequency" + naturalLabel: true + value: Math.round(root.activeBand().frequency) + " Hz" + from: 0 + to: 1 + val: Math.log(root.activeBand().frequency / 20) / Math.log(1000) + onMoved: (v) => root.setEqFrequency(root.selectedEqBand, v) + } + FxRow { + Layout.fillWidth: true + label: "Q" + naturalLabel: true + value: Number(root.activeBand().q).toFixed(1) + from: 0.3 + to: 10 + val: root.activeBand().q + onMoved: (v) => root.setEqQ(root.selectedEqBand, v) + } } } - Hairline { Layout.fillWidth: true } // presets @@ -408,6 +543,7 @@ Item { id: r property string label: "" property string value: "" + property bool naturalLabel: false property real from: 0 property real to: 1 property real val: 0 @@ -417,7 +553,7 @@ Item { spacing: Style.sp(3) Text { - Layout.preferredWidth: Style.sp(16) + Layout.preferredWidth: r.naturalLabel ? implicitWidth : Style.sp(16) text: r.label color: Tokens.ink font.family: Style.fontUi diff --git a/client/chrome/TitleBar.qml b/client/chrome/TitleBar.qml index 5a02d575..d9fe5cf9 100644 --- a/client/chrome/TitleBar.qml +++ b/client/chrome/TitleBar.qml @@ -19,9 +19,11 @@ Rectangle { signal accountClicked(real gx, real gy) signal listenTogetherClicked() signal miniClicked() + signal audioLabClicked() property bool sidebarOpen: true property bool panelOpen: true + property bool audioLabOpen: false property bool discordOn: false implicitHeight: Style.titleBarH @@ -68,14 +70,6 @@ Rectangle { enabled: Router.canGoBack onClicked: Router.pop() } - IconButton { - icon: "arrow-right" - iconSize: Style.fs.lg - diameter: Style.sp(8) - tip: "Forward" - tipBelow: true - enabled: false - } Item { Layout.fillWidth: true } @@ -137,6 +131,8 @@ Rectangle { // --- search field (routes to the search page) --------------------------------------- Rectangle { id: searchField + // Home already has the full search/suggestion field. + visible: !Router.current || Router.current.page !== "home" Layout.preferredWidth: Style.sp(70) Layout.preferredHeight: Style.sp(8) Layout.alignment: Qt.AlignVCenter @@ -265,6 +261,16 @@ Rectangle { active: root.panelOpen onClicked: root.panelToggleClicked() } + IconButton { + visible: Prefs.audiophileMode + icon: "dashboard" + iconSize: Style.fs.lg + diameter: Style.sp(8) + tip: root.audioLabOpen ? "Hide Audio Lab" : "Show Audio Lab" + tipBelow: true + active: root.audioLabOpen + onClicked: root.audioLabClicked() + } IconButton { icon: "sound" iconSize: Style.fs.lg diff --git a/client/components/AudioLabWorkbench.qml b/client/components/AudioLabWorkbench.qml new file mode 100644 index 00000000..576fd902 --- /dev/null +++ b/client/components/AudioLabWorkbench.qml @@ -0,0 +1,54 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Ryoku.Ui.Singletons +import "../" + +// Single Audiophile instrument surface. Exactly one view owns audio capture. +// Spectrum keeps using the original Cava singleton; stereo scopes share one +// 25fps PCM capture bridge, activated only while their selected view is visible. +ColumnLayout { + id: root + property bool active: false + property string mode: "spectrum" + // Match the breathing room around Home controls: tabs sit above the panel, + // never pressed against the plot's top border. + spacing: Style.sp(4) + // Preferred size of the global top strip; App owns its actual height. + implicitHeight: Style.sp(48) + + RowLayout { + Layout.fillWidth: true + spacing: Style.sp(2) + Chip { + text: "Spectrum" + active: root.mode === "spectrum" + onClicked: root.mode = "spectrum" + } + Chip { + text: "Oscilloscope" + active: root.mode === "oscilloscope" + onClicked: root.mode = "oscilloscope" + } + Chip { + text: "Vectorscope" + active: root.mode === "vectorscope" + onClicked: root.mode = "vectorscope" + } + Item { Layout.fillWidth: true } + } + + AudioSpectrumWorkbench { + Layout.fillWidth: true + Layout.fillHeight: true + visible: root.mode === "spectrum" + active: root.active && root.visible && root.mode === "spectrum" + } + AudioStereoScope { + Layout.fillWidth: true + Layout.fillHeight: true + visible: root.mode !== "spectrum" + active: root.active && root.visible && root.mode !== "spectrum" + instrument: root.mode + } +} diff --git a/client/components/AudioSpectrumWorkbench.qml b/client/components/AudioSpectrumWorkbench.qml new file mode 100644 index 00000000..3811ed49 --- /dev/null +++ b/client/components/AudioSpectrumWorkbench.qml @@ -0,0 +1,198 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Ryoku.Ui.Singletons +import "../" + +// Audio Lab v1: real 40-band PipeWire/Cava spectrum, using the existing +// Spectrum singleton. No sample synthesis or simulated bars. The process only +// runs when this panel or another visible consumer has claimed it and music +// is playing. Curve and bars are two views of the same real spectrum. +Rectangle { + id: root + + property bool active: false + property string presentation: "bars" + + implicitHeight: Style.sp(46) + radius: Style.radiusCard + color: Tokens.paperLift + border.width: 1 + border.color: Tokens.line + clip: true + + function updateClaim() { + Spectrum.claim("ryotunes-audio-lab-library", root.active && root.visible); + } + onActiveChanged: updateClaim() + onVisibleChanged: updateClaim() + Component.onCompleted: updateClaim() + Component.onDestruction: Spectrum.claim("ryotunes-audio-lab-library", false) + + function rgba(c, alpha) { + return "rgba(" + Math.round(c.r * 255) + "," + Math.round(c.g * 255) + + "," + Math.round(c.b * 255) + "," + alpha + ")"; + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.sp(3) + spacing: Style.sp(2) + + RowLayout { + Layout.fillWidth: true + spacing: Style.sp(3) + + // Keep the view selector; the diagnostic header is unnecessary chrome. + Item { Layout.fillWidth: true } + Text { + text: "BARS" + color: root.presentation === "bars" ? Tokens.ink : Tokens.inkFaint + font.family: Style.fontMono + font.pixelSize: Style.fs.xs + font.weight: root.presentation === "bars" ? Font.DemiBold : Font.Normal + MouseArea { + anchors.fill: parent + anchors.margins: -Style.sp(2) + cursorShape: Qt.PointingHandCursor + onClicked: root.presentation = "bars" + } + } + Rectangle { + implicitWidth: 1 + implicitHeight: Style.sp(3) + color: Tokens.line + } + Text { + text: "CURVE" + color: root.presentation === "curve" ? Tokens.ink : Tokens.inkFaint + font.family: Style.fontMono + font.pixelSize: Style.fs.xs + font.weight: root.presentation === "curve" ? Font.DemiBold : Font.Normal + MouseArea { + anchors.fill: parent + anchors.margins: -Style.sp(2) + cursorShape: Qt.PointingHandCursor + onClicked: root.presentation = "curve" + } + } + } + + Canvas { + id: chart + Layout.fillWidth: true + Layout.fillHeight: true + renderTarget: Canvas.Image + onWidthChanged: requestPaint() + onHeightChanged: requestPaint() + + Connections { + target: Spectrum + function onLevelsChanged() { if (root.active && root.visible) chart.requestPaint(); } + } + Connections { + target: root + function onPresentationChanged() { chart.requestPaint(); } + } + Connections { + target: Style + function onAccentChanged() { chart.requestPaint(); } + } + + onPaint: { + var c = getContext("2d"); + var w = width, h = height; + c.clearRect(0, 0, w, h); + if (w < 2 || h < 2) return; + var left = Style.sp(1), right = w - Style.sp(1); + var top = Style.sp(2), bottom = h - Style.sp(3); + var cw = right - left, ch = bottom - top; + if (cw <= 0 || ch <= 0) return; + + c.lineWidth = 1; + c.strokeStyle = root.rgba(Tokens.inkFaint, 0.13); + for (var gy = 1; gy < 4; gy++) { + var ly = top + ch * gy / 4; + c.beginPath(); c.moveTo(left, ly); c.lineTo(right, ly); c.stroke(); + } + for (var gx = 1; gx < 8; gx++) { + var lx = left + cw * gx / 8; + c.beginPath(); c.moveTo(lx, top); c.lineTo(lx, bottom); c.stroke(); + } + + var values = Spectrum.levels || []; + var n = Math.min(40, values.length); + if (!Spectrum.analysing || n < 1) return; + + var slot = cw / n; + var main = Style.accent; + var low = root.rgba(main, 0.28); + var bright = root.rgba(main, 0.92); + var grad = c.createLinearGradient(0, bottom, 0, top); + grad.addColorStop(0, low); + grad.addColorStop(1, bright); + + if (root.presentation === "curve") { + c.beginPath(); c.moveTo(left, bottom); + for (var k = 0; k < n; k++) { + var value = Math.max(0, Math.min(1, Number(values[k]) || 0)); + var px = left + (k + 0.5) * slot; + var py = bottom - Math.pow(value, 0.78) * ch; + c.lineTo(px, py); + } + c.lineTo(right, bottom); c.closePath(); + c.fillStyle = grad; c.fill(); + c.beginPath(); + for (var j = 0; j < n; j++) { + var a = Math.max(0, Math.min(1, Number(values[j]) || 0)); + var x = left + (j + 0.5) * slot; + var y = bottom - Math.pow(a, 0.78) * ch; + if (j === 0) c.moveTo(x, y); else c.lineTo(x, y); + } + c.strokeStyle = bright; c.lineWidth = 1.5; c.stroke(); + } else { + c.fillStyle = grad; + var bw = Math.max(1, slot * 0.77); + for (var i = 0; i < n; i++) { + var level = Math.max(0, Math.min(1, Number(values[i]) || 0)); + if (level <= 0) continue; + var bh = Math.max(1, Math.pow(level, 0.78) * ch); + c.fillRect(left + i * slot + (slot - bw) / 2, bottom - bh, bw, bh); + } + } + } + } + RowLayout { + Layout.fillWidth: true + Text { + text: "LOW" + color: Tokens.inkFaint + font.family: Style.fontMono + font.pixelSize: Style.fs.micro + } + Item { Layout.fillWidth: true } + Text { + text: "MID" + color: Tokens.inkFaint + font.family: Style.fontMono + font.pixelSize: Style.fs.micro + } + Item { Layout.fillWidth: true } + Text { + text: "HIGH" + color: Tokens.inkFaint + font.family: Style.fontMono + font.pixelSize: Style.fs.micro + } + } + } + + Text { + anchors.centerIn: parent + visible: root.active && !Style.live + text: Playback.now ? "PAUSED · PLAY TO ANALYSE" : "PLAY A TRACK TO SEE THE SPECTRUM" + color: Tokens.inkMuted + font.family: Style.fontMono + font.pixelSize: Style.fs.sm + } +} diff --git a/client/components/AudioStereoScope.qml b/client/components/AudioStereoScope.qml new file mode 100644 index 00000000..f1bfd58b --- /dev/null +++ b/client/components/AudioStereoScope.qml @@ -0,0 +1,165 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Ryoku.Ui.Singletons +import "../" + +// Real stereo PCM views. One on-demand Python/ffmpeg bridge runs while the +// selected instrument is visible AND Ryotunes is playing. Never records audio +// to disk, and never runs while showing the ordinary Spectrum instrument. +Rectangle { + id: root + property bool active: false + property string instrument: "oscilloscope" // oscilloscope | vectorscope + property var sampleFrame: ({ wave: [], xy: [], phase: 0, rms: 0 }) + property string captureError: "" + readonly property bool analysing: root.active && root.visible && Style.live + + implicitHeight: Style.sp(46) + radius: Style.radiusCard + color: Tokens.paperLift + border.width: 1 + border.color: Tokens.line + clip: true + + function rgba(color, alpha) { + return "rgba(" + Math.round(color.r * 255) + "," + Math.round(color.g * 255) + + "," + Math.round(color.b * 255) + "," + alpha + ")"; + } + function readFrame(line) { + try { + var obj = JSON.parse(line); + if (obj.error) { + root.captureError = String(obj.error); + return; + } + if (!Array.isArray(obj.wave) || !Array.isArray(obj.xy)) return; + root.captureError = ""; + root.sampleFrame = obj; + } catch (error) { + root.captureError = "Stereo capture returned invalid data"; + } + } + + onAnalysingChanged: { + root.sampleFrame = ({ wave: [], xy: [], phase: 0, rms: 0 }); + if (root.analysing) root.captureError = ""; + } + onInstrumentChanged: plot.requestPaint() + onSampleFrameChanged: plot.requestPaint() + + Process { + id: capture + // Quickshell.shellDir is the root client directory, not the cwd. + command: ["python3", Quickshell.shellDir + "/tools/audio_scope_bridge.py"] + property bool backoff: false + running: root.analysing && !capture.backoff + stdout: SplitParser { + splitMarker: "\n" + onRead: (line) => root.readFrame(line) + } + onExited: { + if (root.analysing) { + capture.backoff = true; + restartTimer.restart(); + } + } + } + Timer { id: restartTimer; interval: 1400; onTriggered: capture.backoff = false } + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.sp(3) + spacing: Style.sp(2) + + Canvas { + id: plot + Layout.fillWidth: true + Layout.fillHeight: true + renderTarget: Canvas.Image + onWidthChanged: requestPaint() + onHeightChanged: requestPaint() + Connections { + target: Style + function onAccentChanged(): void { plot.requestPaint(); } + } + onPaint: { + var ctx = getContext("2d"); + var w = width, h = height; + ctx.clearRect(0, 0, w, h); + if (w < 4 || h < 4) return; + var left = Style.sp(2), top = Style.sp(2); + var usableW = w - 2 * left, usableH = h - 2 * top; + if (usableW <= 0 || usableH <= 0) return; + + ctx.strokeStyle = root.rgba(Tokens.inkFaint, 0.15); + ctx.lineWidth = 1; + for (var i = 1; i < 8; ++i) { + var gx = left + usableW * i / 8; + ctx.beginPath(); ctx.moveTo(gx, top); ctx.lineTo(gx, top + usableH); ctx.stroke(); + } + for (var j = 1; j < 6; ++j) { + var gy = top + usableH * j / 6; + ctx.beginPath(); ctx.moveTo(left, gy); ctx.lineTo(left + usableW, gy); ctx.stroke(); + } + + ctx.strokeStyle = root.rgba(Tokens.inkFaint, 0.35); + ctx.beginPath(); + ctx.moveTo(left, top + usableH / 2); + ctx.lineTo(left + usableW, top + usableH / 2); + ctx.stroke(); + if (!root.analysing) return; + + var packet = root.sampleFrame; + if (root.instrument === "oscilloscope") { + var values = packet.wave || []; + if (values.length < 2) return; + ctx.lineWidth = 1.6; + ctx.strokeStyle = root.rgba(Style.accent, 0.95); + ctx.beginPath(); + for (var k = 0; k < values.length; k++) { + var vx = left + k * usableW / (values.length - 1); + var vy = top + usableH / 2 - Math.max(-1, Math.min(1, values[k])) * usableH * 0.45; + if (k === 0) ctx.moveTo(vx, vy); else ctx.lineTo(vx, vy); + } + ctx.stroke(); + } else { + // X = (L - R)/sqrt(2), Y = (L + R)/sqrt(2). + // Mono is vertical; wide stereo spreads horizontally. + var pairs = packet.xy || []; + if (pairs.length < 2) return; + var side = Math.min(usableW, usableH) * 0.42; + var cx = left + usableW / 2, cy = top + usableH / 2; + ctx.strokeStyle = root.rgba(Tokens.inkFaint, 0.26); + ctx.beginPath(); ctx.moveTo(cx, cy - side); ctx.lineTo(cx, cy + side); + ctx.moveTo(cx - side, cy); ctx.lineTo(cx + side, cy); ctx.stroke(); + ctx.fillStyle = root.rgba(Style.accent, 0.48); + for (var v = 0; v < pairs.length; v++) { + var a = Number(pairs[v][0]) || 0; + var b = Number(pairs[v][1]) || 0; + var px = cx + (a - b) * side * 0.70710678; + var py = cy - (a + b) * side * 0.70710678; + ctx.fillRect(px - 1, py - 1, 2, 2); + } + } + } + } + } + + Text { + anchors.centerIn: parent + visible: !root.analysing || root.captureError !== "" + || (root.sampleFrame.rms || 0) < 0.0001 + text: root.captureError !== "" ? root.captureError + : !Style.live ? (Playback.now ? "PAUSED · PLAY TO ANALYSE" : "PLAY A TRACK TO ANALYSE") + : "WAITING FOR STEREO AUDIO" + color: Tokens.inkMuted + font.family: Style.fontMono + font.pixelSize: Style.fs.sm + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + width: parent.width - Style.sp(8) + } +} diff --git a/client/components/Chip.qml b/client/components/Chip.qml index ef78e1d5..c241b634 100644 --- a/client/components/Chip.qml +++ b/client/components/Chip.qml @@ -14,7 +14,7 @@ Rectangle { implicitHeight: Style.sp(8) implicitWidth: label.implicitWidth + Style.sp(8) - radius: height / 2 + radius: Style.radius color: active ? Tokens.bone : chipHover.hovered ? Tokens.tint10 : "transparent" border.width: 1 border.color: active ? Tokens.bone : chipHover.hovered ? Tokens.lineStrong : Tokens.line diff --git a/client/components/HomeListeningHero.qml b/client/components/HomeListeningHero.qml new file mode 100644 index 00000000..567d88ee --- /dev/null +++ b/client/components/HomeListeningHero.qml @@ -0,0 +1,146 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts +import Ryoku.Ui.Singletons +import "../" + +// A listening-first Home feature with an art-derived, static ambient wash. +// Riff-inspired composition, but Ryoku tokens, typography and native playback. +// The real player and its transport remain authoritative; no additional audio engine. +Rectangle { + id: root + + readonly property var now: Playback.now + readonly property bool compact: width < Style.sp(180) + readonly property int artSize: Style.sp(root.compact ? 34 : 48) + signal focusRequested() + + implicitHeight: Style.sp(root.compact ? 48 : 59) + radius: Style.radiusCard + color: Tokens.paperLift + border.width: 1 + border.color: Tokens.lineSoft + clip: true + + // Static cover atmosphere. One asynchronously loaded, bounded thumbnail; no blur + // shader, timer, floating gradient or per-frame effect. + Image { + id: ambientCover + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + width: parent.width * 0.56 + source: root.now && root.now.thumbnail ? Style.thumb(root.now.thumbnail, 640) : "" + sourceSize: Qt.size(640, 640) + fillMode: Image.PreserveAspectCrop + asynchronous: true + cache: true + opacity: status === Image.Ready ? (root.compact ? 0.15 : 0.28) : 0 + Behavior on opacity { + enabled: !Tokens.reduceMotion + NumberAnimation { duration: Style.motion.slow } + } + } + Rectangle { + anchors.fill: parent + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: Tokens.paperLift } + GradientStop { + position: 0.57 + color: Qt.rgba(Tokens.paperLift.r, Tokens.paperLift.g, Tokens.paperLift.b, 0.91) + } + GradientStop { + position: 1.0 + color: Qt.rgba(Tokens.paperLift.r, Tokens.paperLift.g, Tokens.paperLift.b, 0.46) + } + } + } + + RowLayout { + anchors.fill: parent + anchors.margins: Style.sp(root.compact ? 4 : 5) + spacing: Style.sp(root.compact ? 3 : 7) + + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumWidth: 0 + spacing: Style.sp(2) + + Text { + text: Playback.paused ? "CONTINUE LISTENING" : "NOW PLAYING" + color: Tokens.inkMuted + font.family: Style.fontMono + font.pixelSize: Style.fs.micro + font.letterSpacing: Style.trackMicro + } + Text { + Layout.fillWidth: true + Layout.minimumWidth: 0 + text: root.now ? root.now.title : "" + color: Tokens.ink + font.family: Style.fontDisplay + font.pixelSize: root.compact ? Style.fs.xl : Style.fs.hero + elide: Text.ElideRight + textFormat: Text.PlainText + } + Text { + Layout.fillWidth: true + Layout.minimumWidth: 0 + text: root.now ? root.now.artists : "" + color: Tokens.inkMuted + font.family: Style.fontUi + font.pixelSize: Style.fs.md + elide: Text.ElideRight + textFormat: Text.PlainText + } + + Item { Layout.fillHeight: true } + + RowLayout { + Layout.fillWidth: true + spacing: Style.sp(2) + Btn { + text: Playback.paused ? "Resume" : "Pause" + icon: Playback.paused ? "play" : "pause" + primary: true + onClicked: Playback.togglePause().catch((e) => + Playback.toast(String(e), "error")) + } + Btn { + visible: root.width >= Style.sp(147) + text: "Focus" + onClicked: root.focusRequested() + } + IconButton { + visible: root.width >= Style.sp(160) + icon: "queue" + tip: "Open queue" + outlined: true + diameter: Style.ctlH + onClicked: Playback.nowPlayingRequested("queue") + } + Item { Layout.fillWidth: true } + } + } + + Item { + Layout.preferredWidth: root.artSize + Layout.preferredHeight: root.artSize + Layout.alignment: Qt.AlignVCenter + Artwork { + anchors.fill: parent + px: root.artSize + cornerRadius: Style.radiusCard + url: root.now && root.now.thumbnail ? root.now.thumbnail : "" + } + // Artwork doubles as the entrance to the full Focus listening view. + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.focusRequested() + } + } + } +} diff --git a/client/components/MediaCard.qml b/client/components/MediaCard.qml index 4d767560..f9ff43c5 100644 --- a/client/components/MediaCard.qml +++ b/client/components/MediaCard.qml @@ -85,6 +85,7 @@ Item { Item { Layout.fillWidth: true implicitHeight: width + clip: true Artwork { id: art @@ -93,6 +94,11 @@ Item { px: root.cardWidth round: root.round cornerRadius: root.round ? art.width / 2 : Style.radiusCard + scale: cardHover.hovered && !Tokens.reduceMotion ? 1.035 : 1 + Behavior on scale { + enabled: !Tokens.reduceMotion + NumberAnimation { duration: Style.motion.snap; easing.type: Easing.OutCubic } + } placeholderIcon: root.round ? "user" : (root.item && Ids.isOnRepeatId(root.item.id)) ? "on-repeat" : "music" } diff --git a/client/components/NowPlayingCard.qml b/client/components/NowPlayingCard.qml index dfcf7759..6f719252 100644 --- a/client/components/NowPlayingCard.qml +++ b/client/components/NowPlayingCard.qml @@ -256,7 +256,7 @@ Rectangle { Layout.alignment: Qt.AlignVCenter implicitHeight: Style.sp(7) implicitWidth: pillRow.implicitWidth + Style.sp(5) - radius: height / 2 + radius: Style.radius color: pillHover.hovered ? Tokens.tint10 : "transparent" border.width: 1 border.color: pillHover.hovered ? Tokens.lineStrong : Tokens.line diff --git a/client/components/Pill.qml b/client/components/Pill.qml index 5600d7d9..798797c0 100644 --- a/client/components/Pill.qml +++ b/client/components/Pill.qml @@ -21,7 +21,7 @@ Item { Rectangle { anchors.fill: parent - radius: height / 2 + radius: Style.radius color: root.primary ? Tokens.bone : ma.pressed ? Tokens.tint16 : hover.hovered ? Tokens.tint5 diff --git a/client/components/Toggle.qml b/client/components/Toggle.qml index 4d6edfee..b553e959 100644 --- a/client/components/Toggle.qml +++ b/client/components/Toggle.qml @@ -18,7 +18,7 @@ Item { Rectangle { anchors.fill: parent - radius: height / 2 + radius: Style.radius color: root.checked ? Tokens.sun : Tokens.tint16 border.width: 1 border.color: root.checked ? Tokens.sun : Tokens.line diff --git a/client/pages/HomePage.qml b/client/pages/HomePage.qml index 00b85446..5833c883 100644 --- a/client/pages/HomePage.qml +++ b/client/pages/HomePage.qml @@ -20,6 +20,11 @@ import "../lib/recommendations.js" as Recommendations Item { id: page + // The App-owned Audio Lab switch is passed only to HomePage. On Home it + // belongs in the feed between the search box and the listening hero. + property bool audioLabEnabled: false + signal focusRequested() + property var home: null property var chips: [] property var forgotten: null @@ -195,7 +200,7 @@ Item { cacheBuffer: Math.max(0, Math.round(height * 1.5)) boundsBehavior: Flickable.StopAtBounds model: blocks - spacing: Style.sp(10) + spacing: Style.sp(7) // HomeFeed releases its top pin without restoring a stale pre-layout offset. onContentYChanged: page.maybeLoadMore() @@ -220,12 +225,12 @@ Item { width: list.width // The list's spacing runs between delegates only; the header carries its own section // gap so the first shelf never rides up against the last header block. - implicitHeight: headerCol.implicitHeight + Style.sp(10) + implicitHeight: headerCol.implicitHeight + Style.sp(5) ColumnLayout { id: headerCol width: parent.width - spacing: Style.sp(10) + spacing: Style.sp(6) // hero: the greeting, search and key hints on the left (inset 48 px); the Now // Playing card on the right when the page is wide enough that it never crowds the @@ -239,34 +244,13 @@ Item { ColumnLayout { Layout.fillWidth: true - Layout.maximumWidth: Style.sp(120) + // Fill narrower pages; reserve the narrow reading column only + // beside the wide-layout listening card. + Layout.maximumWidth: 16777215 Layout.minimumWidth: Style.sp(70) - Layout.leftMargin: Style.sp(4) Layout.alignment: Qt.AlignTop spacing: Style.sp(2) - // — 力 HOME / LISTEN ··· 01 - RowLayout { - Layout.fillWidth: true - spacing: Style.sp(2) - Rectangle { Layout.preferredWidth: Style.sp(4); Layout.preferredHeight: 1; Layout.alignment: Qt.AlignVCenter; color: Tokens.ink } - Text { text: "力"; color: Tokens.ink; font.family: Tokens.jp; font.pixelSize: Style.fs.sm } - Text { - text: "HOME / LISTEN" - color: Tokens.inkFaint - font.family: Style.fontMono - font.pixelSize: Style.fs.micro - font.letterSpacing: Style.trackMicro - } - Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; Layout.alignment: Qt.AlignVCenter; color: Tokens.lineSoft } - Text { - text: "01" - color: Tokens.inkFaint - font.family: Style.fontMono - font.pixelSize: Style.fs.micro - font.letterSpacing: Style.trackMicro - } - } Text { Layout.fillWidth: true text: page.greeting() + ((Playback.auth && Playback.auth.signedIn && Playback.auth.name) ? (", " + Playback.auth.name) : "") @@ -327,70 +311,25 @@ Item { } } - // key hints - RowLayout { - Layout.topMargin: Style.sp(1) - spacing: Style.sp(2) - Text { text: "Ctrl+K"; color: Tokens.inkMuted; font.family: Style.fontMono; font.pixelSize: Style.fs.micro; font.letterSpacing: Style.trackMicro } - Text { text: "command search"; color: Tokens.inkFaint; font.family: Style.fontUi; font.pixelSize: Style.fs.xs } - Rectangle { Layout.preferredWidth: Style.sp(4); Layout.preferredHeight: 1; Layout.alignment: Qt.AlignVCenter; color: Tokens.lineSoft } - Text { text: "Space"; color: Tokens.inkMuted; font.family: Style.fontMono; font.pixelSize: Style.fs.micro; font.letterSpacing: Style.trackMicro } - Text { text: "play / pause"; color: Tokens.inkFaint; font.family: Style.fontUi; font.pixelSize: Style.fs.xs } - } } - Item { Layout.fillWidth: true; Layout.minimumWidth: hero.wide ? Style.sp(10) : 0 } - NowPlayingCard { - visible: hero.wide && !!Playback.now - Layout.preferredWidth: Style.sp(138) - Layout.maximumWidth: Style.sp(140) - Layout.alignment: Qt.AlignTop - onOpenQueue: Playback.nowPlayingRequested("queue") - } } - // The restored listening session must remain reachable on a narrow Home too. - RowLayout { + // Keep the original shallow analyser below Home's search bar, + // above the artwork-led listening card. The feed owns the height, + // so tracks and recommendations remain reachable by scrolling. + AudioLabWorkbench { + Layout.fillWidth: true + Layout.preferredHeight: Style.sp(48) + visible: page.audioLabEnabled + active: visible + } + + // One listening stage across desktop sizes, no duplicated transport. + HomeListeningHero { Layout.fillWidth: true visible: !!Playback.now - spacing: Style.sp(3) - Artwork { - visible: !hero.wide - url: Playback.now && Playback.now.thumbnail ? Playback.now.thumbnail : "" - px: Style.sp(14) - } - ColumnLayout { - Layout.fillWidth: true - spacing: Style.sp(1) - Text { - Layout.fillWidth: true - text: Playback.paused ? "Continue listening" : "Your listening session" - color: Tokens.ink - font.family: Style.fontUi - font.pixelSize: Style.fs.md - font.weight: Font.Medium - } - Text { - Layout.fillWidth: true - text: Playback.now ? Playback.now.title + " — " + Playback.now.artists : "" - color: Tokens.inkMuted - font.family: Style.fontUi - font.pixelSize: Style.fs.sm - elide: Text.ElideRight - textFormat: Text.PlainText - } - } - Btn { - text: Playback.paused ? "Resume" : "Pause" - icon: Playback.paused ? "play" : "pause" - primary: Playback.paused - onClicked: Playback.togglePause().catch((e) => Playback.toast(String(e), "error")) - } - IconButton { - icon: "queue" - tip: "Open your queue" - onClicked: Playback.nowPlayingRequested("queue") - } + onFocusRequested: page.focusRequested() } HomePersonal { @@ -404,7 +343,7 @@ Item { Item { Layout.fillWidth: true implicitHeight: chipRow.implicitHeight - visible: page.chips.length > 0 + visible: page.chips.length > 0 || page.selected === "" Flickable { id: chipsFlick anchors.fill: parent @@ -417,6 +356,7 @@ Item { id: chipRow spacing: Style.sp(2) Chip { + visible: page.chips.length > 0 text: "All" active: page.selected === "" onClicked: page.load("") @@ -430,6 +370,13 @@ Item { onClicked: page.load(page.selected === modelData.params ? "" : modelData.params) } } + // The creation action stays discoverable without a large, + // otherwise empty section taking space above the music. + Chip { + visible: page.selected === "" + text: "+ Shortcut" + onClicked: shortcutPicker.open() + } } } Rectangle { @@ -446,17 +393,17 @@ Item { } } - // shortcuts (pinned, unfiltered only) — bordered 56 px cards, then a dashed - // "Add shortcut" card of the same height + // Show the full shortcuts section once populated. Before then, + // the filter rail offers a compact "+ Shortcut" action instead. ColumnLayout { Layout.fillWidth: true - visible: page.selected === "" + visible: false spacing: Style.sp(4) RowLayout { Layout.fillWidth: true spacing: Style.sp(2) Icon { Layout.alignment: Qt.AlignVCenter; name: "dashboard"; size: Style.fs.md; color: Tokens.inkMuted } - SectionHeading { Layout.fillWidth: true; title: "// Shortcuts"; mark: Style.decorRich ? "選" : "" } + SectionHeading { Layout.fillWidth: true; title: "Shortcuts"; mark: Style.decorRich ? "選" : "" } } Flow { id: pinFlow diff --git a/client/pages/SettingsPage.qml b/client/pages/SettingsPage.qml index f3ba50a7..9d90397d 100644 --- a/client/pages/SettingsPage.qml +++ b/client/pages/SettingsPage.qml @@ -61,6 +61,7 @@ Item { readonly property var sections: [ { k: "general", l: "General", jp: "全般" }, { k: "playback", l: "Playback", jp: "再生" }, + { k: "audiophile", l: "Audiophile", jp: "音響" }, { k: "downloads", l: "Downloads", jp: "保存" }, { k: "data", l: "Data & storage", jp: "保存" }, { k: "account", l: "Account", jp: "鍵" }, @@ -550,6 +551,7 @@ Item { var h = 0; if (page.section === "general") h = generalCol.implicitHeight; else if (page.section === "playback") h = playbackCol.implicitHeight; + else if (page.section === "audiophile") h = audiophileCol.implicitHeight; else if (page.section === "data") h = dataCol.implicitHeight; else if (page.section === "downloads") h = downloadSettingsLoader.item ? downloadSettingsLoader.item.implicitHeight : 0; else if (page.section === "account") h = accountCol.implicitHeight; @@ -854,6 +856,117 @@ Item { } } + // ─────────────────────────── AUDIOPHILE MODE ─────────────────────────── + ColumnLayout { + id: audiophileCol + visible: page.section === "audiophile" + anchors { left: parent.left; right: parent.right; top: parent.top; leftMargin: Style.sp(6); rightMargin: Style.sp(6); topMargin: Style.sp(4) } + spacing: Style.sp(4) + + SectionHeading { Layout.fillWidth: true; title: "Audiophile Mode"; mark: "音響" } + + Text { + Layout.fillWidth: true + text: "A deeper listening workspace, without turning the everyday music player into a control room." + color: Tokens.inkMuted + font.family: Style.fontUi + font.pixelSize: Style.fs.sm + wrapMode: Text.WordWrap + } + + ToggleRow { + title: "Enable Audiophile Mode" + desc: "Unlock the advanced visualisers and listening tools. Hidden analysers stay idle when their view is closed." + value: Prefs.audiophileMode + onFlipped: (on) => { + Prefs.audiophileMode = on; + Prefs.save(); + } + } + + Text { + Layout.fillWidth: true + text: "This is a workspace switch, not an audio bypass. Your EQ curve and enabled state are preserved independently." + color: Tokens.inkFaint + font.family: Style.fontUi + font.pixelSize: Style.fs.sm + wrapMode: Text.WordWrap + } + + SectionHeading { + Layout.fillWidth: true + Layout.topMargin: Style.sp(3) + visible: Prefs.audiophileMode + title: "Instruments" + mark: "01" + } + + RowLayout { + Layout.fillWidth: true + visible: Prefs.audiophileMode + spacing: Style.sp(4) + ColumnLayout { + Layout.fillWidth: true + spacing: 1 + Text { text: "Spectrum analyser"; color: Tokens.ink; font.family: Style.fontUi; font.pixelSize: Style.fs.md; font.weight: Font.Medium } + Text { Layout.fillWidth: true; text: "Real 40-band spectrum, with Bars and Curve views. Available throughout Ryotunes."; color: Tokens.inkMuted; font.family: Style.fontUi; font.pixelSize: Style.fs.sm; wrapMode: Text.WordWrap } + } + Pill { + label: "Show Audio Lab" + onClicked: Prefs.showAudioLabRequested() + } + } + + RowLayout { + Layout.fillWidth: true + visible: Prefs.audiophileMode + spacing: Style.sp(4) + ColumnLayout { + Layout.fillWidth: true + spacing: 1 + Text { text: "20-band parametric EQ"; color: Tokens.ink; font.family: Style.fontUi; font.pixelSize: Style.fs.md; font.weight: Font.Medium } + Text { Layout.fillWidth: true; text: "Frequency, gain and Q, with the saved curve controlled in Sound. EQ playback is independent of this mode."; color: Tokens.inkMuted; font.family: Style.fontUi; font.pixelSize: Style.fs.sm; wrapMode: Text.WordWrap } + } + Pill { + label: "Open Sound" + onClicked: Playback.soundRequested() + } + } + + RowLayout { + Layout.fillWidth: true + visible: Prefs.audiophileMode + spacing: Style.sp(4) + ColumnLayout { + Layout.fillWidth: true + spacing: 1 + Text { text: "Stereo instruments"; color: Tokens.ink; font.family: Style.fontUi; font.pixelSize: Style.fs.md; font.weight: Font.Medium } + Text { Layout.fillWidth: true; text: "Oscilloscope and vectorscope from real stereo samples, available on any page."; color: Tokens.inkMuted; font.family: Style.fontUi; font.pixelSize: Style.fs.sm; wrapMode: Text.WordWrap } + } + Pill { + label: "Show Audio Lab" + onClicked: Prefs.showAudioLabRequested() + } + } + + SectionHeading { + Layout.fillWidth: true + Layout.topMargin: Style.sp(3) + visible: Prefs.audiophileMode + title: "On the roadmap" + mark: "02" + } + Text { + Layout.fillWidth: true + visible: Prefs.audiophileMode + text: "Enhanced lyrics: optional cinematic presentation, retaining existing sync, timing offsets and click-to-seek." + color: Tokens.inkMuted + font.family: Style.fontUi + font.pixelSize: Style.fs.sm + wrapMode: Text.WordWrap + } + } + // ─────────────────────────── PLAYBACK ─────────────────────────── ColumnLayout { id: playbackCol diff --git a/client/tools/audio_scope_bridge.py b/client/tools/audio_scope_bridge.py new file mode 100644 index 00000000..23bfa31f --- /dev/null +++ b/client/tools/audio_scope_bridge.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +"""On-demand Ryotunes stereo PCM -> small newline-delimited JSON frames. + +Requires existing ffmpeg and pactl. Captures ONLY a Pulse/PipeWire output monitor. +Never writes audio to a file; terminates ffmpeg on exit. No fake input frames. +""" +from __future__ import annotations + +import array +import json +import math +import os +import signal +import subprocess +import sys + +RATE = 48000 +FRAMES = 1920 # 25 fps, one process shared by BOTH scope views +TRACE_POINTS = 180 +PHASE_POINTS = 240 + + +def analyse(audio: array.array, last_scale: float) -> tuple[dict, float]: + left = audio[::2] + right = audio[1::2] + n = min(len(left), len(right)) + if n < 2: + raise ValueError("Not enough stereo samples") + + power_l = sum(float(a) * a for a in left) + power_r = sum(float(b) * b for b in right) + cross = sum(float(a) * float(b) for a, b in zip(left, right)) + denom = math.sqrt(power_l * power_r) + phase = max(-1.0, min(1.0, cross / denom)) if denom > 1e-12 else 0.0 + peak = max(max(abs(x) for x in left), max(abs(x) for x in right)) + rms = math.sqrt((power_l + power_r) / (2 * n)) + # The amplitude scaling is intentionally indicated in the UI: the shapes + # are measured stereo samples, but a quiet master should still be visible. + desired = min(18.0, 0.85 / max(0.04, peak)) + scale = 0.75 * last_scale + 0.25 * desired + if rms < 0.0001: + return {"wave": [], "xy": [], "phase": 0.0, "rms": 0.0}, scale + + mono = [(float(a) + float(b)) * 0.5 for a, b in zip(left, right)] + # Rising zero-crossing triggers keep a roughly periodic tone stable. + start = next((j for j in range(12, min(n - 1000, 650)) + if mono[j] <= 0 and mono[j + 1] > 0), 0) + window = min(1000, n - start) + wave = [round(max(-1, min(1, mono[start + (i * (window - 1) // (TRACE_POINTS - 1))] * scale)), 4) + for i in range(TRACE_POINTS)] + xy = [[round(max(-1, min(1, float(left[i * (n - 1) // (PHASE_POINTS - 1)]) * scale)), 4), + round(max(-1, min(1, float(right[i * (n - 1) // (PHASE_POINTS - 1)]) * scale)), 4)] + for i in range(PHASE_POINTS)] + return {"wave": wave, "xy": xy, "phase": round(phase, 3), "rms": round(rms, 5)}, scale + + +def self_test() -> None: + def stereo(fn): + data = array.array("f") + for i in range(FRAMES): + l = 0.12 * math.sin(i * 2 * math.pi * 440 / RATE) + r = fn(l, i) + data.extend((l, r)) + return data + + same, _ = analyse(stereo(lambda l, i: l), 1.0) + inverse, _ = analyse(stereo(lambda l, i: -l), 1.0) + independent, _ = analyse(stereo(lambda l, i: 0.12 * math.cos(i * 2 * math.pi * 440 / RATE)), 1.0) + assert len(same["wave"]) == TRACE_POINTS + assert len(same["xy"]) == PHASE_POINTS + assert same["phase"] > 0.999, same["phase"] + assert inverse["phase"] < -0.999, inverse["phase"] + assert abs(independent["phase"]) < 0.05, independent["phase"] + assert all(-1 <= x <= 1 for x in same["wave"]) + print("PASS: stereo correlation, real sample trace and frame bounds") + + +def sink_monitor() -> str: + sink = subprocess.check_output(["pactl", "get-default-sink"], text=True, timeout=5).strip() + if not sink or "/" in sink or "\n" in sink: + raise RuntimeError("Invalid default PipeWire sink") + source = sink + ".monitor" + lines = subprocess.check_output(["pactl", "list", "short", "sources"], text=True, timeout=5) + names = {parts[1] for line in lines.splitlines() if (parts := line.split()) and len(parts) > 1} + if source not in names: + raise RuntimeError("Default output monitor is unavailable") + return source + + +def capture() -> None: + source = sink_monitor() + command = [ + "ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error", + "-f", "pulse", "-sample_rate", str(RATE), "-channels", "2", "-i", source, + "-vn", "-ac", "2", "-f", "f32le", "-acodec", "pcm_f32le", "-", + ] + proc = subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, bufsize=FRAMES * 8) + try: + assert proc.stdout is not None + scale = 1.0 + while True: + raw = proc.stdout.read(FRAMES * 8) + if len(raw) != FRAMES * 8: + break + audio = array.array("f") + audio.frombytes(raw) + if sys.byteorder != "little": + audio.byteswap() + payload, scale = analyse(audio, scale) + print(json.dumps(payload, separators=(",", ":")), flush=True) + finally: + if proc.poll() is None: + proc.terminate() + try: + proc.wait(timeout=2) + except subprocess.TimeoutExpired: + proc.kill() + proc.wait(timeout=2) + + +def main() -> None: + if sys.argv[1:] == ["--self-test"]: + self_test() + return + signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit(0)) + try: + capture() + except (OSError, subprocess.SubprocessError, RuntimeError) as error: + print(json.dumps({"error": str(error)}), flush=True) + sys.exit(1) + except BrokenPipeError: + return + + +if __name__ == "__main__": + main() diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 50d2613a..9f8ba7ba 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -18,6 +18,7 @@ pub mod session; pub mod soundcloud_bridge; pub mod spotify; pub mod spotify_bridge; +pub mod soloist; pub mod state; pub mod tidal_bridge; pub mod update; diff --git a/crates/core/src/soloist.rs b/crates/core/src/soloist.rs new file mode 100644 index 00000000..cdab7e20 --- /dev/null +++ b/crates/core/src/soloist.rs @@ -0,0 +1,443 @@ +//! Optional bridge to Spotify Soloist's localhost-only WebSocket API. +//! +//! This module never launches Soloist, receives an API key, reads its credential store, +//! or changes the existing librespot/MPV player. It connects to the *already-running* +//! user-owned Soloist process for a short RPC and closes the socket afterward. + +use std::net::IpAddr; +use std::path::PathBuf; +use std::time::Duration; + +use anyhow::{anyhow, bail, ensure, Context as _, Result}; +use futures_util::{SinkExt as _, StreamExt as _}; +use serde_json::{json, Value}; +use tokio::time::{timeout, Instant}; +use tokio_tungstenite::{connect_async, tungstenite::Message}; + +const CONNECT_TIMEOUT: Duration = Duration::from_secs(3); +const REPLY_TIMEOUT: Duration = Duration::from_secs(5); +const MAX_REPLY_BYTES: usize = 2 * 1024 * 1024; + +/// The bridge's probe state is separate from Spotify's original librespot auth state. +/// A missing Soloist process must never mark the old Spotify provider signed out. +pub struct SoloistBridge { + data_dir: PathBuf, +} + +impl SoloistBridge { + pub fn discover() -> Self { + let home = std::env::var_os("HOME").map(PathBuf::from).unwrap_or_default(); + let data_dir = + std::env::var_os("RYOTUNES_SOLOIST_DATA_DIR").map(PathBuf::from).unwrap_or_else(|| { + // Respect the isolated test session we have already paired; the + // production default remains the ordinary Soloist data directory. + let eval = home.join(".local/share/ryotunes-soloist-eval"); + if eval.is_dir() { + eval + } else { + std::env::var_os("XDG_DATA_HOME") + .map(PathBuf::from) + .unwrap_or_else(|| home.join(".local/share")) + .join("soloist") + } + }); + Self { data_dir } + } + + fn endpoint(&self) -> Result { + let raw_host = std::fs::read_to_string(self.data_dir.join("ws.addr")) + .context("Soloist is not running with its local WebSocket API (ws.addr missing)")?; + let host = raw_host.trim(); + let address: IpAddr = host.parse().context("invalid Soloist WebSocket address")?; + ensure!(address.is_loopback(), "refusing a non-local Soloist WebSocket address"); + let raw_port = std::fs::read_to_string(self.data_dir.join("ws.port")) + .context("Soloist WebSocket port is unavailable (ws.port missing)")?; + let port: u16 = raw_port.trim().parse().context("invalid Soloist WebSocket port")?; + ensure!(port != 0, "Soloist WebSocket port must be nonzero"); + let host = match address { + IpAddr::V4(v4) => v4.to_string(), + IpAddr::V6(v6) => format!("[{v6}]"), + }; + Ok(format!("ws://{host}:{port}")) + } + + async fn connect( + &self, + ) -> Result< + tokio_tungstenite::WebSocketStream< + tokio_tungstenite::MaybeTlsStream, + >, + > { + let url = self.endpoint()?; + let (socket, _) = timeout(CONNECT_TIMEOUT, connect_async(url.as_str())) + .await + .context("timed out contacting Soloist's local WebSocket")? + .context("cannot connect to Soloist's local WebSocket")?; + Ok(socket) + } + + /// Read the actual authenticated/active-device status, not a guessed process state. + pub async fn status(&self) -> Result { + let mut socket = self.connect().await?; + Self::send(&mut socket, json!({"type":"command", "command":"get_auth_state"})).await?; + Self::receive(&mut socket, "auth_state", None).await + } + + /// The official full snapshot, plus a Ryotunes-friendly rendering of its item. + pub async fn now(&self) -> Result { + let mut socket = self.connect().await?; + Self::send(&mut socket, json!({"type":"command", "command":"get_state"})).await?; + let raw = Self::receive(&mut socket, "playback_state", None).await?; + Ok(json!({ "now": normalize_now(&raw), "source": raw })) + } + + pub async fn queue(&self, limit: u32) -> Result { + ensure!(limit <= 80, "Soloist queue limit must not exceed 80"); + let mut socket = self.connect().await?; + Self::send(&mut socket, json!({"type":"command", "command":"get_queue", "limit": limit})) + .await?; + Self::receive(&mut socket, "queue_changed", None).await + } + + /// Observe the existing Soloist playback process. No playback commands are issued. + /// Each incoming notification is published independently of the legacy MPV player. + /// The caller owns reconnection and the lifetime of the listening task. + pub async fn watch_stream(&self, mut publish: F) -> Result<()> + where + F: FnMut(Value) + Send, + { + let mut socket = self.connect().await?; + + // A new observer can attach after a track started. Request snapshots so + // its subscribers do not depend on receiving a future change first. + Self::send(&mut socket, json!({"type":"command", "command":"get_state"})).await?; + Self::send(&mut socket, json!({"type":"command", "command":"get_queue", "limit": 10})) + .await?; + + let mut keepalive = tokio::time::interval(Duration::from_secs(30)); + keepalive.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + loop { + tokio::select! { + _ = keepalive.tick() => { + socket.send(Message::Ping(Vec::new().into())) + .await.context("cannot ping Soloist event socket")?; + } + incoming = socket.next() => { + let incoming = incoming + .ok_or_else(|| anyhow!("Soloist event socket closed"))? + .context("Soloist event WebSocket error")?; + match incoming { + Message::Text(text) => { + if text.len() > MAX_REPLY_BYTES { + tracing::warn!("dropping oversized Soloist event"); + continue; + } + let source: Value = match serde_json::from_str(&text) { + Ok(value) => value, + Err(error) => { + tracing::debug!(%error, "ignoring invalid Soloist event JSON"); + continue; + } + }; + if let Some(event) = event_payload(source) { + publish(event); + } + } + Message::Ping(data) => { + socket.send(Message::Pong(data)) + .await.context("cannot reply to Soloist ping")?; + } + Message::Close(_) => bail!("Soloist closed its event stream"), + _ => {} + } + } + } + } + } + + /// Explicit whitelist: never allow arbitrary messages or any account/auth commands. + /// A successful command_result means *dispatched*, not that Spotify has acted yet. + pub async fn control(&self, action: &str, args: &Value) -> Result { + let mut socket = self.connect().await?; + let requests = control_messages(action, args)?; + for request in requests { + let expected = request["command"].as_str().unwrap_or_default().to_owned(); + Self::send(&mut socket, request).await?; + Self::receive(&mut socket, "command_result", Some(&expected)).await?; + } + Ok(json!({ "accepted": true, "action": action })) + } + + async fn send(socket: &mut tokio_tungstenite::WebSocketStream, value: Value) -> Result<()> + where + S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin, + { + socket + .send(Message::Text(value.to_string().into())) + .await + .context("cannot send Soloist request") + } + + async fn receive( + socket: &mut tokio_tungstenite::WebSocketStream, + desired_type: &str, + desired_command: Option<&str>, + ) -> Result + where + S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin, + { + let deadline = Instant::now() + REPLY_TIMEOUT; + loop { + let remaining = deadline.saturating_duration_since(Instant::now()); + if remaining.is_zero() { + bail!("Soloist did not return {desired_type} in time"); + } + let frame = timeout(remaining, socket.next()) + .await + .context("Soloist response timed out")? + .ok_or_else(|| anyhow!("Soloist closed the WebSocket"))? + .context("Soloist WebSocket error")?; + match frame { + Message::Text(text) => { + ensure!( + text.len() <= MAX_REPLY_BYTES, + "Soloist response exceeds the size limit" + ); + let response: Value = + serde_json::from_str(&text).context("invalid JSON in Soloist reply")?; + let kind = response["type"].as_str().unwrap_or(""); + if kind == "error" { + bail!( + "Soloist: {}", + response["message"].as_str().unwrap_or("unknown error") + ); + } + if kind == desired_type + && desired_command + .is_none_or(|name| response["command"].as_str() == Some(name)) + { + return Ok(response); + } + // The websocket emits auth_state and possibly playback_state on + // connect. Ignore unsolicited events until our requested response. + } + Message::Ping(bytes) => { + socket + .send(Message::Pong(bytes)) + .await + .context("cannot answer Soloist ping")?; + } + Message::Close(_) => bail!("Soloist closed the WebSocket during the request"), + _ => {} + } + } + } +} + +fn valid_spotify_uri(uri: &str, track_only: bool) -> Result<()> { + let allowed = ["spotify:track:", "spotify:album:", "spotify:playlist:", "spotify:episode:"]; + let prefix = allowed.iter().find(|prefix| uri.starts_with(**prefix)); + ensure!(prefix.is_some(), "unsupported Spotify playback URI"); + ensure!(!track_only || prefix == Some(&"spotify:track:"), "queue items must be Spotify tracks"); + let id = uri.rsplit(':').next().unwrap_or(""); + ensure!( + id.len() == 22 && id.bytes().all(|b| b.is_ascii_alphanumeric()), + "invalid Spotify URI identifier" + ); + Ok(()) +} + +fn control_messages(action: &str, args: &Value) -> Result> { + let msg = |name| json!({ "type": "command", "command": name }); + let requests = match action { + "play" => { + let mut request = msg("play"); + if let Some(uri) = args.get("uri") { + let uri = uri.as_str().ok_or_else(|| anyhow!("uri must be a string"))?; + valid_spotify_uri(uri, false)?; + request["uri"] = json!(uri); + } + vec![request] + } + "pause" | "skip_next" | "skip_prev" | "activate" | "deactivate" => vec![msg(action)], + "seek" => { + let pos = + args["positionMs"].as_u64().context("positionMs must be a nonnegative integer")?; + ensure!(pos <= 86_400_000, "seek position exceeds 24 hours"); + vec![json!({"type":"command", "command":"seek", "position_ms": pos})] + } + "volume" => { + let v = args["volume"].as_u64().context("volume must be an integer")?; + ensure!(v <= 100, "volume must be 0–100"); + vec![json!({"type":"command", "command":"set_volume", "volume": v})] + } + "shuffle" => { + let value = args["enabled"].as_bool().context("enabled must be a boolean")?; + vec![json!({"type":"command", "command":"set_shuffle", "enabled": value})] + } + "repeat" => match args["mode"].as_str() { + Some("off") => vec![ + json!({"type":"command", "command":"set_repeat_track", "enabled": false}), + json!({"type":"command", "command":"set_repeat_context", "enabled": false}), + ], + Some("context") => vec![ + json!({"type":"command", "command":"set_repeat_track", "enabled": false}), + json!({"type":"command", "command":"set_repeat_context", "enabled": true}), + ], + Some("track") => vec![ + json!({"type":"command", "command":"set_repeat_context", "enabled": false}), + json!({"type":"command", "command":"set_repeat_track", "enabled": true}), + ], + _ => bail!("repeat mode must be off, context or track"), + }, + "add_to_queue" => { + let uri = args["uri"].as_str().context("uri must be a Spotify track URI")?; + valid_spotify_uri(uri, true)?; + vec![json!({"type":"command", "command":"add_to_queue", "uri": uri})] + } + _ => bail!("unsupported Soloist control action: {action}"), + }; + Ok(requests) +} + +/// Snapshot mapping for the current Ryotunes `SongItem` conventions. +/// Keep the raw source alongside it; this does not yet replace the live MPV model. +pub fn normalize_now(source: &Value) -> Value { + let item = &source["item"]; + let decorations = &item["decorations"]; + let creators = decorations["creators"].as_array(); + let artists = creators + .map(|list| { + list.iter() + .filter_map(|person| person["entity"]["decorations"]["identity"]["name"].as_str()) + .collect::>() + .join(", ") + }) + .unwrap_or_default(); + let covers = decorations["visual_identity"]["cover"].as_array(); + let thumbnail = covers.and_then(|list| { + list.iter() + .find(|cover| cover["size"] == "large") + .or_else(|| list.iter().find(|cover| cover["size"] == "xlarge")) + .or_else(|| list.last()) + .and_then(|cover| cover["url"].as_str()) + }); + let status = source["status"].as_str().unwrap_or("idle"); + json!({ + "videoId": item["uri"].as_str(), + "title": decorations["identity"]["name"].as_str(), + "artists": artists, + "album": decorations["parent"]["entity"]["decorations"]["identity"]["name"].as_str(), + "albumId": decorations["parent"]["entity"]["uri"].as_str(), + "thumbnail": thumbnail, + "durationMs": decorations["playback"]["duration_ms"].as_u64(), + "positionMs": source["position"]["position_ms"].as_u64(), + "timestampMs": source["position"]["timestamp_ms"].as_u64(), + "speed": source["position"]["speed"].as_f64(), + "volume": source["volume"].as_u64(), + "paused": status != "playing", + "status": status, + "isActive": source["is_active"].as_bool().unwrap_or(false), + "availableActions": &source["available_actions"], + "contextUri": source["context"]["uri"].as_str(), + }) +} + +/// Explicit notification allowlist: never forward auth or arbitrary command replies +/// into the UI event bus. Keep full provider data under `source`; the normalised +/// `now` is included only for complete playback snapshots. +fn event_payload(source: Value) -> Option { + let kind = source.get("type")?.as_str()?.to_owned(); + if !matches!( + kind.as_str(), + "playback_state" + | "playback_changed" + | "position_sync" + | "device_changed" + | "queue_changed" + | "track_changed" + | "volume_changed" + | "options_changed" + ) { + return None; + } + let mut event = json!({ "kind": kind, "source": source }); + if kind == "playback_state" { + event["now"] = normalize_now(&event["source"]); + } + Some(event) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn soloist_live_events_are_namespaced_and_filter_private_messages() { + let playback = json!({ + "type": "playback_state", "status": "buffering", "is_active": false, + "item": { "uri": "spotify:track:3stWWPN41byqp8loPdy92u", "decorations": { + "identity": { "name": "Master of None" }, "creators": [], + "playback": { "duration_ms": 192240 } + }} + }); + let event = event_payload(playback).expect("playback is allowed"); + assert_eq!(event["kind"], "playback_state"); + assert_eq!(event["now"]["title"], "Master of None"); + assert_eq!(event["now"]["status"], "buffering"); + assert_eq!(event["now"]["isActive"], false); + let active = event_payload(json!({ + "type": "device_changed", "is_active": true, + "device_name": "Ryotunes Soloist Test" + })) + .expect("active device transition"); + assert_eq!(active["source"]["is_active"], true); + assert!(event_payload(json!({"type":"auth_state", "token":"secret"})).is_none()); + assert!(event_payload(json!({"type":"command_result", "command":"pause"})).is_none()); + } + + #[test] + fn parses_a_realistic_playback_snapshot() { + let raw = json!({ + "type": "playback_state", "status": "playing", "is_active": true, + "item": {"uri": "spotify:track:2X485T9Z5Ly0xyaghN73ed", "decorations": { + "identity": {"name": "Let It Happen"}, + "visual_identity": {"cover": [{"url": "small", "size": "small"}, {"url": "large", "size": "large"}]}, + "parent": {"entity": {"uri": "spotify:album:79dL7FLiJFOO0EoehUHQBv", "decorations": {"identity": {"name": "Currents"}}}}, + "creators": [{"entity": {"decorations": {"identity": {"name": "Tame Impala"}}}}], + "playback": {"duration_ms": 459877} + }}, + "position": {"position_ms": 276807, "timestamp_ms": 1793220359871u64, "speed": 1.0}, + "volume": 100, "available_actions": {"pause": {}} + }); + let now = normalize_now(&raw); + assert_eq!(now["title"], "Let It Happen"); + assert_eq!(now["artists"], "Tame Impala"); + assert_eq!(now["album"], "Currents"); + assert_eq!(now["thumbnail"], "large"); + assert_eq!(now["durationMs"], 459877); + assert_eq!(now["positionMs"], 276807); + assert_eq!(now["paused"], false); + } + + #[test] + fn blocks_unsafe_controls_and_bad_uris() { + assert!(control_messages("sign_out", &Value::Null).is_err()); + assert!(control_messages("play", &json!({"uri":"https://example.com"})).is_err()); + assert!(control_messages( + "add_to_queue", + &json!({"uri":"spotify:playlist:37i9dQZF1EYkqdzj48dyYq"}) + ) + .is_err()); + assert!(control_messages("volume", &json!({"volume": 101})).is_err()); + assert!(control_messages("pause", &Value::Null).is_ok()); + } + + #[test] + fn repeat_mode_emits_two_ordered_commands() { + let v = control_messages("repeat", &json!({"mode":"track"})).unwrap(); + assert_eq!(v[0]["command"], "set_repeat_context"); + assert_eq!(v[1]["command"], "set_repeat_track"); + assert_eq!(v[1]["enabled"], true); + } +} diff --git a/crates/core/src/state.rs b/crates/core/src/state.rs index 3bcdf8b8..3a68ff12 100644 --- a/crates/core/src/state.rs +++ b/crates/core/src/state.rs @@ -413,6 +413,58 @@ impl QueueState { } } + +// Persist the complete effect chain, including the enabled flag and all 20 bands. +// This is a private daemon setting, not a UI preference controlled by a webview. +const AUDIO_FX_SETTING_KEY: &str = "audio_fx"; + +fn validate_audio_fx(fx: &AudioFx) -> Result<(), String> { + if !fx.speed.is_finite() || !(0.25..=2.0).contains(&fx.speed) { + return Err("Tempo must be between 0.25× and 2.00×.".into()); + } + if !(-12..=12).contains(&fx.semitones) { + return Err("Pitch must be between -12 and +12 semitones.".into()); + } + if !fx.reverb.is_finite() || !(0.0..=1.0).contains(&fx.reverb) { + return Err("Reverb must be between 0 and 1.".into()); + } + if !fx.bass_db.is_finite() || !(-6.0..=12.0).contains(&fx.bass_db) { + return Err("Bass must be between -6 and +12 dB.".into()); + } + if !fx.width.is_finite() || !(0.0..=1.0).contains(&fx.width) { + return Err("Width must be between 0 and 1.".into()); + } + for (index, band) in fx.eq.bands.iter().enumerate() { + if !band.frequency.is_finite() || !(20.0..=20000.0).contains(&band.frequency) + || !band.q.is_finite() || !(0.3..=10.0).contains(&band.q) + || !band.gain.is_finite() || !(-12.0..=12.0).contains(&band.gain) + { + return Err(format!("Invalid equalizer values in band {}.", index + 1)); + } + } + Ok(()) +} + +// An invalid stored value must not prevent the daemon from starting or leave a +// partially applied filter chain behind. The UI receives the validated mirror. +fn read_saved_audio_fx(db: &Db) -> AudioFx { + let Some(raw) = db.get_setting(AUDIO_FX_SETTING_KEY) else { + return AudioFx::default(); + }; + let fx = match serde_json::from_str::(&raw) { + Ok(fx) => fx, + Err(error) => { + tracing::warn!(%error, "Ignoring malformed saved audio effects"); + return AudioFx::default(); + } + }; + if let Err(reason) = validate_audio_fx(&fx) { + tracing::warn!(%reason, "Ignoring out-of-range saved audio effects"); + return AudioFx::default(); + } + fx +} + impl AppState { #[allow(clippy::too_many_arguments)] pub fn new( @@ -478,6 +530,18 @@ impl AppState { Ok(json) => tidal_db.set_setting(TIDAL_AUTH_KEY, &json), Err(e) => tracing::error!(error = %e, "tidal: cannot serialize rotated auth"), }); + let saved_audio_fx = read_saved_audio_fx(&db); + let live_audio_fx = if saved_audio_fx == AudioFx::default() { + saved_audio_fx + } else { + match player.set_fx(&saved_audio_fx) { + Ok(()) => saved_audio_fx, + Err(error) => { + tracing::warn!(%error, "Could not restore saved audio effects"); + AudioFx::default() + } + } + }; AppState { it, clients, @@ -508,7 +572,7 @@ impl AppState { last_persisted_fingerprint: AtomicU64::new(0), stop_after_current: AtomicBool::new(false), low_resource_mode: AtomicBool::new(low_resource_mode), - audio_fx: parking_lot::RwLock::new(AudioFx::default()), + audio_fx: parking_lot::RwLock::new(live_audio_fx), spotify_stream: parking_lot::Mutex::new(None), } } @@ -4316,31 +4380,12 @@ impl AppState { /// that must not trust a client), applied to the player as one `af` chain, then — only after a /// successful apply — stored and echoed to every client as `audio-fx` so all windows agree. pub fn set_audio_fx(&self, fx: AudioFx) -> Result<(), String> { - if !fx.speed.is_finite() || !(0.25..=2.0).contains(&fx.speed) { - return Err("Tempo must be between 0.25× and 2.00×.".into()); - } - if !(-12..=12).contains(&fx.semitones) { - return Err("Pitch must be between -12 and +12 semitones.".into()); - } - if !fx.reverb.is_finite() || !(0.0..=1.0).contains(&fx.reverb) { - return Err("Reverb must be between 0 and 1.".into()); - } - if !fx.bass_db.is_finite() || !(-6.0..=12.0).contains(&fx.bass_db) { - return Err("Bass must be between -6 and +12 dB.".into()); - } - if !fx.width.is_finite() || !(0.0..=1.0).contains(&fx.width) { - return Err("Width must be between 0 and 1.".into()); - } - for (index, band) in fx.eq.bands.iter().enumerate() { - if !band.frequency.is_finite() || !(20.0..=20000.0).contains(&band.frequency) - || !band.q.is_finite() || !(0.3..=10.0).contains(&band.q) - || !band.gain.is_finite() || !(-12.0..=12.0).contains(&band.gain) - { - return Err(format!("Invalid equalizer values in band {}.", index + 1)); - } - } + validate_audio_fx(&fx)?; self.player.set_fx(&fx).map_err(|e| e.to_string())?; *self.audio_fx.write() = fx; + if let Ok(serialized) = serde_json::to_string(&fx) { + self.db.set_setting(AUDIO_FX_SETTING_KEY, &serialized); + } self.emit("audio-fx", fx); Ok(()) } @@ -4357,6 +4402,26 @@ mod tests { }; use crate::db::Db; + #[test] + fn audio_fx_persistence_round_trip_and_invalid_fallback() { + use super::{read_saved_audio_fx, AUDIO_FX_SETTING_KEY}; + let db = Db::open(std::path::Path::new(":memory:")).unwrap(); + let mut fx = player::AudioFx::default(); + fx.eq.enabled = true; + fx.eq.bands[10].gain = 8.5; + fx.eq.bands[10].q = 1.6; + fx.eq.bands[10].frequency = 1150.0; + db.set_setting(AUDIO_FX_SETTING_KEY, &serde_json::to_string(&fx).unwrap()); + assert_eq!(read_saved_audio_fx(&db), fx); + + db.set_setting(AUDIO_FX_SETTING_KEY, "not json"); + assert_eq!(read_saved_audio_fx(&db), player::AudioFx::default()); + + fx.eq.bands[10].gain = 30.0; + db.set_setting(AUDIO_FX_SETTING_KEY, &serde_json::to_string(&fx).unwrap()); + assert_eq!(read_saved_audio_fx(&db), player::AudioFx::default()); + } + #[test] fn personal_round_trips_and_rejects_bad_blobs() { let db = Db::open(std::path::Path::new(":memory:")).unwrap(); diff --git a/crates/ryotunesd/src/main.rs b/crates/ryotunesd/src/main.rs index 7c5ed6d5..5542c707 100644 --- a/crates/ryotunesd/src/main.rs +++ b/crates/ryotunesd/src/main.rs @@ -61,6 +61,25 @@ fn main() -> anyhow::Result<()> { let (state, events, media_rx, lt_rx) = app::build(paths, sink.clone(), js, login, &tokio::runtime::Handle::current())?; app::spawn_pumps(state.clone(), events, media_rx, lt_rx, sink.clone(), lifecycle.clone()); + + // Optional Soloist event observer. Deliberately namespaced: no MPV state, + // Spotify sign-in state, QML player or existing provider routing is changed. + // Enable only for a manual integration test: RYOTUNES_SOLOIST_EVENTS=1. + if std::env::var("RYOTUNES_SOLOIST_EVENTS").ok().as_deref() == Some("1") { + let observer_state = state.clone(); + tokio::spawn(async move { + let bridge = ryotunes_core::soloist::SoloistBridge::discover(); + loop { + if let Err(error) = bridge + .watch_stream(|event| observer_state.emit("soloist-event", event)) + .await + { + tracing::debug!(%error, "Soloist event stream ended; reconnecting"); + } + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + } + }); + } tray::spawn(state.clone(), quit_tx.clone(), sink.clone()); // Restore a cached Spotify session in the background so browsing and playback are ready diff --git a/crates/ryotunesd/src/methods.rs b/crates/ryotunesd/src/methods.rs index e42726ba..868eb63f 100644 --- a/crates/ryotunesd/src/methods.rs +++ b/crates/ryotunesd/src/methods.rs @@ -156,6 +156,11 @@ fn spotify_err(e: impl ToString) -> ErrorBody { ErrorBody { code: "spotify".into(), message: e.to_string() } } +/// Soloist connection errors must not affect the existing Spotify/librespot auth state. +fn soloist_err(e: impl ToString) -> ErrorBody { + ErrorBody { code: "soloist".into(), message: e.to_string() } +} + /// The error a by-prefix Spotify command returns when no session is signed in. fn spotify_signed_out() -> ErrorBody { ErrorBody { code: "spotify_signed_out".into(), message: "Sign in to Spotify".into() } @@ -428,6 +433,29 @@ impl Dispatch for Methods { } "get_provider" => ok(json!({ "provider": st.spotify.selected().as_str() })), "spotify_status" => ok(spotify_status_json(st).await), + // --- Soloist trial bridge (opt-in; does not touch the MPV player) ---------------- + "soloist_status" => { + let bridge = ryotunes_core::soloist::SoloistBridge::discover(); + match bridge.status().await { + Ok(status) => ok(json!({ "available": true, "state": status })), + Err(error) => ok(json!({ "available": false, "reason": error.to_string() })), + } + } + "soloist_now" => { + let bridge = ryotunes_core::soloist::SoloistBridge::discover(); + ok(bridge.now().await.map_err(soloist_err)?) + } + "soloist_queue" => { + let limit = opt_arg::(¶ms, "limit").unwrap_or(10); + let bridge = ryotunes_core::soloist::SoloistBridge::discover(); + ok(bridge.queue(limit).await.map_err(soloist_err)?) + } + "soloist_control" => { + let action = arg::(¶ms, "action")?; + let args = params.get("args").cloned().unwrap_or_else(|| json!({})); + let bridge = ryotunes_core::soloist::SoloistBridge::discover(); + ok(bridge.control(&action, &args).await.map_err(soloist_err)?) + } "soundcloud_status" => ok(soundcloud_status_json(st)), "tidal_status" => ok(tidal_status_json(st)), "tidal_sign_out" => { From d88f0a06b77189f4d243ddf9daa1576b261e432e Mon Sep 17 00:00:00 2001 From: Aether Date: Wed, 28 Oct 2026 22:24:00 +0000 Subject: [PATCH 03/20] feat(nix): add standalone Ryotunes flake packaging --- INSTALL.md | 68 +++++++++++++++++++ flake.lock | 175 ++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 55 +++++++++++++++ nix/package.nix | 153 ++++++++++++++++++++++++++++++++++++++++++ nix/wrapper.nix | 59 ++++++++++++++++ 5 files changed, 510 insertions(+) create mode 100644 INSTALL.md create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/package.nix create mode 100644 nix/wrapper.nix diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 00000000..5b297658 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,68 @@ +# RyotunesNix flake packaging + +This is a source flake for the **RyotunesNix repository**, not a patch for upstream `ryotunes`. The package includes the Rust binaries, Quickshell UI, Ryoku QML modules, and the Audio Lab's Python/FFmpeg/PipeWire monitor helpers. + +The Spotify Soloist bridge is part of the source, but **Soloist is still a separate runtime**. The flake does not download a time-limited Soloist binary, collect credentials, or change the live playback daemon. + +## Apply to RyotunesNix only + +First merge the Spotify, Audio Lab and UI changes into `Ryoku-dev/ryotunes` without these packaging files. Keep the NixOS packaging in its own branch of `Ryoku-dev/RyotunesNix`. + +After merging and synchronizing the application source into the fork: + +```bash +cd ~/Projects/ryoku-sprint/ryotunesnix +git switch -c feat/nix-flake +unzip -o ~/Downloads/ryotunesnix-flake.zip -d . +git add flake.nix nix/package.nix nix/wrapper.nix INSTALL.md +nix-instantiate --parse flake.nix >/dev/null +nix flake lock +nix eval --raw .#packages.x86_64-linux.ryotunes-unwrapped.version +git status --short +``` + +`unzip` will copy only these four files. Run it on a fresh RyotunesNix-only branch, never on the upstream PR branch. + +The flake's `cargoLock.allowBuiltinFetchGit` uses the pinned Git revisions in Cargo.lock rather than a placeholder cargoHash. It can fetch these Git sources during Nix evaluation. The pnpm lockfile uses the same hash as the existing Ryoku-on-NixOS packaging, because the `ui/pnpm-lock.yaml` content has not changed. All inputs should be recorded by `flake.lock` and committed. + +## Test and deploy + +The source still needs a real Nix build and desktop playback check on the target NixOS system. Avoid a full second build until disk space permits and the source merge is complete. + +```bash +nix build .#ryotunes -L +``` + +Only after the package builds and the active Soloist process remains functional should Ryoku-on-NixOS be changed. + +## Ryoku-on-NixOS integration + +Use the fork's `ryotunes-unwrapped` output with **Ryoku's existing wrapper and socket-activated daemon**. This avoids two service definitions, two installed app copies, and duplicate QML packages. + +In Ryoku-on-NixOS `flake.nix`, replace `ryotunesSrc` with: + +```nix +ryotunesNix = { + url = "github:Ryoku-dev/RyotunesNix"; + inputs.nixpkgs.follows = "ryokuPackagesNixpkgs"; + inputs.ryoku.follows = "ryokuUpstream"; +}; +``` + +Pass `ryotunesNix` through the outputs arguments into `./nix/packages`. In `nix/packages/default.nix`, replace the old `ryotunesUnwrapped` import with: + +```nix +ryotunesUnwrapped = + ryotunesNix.packages.${pkgs.stdenv.hostPlatform.system}.ryotunes-unwrapped; +``` + +Keep `ryotunes = import ./ryoku-ryotunes.nix { ... };` intact, so Ryoku uses its own `ryokuQml` package. Keep `ryotunesd.socket` and `ryotunesd.service` and their existing paths. They will resolve through the replacement `ryoku-ryotunes` wrapper. Remove the obsolete pinned source input and old derivation only after validation. + +The system currently may also have a **temporary user-level systemd override** pointing to the Cargo build. Inspect it before removing it, and don't stop or replace the working Soloist process until the new Nix-owned executable has passed a playback check. + +## Current limitations + +* The flake has been prepared from repository sources and existing NixOS packaging, but not built in this environment. +* Soloist remains externally managed and has a limited-lifetime binary. +* The QML audio scope uses `pactl` and FFmpeg with your PipeWire default output monitor, not arbitrary audio capture. +* This packaging does not claim to route Soloist audio through the libmpv equalizer. diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..107ec8d3 --- /dev/null +++ b/flake.lock @@ -0,0 +1,175 @@ +{ + "nodes": { + "bibataMaterialSrc": { + "flake": false, + "locked": { + "lastModified": 1738046050, + "narHash": "sha256-p36pHyoVOcDPm/tbk8YKsL+ItTaVKrGTfQ8zp022mGA=", + "owner": "rtgiskard", + "repo": "bibata_cursor", + "rev": "f4ccfe8abb63fddc7b3ce51a866fd8378395cb3d", + "type": "github" + }, + "original": { + "owner": "rtgiskard", + "repo": "bibata_cursor", + "rev": "f4ccfe8abb63fddc7b3ce51a866fd8378395cb3d", + "type": "github" + } + }, + "hyprglassSrc": { + "flake": false, + "locked": { + "lastModified": 1784579282, + "narHash": "sha256-x/584kY+XXlU/OWKtZAFo89VtowjLXs1DiP9PC0o0Os=", + "owner": "hyprnux", + "repo": "hyprglass", + "rev": "5bc835dcc909cef6980291688143048cf16942b5", + "type": "github" + }, + "original": { + "owner": "hyprnux", + "ref": "v0.7.0", + "repo": "hyprglass", + "type": "github" + } + }, + "imgbordersSrc": { + "flake": false, + "locked": { + "lastModified": 1786876215, + "narHash": "sha256-O+896T2qrisxiWTotB5HlzKw8XEJqPDTgSUHAAVUD18=", + "ref": "master", + "rev": "08be22236144d3c91607bcfa955ed0d457f4f50b", + "revCount": 76, + "type": "git", + "url": "https://codeberg.org/zacoons/imgborders.git" + }, + "original": { + "ref": "master", + "type": "git", + "url": "https://codeberg.org/zacoons/imgborders.git" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1787498568, + "narHash": "sha256-9i/VTdusq/+NM/tz+J1Re+ojkMB8MBf0QshnYfzHz30=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "56c02bc00adcf003215cc4bd996d6efaf4cff188", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "56c02bc00adcf003215cc4bd996d6efaf4cff188", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1787736819, + "narHash": "sha256-cV5xEJJK3BvhU8rEd4mC9UsmDi5qscv/kzGPhBRC5WA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9fbb54b33e91ee4ca368e35a78e0613c720600b3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "ryoku": "ryoku" + } + }, + "ryoku": { + "inputs": { + "bibataMaterialSrc": "bibataMaterialSrc", + "hyprglassSrc": "hyprglassSrc", + "imgbordersSrc": "imgbordersSrc", + "nixpkgs": [ + "nixpkgs" + ], + "ryomanagerSrc": "ryomanagerSrc", + "ryotunesSrc": "ryotunesSrc", + "skwdWall": "skwdWall" + }, + "locked": { + "lastModified": 1793223354, + "narHash": "sha256-qUP3qTAGSfbSfHyFcFrGSs1NhtuA8Stuol+wdD4MCY4=", + "owner": "Ryoku-dev", + "repo": "ryoku", + "rev": "1d3feb1b8a6db9ee9ffdc15d0cb626828a5eed4a", + "type": "github" + }, + "original": { + "owner": "Ryoku-dev", + "ref": "unstable-dev", + "repo": "ryoku", + "type": "github" + } + }, + "ryomanagerSrc": { + "flake": false, + "locked": { + "lastModified": 1792184455, + "narHash": "sha256-69+vyEh4V/YqqAGNScnjA0I92qdSyxHeQJqcNIbgMMg=", + "owner": "aethctl", + "repo": "ryomanager", + "rev": "459c6714e31276f73025369e722cf4ab48eccbc3", + "type": "github" + }, + "original": { + "owner": "aethctl", + "repo": "ryomanager", + "rev": "459c6714e31276f73025369e722cf4ab48eccbc3", + "type": "github" + } + }, + "ryotunesSrc": { + "flake": false, + "locked": { + "lastModified": 1791950985, + "narHash": "sha256-pFguZ3ZSQ7DLI9wDukjhUklt54jUOc6mEn5XBVpsolY=", + "owner": "Ryoku-dev", + "repo": "ryotunes", + "rev": "ade86b744651b908f3946b6769853e5084a59ce1", + "type": "github" + }, + "original": { + "owner": "Ryoku-dev", + "repo": "ryotunes", + "rev": "ade86b744651b908f3946b6769853e5084a59ce1", + "type": "github" + } + }, + "skwdWall": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1792102654, + "narHash": "sha256-dNISNxw7+ehLeaA+XBJzUjV24ik6Oibce6C0RxzzZns=", + "owner": "liixini", + "repo": "skwd-wall", + "rev": "d9142ad9e23bf33390e3fea38a5ce45a6fc286da", + "type": "github" + }, + "original": { + "owner": "liixini", + "ref": "nix", + "repo": "skwd-wall", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..ceb763da --- /dev/null +++ b/flake.nix @@ -0,0 +1,55 @@ +{ + description = "RyotunesNix: Ryoku-native music player for NixOS"; + + inputs = { + nixpkgs.url = + "github:NixOS/nixpkgs/56c02bc00adcf003215cc4bd996d6efaf4cff188"; + + ryoku = { + url = "github:Ryoku-dev/ryoku/unstable-dev"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, ryoku, ... }: + let + system = "x86_64-linux"; + + quickshellScreencopyFix = final: prev: { + quickshell = prev.quickshell.overrideAttrs (old: { + patches = (old.patches or [ ]) ++ [ + (prev.fetchurl { + url = "https://github.com/quickshell-mirror/quickshell/commit/851ab0eac6bdfa9201a1a50e7bc3a6d29af25949.patch"; + hash = "sha256-g+8NQeFyGK9v76cPM9ogkMSfLH0SGPOyREFkTf6MJ7I="; + }) + ]; + }); + }; + + pkgs = import nixpkgs { + inherit system; + overlays = [ quickshellScreencopyFix ]; + }; + + ryotunesUnwrapped = pkgs.callPackage ./nix/package.nix { + src = self.outPath; + }; + + ryotunes = pkgs.callPackage ./nix/wrapper.nix { + inherit ryotunesUnwrapped; + ryokuQml = ryoku.packages.${system}.ryoku-qml; + }; + in + { + packages.${system} = { + inherit ryotunes; + ryotunes-unwrapped = ryotunesUnwrapped; + default = ryotunes; + }; + + apps.${system}.default = { + type = "app"; + program = "${ryotunes}/bin/ryotunes"; + }; + }; +} diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 00000000..be8197d4 --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,153 @@ +{ + lib, + rustPlatform, + fetchPnpmDeps, + pkg-config, + nodejs, + pnpm, + pnpmConfigHook, + wrapGAppsHook3, + glib, + gtk3, + gdk-pixbuf, + cairo, + pango, + webkitgtk_4_1, + libsoup_3, + mpv, + libappindicator-gtk3, + libayatana-appindicator, + openssl, + librsvg, + src, +}: + +rustPlatform.buildRustPackage rec { + pname = "ryotunesnix-unwrapped"; + version = "1.2.3"; + + inherit src; + + cargoLock = { + lockFile = ../Cargo.lock; + allowBuiltinFetchGit = true; + }; + + pnpmDeps = fetchPnpmDeps { + pname = "ryotunes-ui"; + inherit version; + src = src + "/ui"; + fetcherVersion = 4; + hash = "sha256-L0HRYhFO5A0qqwdiYzN+h28kV7vAqB3up+Zu4ShYnBo="; + }; + pnpmRoot = "ui"; + + nativeBuildInputs = [ + pkg-config + nodejs + pnpm + pnpmConfigHook + wrapGAppsHook3 + rustPlatform.bindgenHook + ]; + + buildInputs = [ + glib + gtk3 + gdk-pixbuf + cairo + pango + webkitgtk_4_1 + libsoup_3 + mpv + libappindicator-gtk3 + libayatana-appindicator + openssl + librsvg + ]; + + preBuild = '' + export HOME="$TMPDIR" + (cd ui && pnpm build) + test -f ui/build/index.html + ''; + + cargoBuildFlags = [ + "--package" "ryotunes" + "--package" "sync-server" + "--package" "ryotunesd" + "--package" "ryotunes-cli" + ]; + + doCheck = false; + + installPhase = '' + runHook preInstall + + install_bin() { + local name="$1" + local binary + binary="$(find target -type f -path "*/release/$name" -perm -0100 -print -quit)" + if [ -z "$binary" ]; then + printf 'Ryotunes binary not built: %s\n' "$name" >&2 + exit 1 + fi + install -Dm755 "$binary" "$out/bin/$name" + } + + install_bin ryotunes + install_bin ryotunes-sync + install_bin ryotunesd + install_bin ryotunes-cli + + mkdir -p \ + "$out/share/ryotunes/client" \ + "$out/share/ryotunes/skins" \ + "$out/share/ryotunes/matugen" \ + "$out/share/applications" \ + "$out/share/metainfo" \ + "$out/share/licenses/ryotunes" \ + "$out/share/doc/ryotunes" + + cp -r client/. "$out/share/ryotunes/client/" + rm -rf "$out/share/ryotunes/client/tests" + cp -r skins/. "$out/share/ryotunes/skins/" + + find "$out/share/ryotunes/client" "$out/share/ryotunes/skins" \ + -type d -exec chmod 755 {} + + find "$out/share/ryotunes/client" "$out/share/ryotunes/skins" \ + -type f -exec chmod 644 {} + + + install -Dm644 matugen/ryotunes.json \ + "$out/share/ryotunes/matugen/ryotunes.json" + install -Dm644 packaging/linux/ryotunes.desktop \ + "$out/share/applications/ryotunes.desktop" + install -Dm644 packaging/linux/dev.ryoku.ryotunes.metainfo.xml \ + "$out/share/metainfo/dev.ryoku.ryotunes.metainfo.xml" + + for size in 32 64 128; do + install -Dm644 "src-tauri/icons/''${size}x''${size}.png" \ + "$out/share/icons/hicolor/''${size}x''${size}/apps/ryotunes.png" + done + install -Dm644 src-tauri/icons/128x128@2x.png \ + "$out/share/icons/hicolor/256x256/apps/ryotunes.png" + install -Dm644 src-tauri/icons/icon.png \ + "$out/share/icons/hicolor/512x512/apps/ryotunes.png" + + install -Dm755 scripts/diagnostics.sh \ + "$out/bin/ryotunes-diagnostics" + install -Dm644 LICENSE "$out/share/licenses/ryotunes/LICENSE" + install -Dm644 README.md "$out/share/doc/ryotunes/README.md" + install -Dm644 UPSTREAM.md "$out/share/doc/ryotunes/UPSTREAM.md" + + runHook postInstall + ''; + + meta = { + description = "Ryotunes native playback daemon, CLI and QML client"; + homepage = "https://github.com/Ryoku-dev/RyotunesNix"; + license = lib.licenses.gpl3Plus; + platforms = [ "x86_64-linux" ]; + mainProgram = "ryotunes"; + }; +} diff --git a/nix/wrapper.nix b/nix/wrapper.nix new file mode 100644 index 00000000..8d67728e --- /dev/null +++ b/nix/wrapper.nix @@ -0,0 +1,59 @@ +{ + lib, + symlinkJoin, + makeWrapper, + quickshell, + qt6, + kdePackages, + python3, + ffmpeg, + pulseaudio, + xdg-utils, + ryotunesUnwrapped, + ryokuQml, +}: + +let + qmlPackages = [ + ryokuQml + qt6.qtdeclarative + qt6.qtmultimedia + qt6.qtwayland + qt6.qt5compat + qt6.qtsvg + qt6.qtimageformats + kdePackages.kirigami.unwrapped + ]; + + qmlImports = lib.makeSearchPath "lib/qt-6/qml" qmlPackages; + qtPlugins = lib.makeSearchPath "lib/qt-6/plugins" qmlPackages; + runtimePath = lib.makeBinPath [ python3 ffmpeg pulseaudio xdg-utils ]; +in +symlinkJoin { + name = "ryotunesnix-${ryotunesUnwrapped.version}"; + paths = [ ryotunesUnwrapped ]; + nativeBuildInputs = [ makeWrapper ]; + preferLocalBuild = false; + allowSubstitutes = true; + + postBuild = '' + makeWrapper ${quickshell}/bin/qs "$out/bin/ryotunes-qml" \ + --add-flags "-p ${ryotunesUnwrapped}/share/ryotunes/client" \ + --prefix QML_IMPORT_PATH : "${qmlImports}" \ + --prefix QML2_IMPORT_PATH : "${qmlImports}" \ + --prefix QT_PLUGIN_PATH : "${qtPlugins}" \ + --prefix PATH : "${runtimePath}" + + rm "$out/bin/ryotunes" + makeWrapper ${ryotunesUnwrapped}/bin/ryotunes "$out/bin/ryotunes" \ + --prefix PATH : "$out/bin:${runtimePath}" + + rm "$out/bin/ryotunesd" + makeWrapper ${ryotunesUnwrapped}/bin/ryotunesd "$out/bin/ryotunesd" \ + --prefix PATH : "$out/bin:${runtimePath}" + ''; + + meta = ryotunesUnwrapped.meta // { + description = "Ryotunes with native Ryoku QML integration"; + }; +} From bdf47373226b11b2696f81bfa328db5231b348df Mon Sep 17 00:00:00 2001 From: Aether Date: Wed, 28 Oct 2026 23:52:59 +0000 Subject: [PATCH 04/20] fix(nix): ship native Ryotunes and validate with GitHub Actions --- .github/workflows/native-nix.yml | 38 ++++ .github/workflows/release.yml | 298 ------------------------------- nix/package.nix | 22 +-- nix/wrapper.nix | 7 +- 4 files changed, 42 insertions(+), 323 deletions(-) create mode 100644 .github/workflows/native-nix.yml delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/native-nix.yml b/.github/workflows/native-nix.yml new file mode 100644 index 00000000..af4c35b5 --- /dev/null +++ b/.github/workflows/native-nix.yml @@ -0,0 +1,38 @@ +name: RyotunesNix Native Build + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +jobs: + native-build: + runs-on: ubuntu-24.04 + timeout-minutes: 90 + + steps: + - uses: actions/checkout@v4 + + - uses: cachix/install-nix-action@v31 + + - name: Validate flake + run: nix flake show + + - name: Build native Ryotunes + run: nix build .#ryotunes --no-link -L + + - name: Verify native launcher + run: | + set -euo pipefail + + out="$(nix path-info .#ryotunes)" + + test -x "$out/bin/ryotunes" + test -x "$out/bin/ryotunesd" + test -x "$out/bin/ryotunes-cli" + test -f "$out/share/ryotunes/client/shell.qml" + + grep -Fq '/bin/qs' "$out/bin/ryotunes" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index f190e09b..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,298 +0,0 @@ -name: Release - -# Ryotunes releases itself on every push to the default branch. One pipeline: -# 1. release: work out the next v1 version from the published tags (the -# version ledger, scripts/next-version.sh), sync every manifest to it, -# verify the tree builds and the release invariants hold, then tag that -# exact pushed source. A later source push never invalidates an earlier -# run; manifests are fast-forwarded back to the default branch only when -# it has not moved in the meantime. -# 2. package: an unprivileged makepkg in an Arch container builds the reserved -# tag's exact source into ryotunes--1-x86_64.pkg.tar.zst (+ .sha256). -# 3. publish: a normal (latest, non-draft) GitHub release carrying the package, -# its sha256, a reproducible source tarball + sha256, and notes built from -# the changelog plus install instructions. Ryoku's update/doctor read this -# release directly; there is no downstream repository_dispatch. -# -# The version bump is pushed with GITHUB_TOKEN, and a GITHUB_TOKEN push never -# retriggers a workflow, so there is no bot loop and nothing relies on a -# token-pushed tag firing a second workflow (the historic tag-trigger trap). -# A failed package/publish leaves a reserved tag. Re-run the failed workflow or -# dispatch that tag to finish publishing without minting another version. -on: - push: - branches: ['**'] - workflow_dispatch: - inputs: - tag: - description: Existing tag to rebuild and re-publish, e.g. v1.0.2 - required: true - -permissions: - contents: write - -# Queue pushes instead of replacing the one pending run (GitHub's default). -# queue:max retains up to GitHub's 100-run limit; never cancel an active release. -concurrency: - group: release - cancel-in-progress: false - queue: max - -jobs: - release: - name: Verify and reserve the version - if: github.event_name == 'workflow_dispatch' || github.ref == format('refs/heads/{0}', github.event.repository.default_branch) - runs-on: ubuntu-24.04 - outputs: - action: ${{ steps.plan.outputs.action }} - version: ${{ steps.plan.outputs.version }} - tag: ${{ steps.plan.outputs.tag }} - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ inputs.tag || github.sha }} - fetch-depth: 0 - - # Decide what this run does: - # release - a fresh push: compute + reserve the next version. - # republish - an existing tag: rebuild and re-upload (manual dispatch, or - # automatic resume when a reserved tag has no complete release). - # skip - this exact source is already fully released. - - name: Plan - id: plan - env: - GH_TOKEN: ${{ github.token }} - EVENT_NAME: ${{ github.event_name }} - INPUT_TAG: ${{ inputs.tag }} - run: | - set -euo pipefail - if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then - tag="$INPUT_TAG" - [[ "$tag" =~ ^v1\.(0|[1-9][0-9]*)\.[0-9]$ ]] \ - || { echo "::error::expected a canonical v1 release tag"; exit 1; } - git rev-parse -q --verify "refs/tags/$tag" >/dev/null \ - || { echo "::error::tag $tag does not exist"; exit 1; } - ver="${tag#v}" - cv="$(sed -n 's/^version = "\([0-9.]*\)"/\1/p' Cargo.toml | head -1)" - [[ "$cv" == "$ver" ]] \ - || { echo "::error::tag $tag tree is version $cv, not $ver"; exit 1; } - { - echo "action=republish" - echo "version=$ver" - echo "tag=$tag" - } >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Idempotency by source SHA: was this pushed commit already reserved as a - # v1 tag (the tag sits on it, or on the release commit made on top of it)? - src="$(git rev-parse HEAD)" - reserved="" - for t in $(git tag -l 'v1.*'); do - [[ "$t" =~ ^v1\.(0|[1-9][0-9]*)\.[0-9]$ ]] || continue - tc="$(git rev-parse "$t^{commit}")" - tp="$(git rev-parse "$t^{commit}^1" 2>/dev/null || echo none)" - if [[ "$tc" == "$src" || "$tp" == "$src" ]]; then reserved="$t"; break; fi - done - if [[ -n "$reserved" ]]; then - ver="${reserved#v}" - complete="$(gh release view "$reserved" --json isDraft,isPrerelease,assets 2>/dev/null || true)" - if RELEASE_JSON="$complete" VERSION="$ver" python3 - <<'PY' - import json, os, sys - try: - r = json.loads(os.environ["RELEASE_JSON"]) - v = os.environ["VERSION"] - package, source = f"ryotunes-{v}-1-x86_64.pkg.tar.zst", f"ryotunes-{v}.tar.gz" - required = {package, package + ".sha256", source, source + ".sha256"} - complete = not r["isDraft"] and not r["isPrerelease"] and required <= {a["name"] for a in r["assets"]} - except (ValueError, KeyError): - complete = False - sys.exit(0 if complete else 1) - PY - then - echo "action=skip" >> "$GITHUB_OUTPUT" - echo "$reserved is already published; nothing to do" - else - echo "action=republish" >> "$GITHUB_OUTPUT" - echo "$reserved is reserved but its release is incomplete; resuming" - fi - echo "version=$ver" >> "$GITHUB_OUTPUT" - echo "tag=$reserved" >> "$GITHUB_OUTPUT" - exit 0 - fi - - ver="$(scripts/next-version.sh)" - { - echo "action=release" - echo "version=$ver" - echo "tag=v$ver" - } >> "$GITHUB_OUTPUT" - echo "reserving v$ver from $src" - - - name: Check out the tag to re-publish - if: steps.plan.outputs.action == 'republish' - run: git checkout --force --detach "${{ steps.plan.outputs.tag }}" - - - name: Sync every manifest to the new version - if: steps.plan.outputs.action == 'release' - run: scripts/sync-version.sh "${{ steps.plan.outputs.version }}" --release - - - name: Install Linux dependencies - if: steps.plan.outputs.action != 'skip' - run: | - sudo apt-get update - sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev libmpv-dev - - - uses: dtolnay/rust-toolchain@stable - if: steps.plan.outputs.action != 'skip' - with: - components: rustfmt - - uses: Swatinem/rust-cache@v2 - if: steps.plan.outputs.action != 'skip' - - uses: pnpm/action-setup@v4 - if: steps.plan.outputs.action != 'skip' - with: - version: 11 - - uses: actions/setup-node@v4 - if: steps.plan.outputs.action != 'skip' - with: - node-version: 22 - cache: pnpm - cache-dependency-path: ui/pnpm-lock.yaml - - - name: Release invariants - if: steps.plan.outputs.action != 'skip' - run: python3 scripts/check-release-invariants.py - - - name: Build the web UI - if: steps.plan.outputs.action != 'skip' - working-directory: ui - run: | - pnpm install --frozen-lockfile - pnpm build - - - name: Build every binary - if: steps.plan.outputs.action != 'skip' - run: cargo build --release --locked --package ryotunes --package ryotunesd --package ryotunes-cli - - - name: Skins validate - if: steps.plan.outputs.action != 'skip' - run: | - for d in skins/*/; do target/release/ryotunes-cli skin check "$d"; done - - - name: Unit tests - if: steps.plan.outputs.action != 'skip' - run: cargo test --workspace --locked --exclude ryotunes - - # Reserve the exact pushed source, even if a later push has moved the - # default branch while this build ran. Never force-push a user's history. - - name: Reserve the version - if: steps.plan.outputs.action == 'release' - env: - TAG: ${{ steps.plan.outputs.tag }} - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - SOURCE_SHA: ${{ github.sha }} - run: | - set -euo pipefail - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add Cargo.toml Cargo.lock src-tauri/tauri.conf.json ui/package.json client/version packaging/arch/PKGBUILD packaging/linux/dev.ryoku.ryotunes.metainfo.xml CHANGELOG.md - git commit --allow-empty -m "release: $TAG" - git tag -a "$TAG" -m "Ryotunes $TAG" -m "Source-Commit: $SOURCE_SHA" - git push origin "refs/tags/$TAG" - remote_head="$(git ls-remote origin "refs/heads/$DEFAULT_BRANCH" | cut -f1)" - if [[ "$remote_head" == "$SOURCE_SHA" ]]; then - git push origin "HEAD:refs/heads/$DEFAULT_BRANCH" \ - || echo "::notice::Branch moved or is protected; the release tag is complete, branch versions will sync on a later push." - fi - - package: - name: Build the Arch package - needs: release - if: needs.release.outputs.action != 'skip' - runs-on: ubuntu-24.04 - container: - image: archlinux:base-devel - steps: - # actions/checkout needs git present in the container before it can run. - - name: Install git for checkout - run: pacman -Syu --noconfirm --needed git - - - uses: actions/checkout@v4 - with: - ref: ${{ needs.release.outputs.tag }} - - # The shared script a maintainer runs locally too - # (docker run ... archlinux:latest scripts/build-arch-package.sh). It runs as - # root, sets up an unprivileged builder, re-execs itself, builds the reserved - # tag's exact source, and writes the epochless contract asset - # ryotunes--1-x86_64.pkg.tar.zst (+ .sha256) into dist/. - - name: Build the package - run: scripts/build-arch-package.sh "$GITHUB_WORKSPACE/dist" - - - name: Upload the package - uses: actions/upload-artifact@v4 - with: - name: arch-package - path: dist/ryotunes-*-x86_64.pkg.tar.zst* - if-no-files-found: error - - publish: - name: Publish the GitHub release - needs: [release, package] - if: needs.release.outputs.action != 'skip' - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ needs.release.outputs.tag }} - fetch-depth: 0 - - - name: Fetch the Arch package - uses: actions/download-artifact@v4 - with: - name: arch-package - path: dist - - - name: Source tarball - env: - TAG: ${{ needs.release.outputs.tag }} - VERSION: ${{ needs.release.outputs.version }} - run: | - set -euo pipefail - mkdir -p dist - name="ryotunes-$VERSION" - # git archive is reproducible for a tag: same tree, same bytes. - git archive --format=tar --prefix="$name/" "$TAG" | gzip -n > "dist/$name.tar.gz" - ( cd dist && sha256sum "$name.tar.gz" > "$name.tar.gz.sha256" ) - - - name: Release notes - env: - GH_TOKEN: ${{ github.token }} - TAG: ${{ needs.release.outputs.tag }} - run: python3 scripts/release-notes.py "$TAG" > dist/NOTES.md - - - name: Create or update the release - env: - GH_TOKEN: ${{ github.token }} - TAG: ${{ needs.release.outputs.tag }} - run: | - set -euo pipefail - # A normal published (non-draft, non-prerelease, latest) release is what - # /repos/ryoku-dev/ryotunes/releases/latest returns to Ryoku's clients. - assets=(dist/*.tar.gz dist/*.pkg.tar.zst dist/*.sha256) - latest="$(gh release list --limit 1000 --json isLatest,tagName --jq '.[] | select(.isLatest) | .tagName')" - # A manual repair of an older tag must not downgrade releases/latest. - promote="$(python3 - "$TAG" "$latest" <<'PY' - import re, sys - def version(tag): - return tuple(map(int, tag[1:].split("."))) if re.fullmatch(r"v1\.(0|[1-9][0-9]*)\.[0-9]", tag) else (0, 0, 0) - print("true" if version(sys.argv[1]) >= version(sys.argv[2]) else "false") - PY - )" - if gh release view "$TAG" >/dev/null 2>&1; then - gh release upload "$TAG" "${assets[@]}" --clobber - gh release edit "$TAG" --draft=false --prerelease=false --latest="$promote" --notes-file dist/NOTES.md - else - gh release create "$TAG" "${assets[@]}" --latest="$promote" --title "Ryotunes $TAG" --notes-file dist/NOTES.md - fi diff --git a/nix/package.nix b/nix/package.nix index be8197d4..cedcfa4e 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -1,11 +1,7 @@ { lib, rustPlatform, - fetchPnpmDeps, pkg-config, - nodejs, - pnpm, - pnpmConfigHook, wrapGAppsHook3, glib, gtk3, @@ -33,20 +29,8 @@ rustPlatform.buildRustPackage rec { allowBuiltinFetchGit = true; }; - pnpmDeps = fetchPnpmDeps { - pname = "ryotunes-ui"; - inherit version; - src = src + "/ui"; - fetcherVersion = 4; - hash = "sha256-L0HRYhFO5A0qqwdiYzN+h28kV7vAqB3up+Zu4ShYnBo="; - }; - pnpmRoot = "ui"; - nativeBuildInputs = [ pkg-config - nodejs - pnpm - pnpmConfigHook wrapGAppsHook3 rustPlatform.bindgenHook ]; @@ -68,12 +52,9 @@ rustPlatform.buildRustPackage rec { preBuild = '' export HOME="$TMPDIR" - (cd ui && pnpm build) - test -f ui/build/index.html ''; cargoBuildFlags = [ - "--package" "ryotunes" "--package" "sync-server" "--package" "ryotunesd" "--package" "ryotunes-cli" @@ -95,7 +76,6 @@ rustPlatform.buildRustPackage rec { install -Dm755 "$binary" "$out/bin/$name" } - install_bin ryotunes install_bin ryotunes-sync install_bin ryotunesd install_bin ryotunes-cli @@ -144,7 +124,7 @@ rustPlatform.buildRustPackage rec { ''; meta = { - description = "Ryotunes native playback daemon, CLI and QML client"; + description = "Ryotunes playback daemon, CLI and native Quickshell client"; homepage = "https://github.com/Ryoku-dev/RyotunesNix"; license = lib.licenses.gpl3Plus; platforms = [ "x86_64-linux" ]; diff --git a/nix/wrapper.nix b/nix/wrapper.nix index 8d67728e..abd0b98b 100644 --- a/nix/wrapper.nix +++ b/nix/wrapper.nix @@ -37,16 +37,15 @@ symlinkJoin { allowSubstitutes = true; postBuild = '' - makeWrapper ${quickshell}/bin/qs "$out/bin/ryotunes-qml" \ + # The desktop entry and default flake app must launch native Quickshell. + makeWrapper ${quickshell}/bin/qs "$out/bin/ryotunes" \ --add-flags "-p ${ryotunesUnwrapped}/share/ryotunes/client" \ --prefix QML_IMPORT_PATH : "${qmlImports}" \ --prefix QML2_IMPORT_PATH : "${qmlImports}" \ --prefix QT_PLUGIN_PATH : "${qtPlugins}" \ --prefix PATH : "${runtimePath}" - rm "$out/bin/ryotunes" - makeWrapper ${ryotunesUnwrapped}/bin/ryotunes "$out/bin/ryotunes" \ - --prefix PATH : "$out/bin:${runtimePath}" + ln -s ryotunes "$out/bin/ryotunes-qml" rm "$out/bin/ryotunesd" makeWrapper ${ryotunesUnwrapped}/bin/ryotunesd "$out/bin/ryotunesd" \ From 37ef8d3f83bc822bcc5c0c65334cc8a7b5428021 Mon Sep 17 00:00:00 2001 From: feelyourwarmth Date: Thu, 29 Oct 2026 09:49:28 -0300 Subject: [PATCH 05/20] fix: client now closes like a good boy --- client/Daemon.qml | 15 ++++++++++----- client/shell.qml | 7 ++++++- crates/ryotunesd/src/methods.rs | 2 ++ crates/ryotunesd/src/tray.rs | 1 + 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/client/Daemon.qml b/client/Daemon.qml index 92647924..9ea945f0 100644 --- a/client/Daemon.qml +++ b/client/Daemon.qml @@ -10,6 +10,8 @@ import Quickshell.Io Singleton { id: root + property bool quitting: false + readonly property string socketPath: (Quickshell.env("XDG_RUNTIME_DIR") || "/tmp") + "/ryotunes/ryotunesd.sock" readonly property bool connected: !!root.sock && root.sock.connected property int protocol: 0 @@ -109,10 +111,12 @@ Singleton { property var sock: null function reconnect() { - if (root.connected) return; - // Quickshell retains the native socket after an initial connection error; assigning - // connected=true again does not reconnect it. A fresh Socket also clears stale framing. - if (root.sock) root.sock.destroy(); + if (root.quitting || root.connected) + return; + + if (root.sock) + root.sock.destroy(); + root.sock = socketComponent.createObject(root); root.sock.connected = true; } @@ -191,8 +195,9 @@ Singleton { Timer { id: retry interval: 2000 - running: !root.connected + running: !root.quitting && !root.connected repeat: true + onTriggered: root.reconnect() } } diff --git a/client/shell.qml b/client/shell.qml index 6361e13d..9d677364 100644 --- a/client/shell.qml +++ b/client/shell.qml @@ -169,9 +169,14 @@ ShellRoot { } Connections { target: Daemon + function onEvent(name: string, data: var): void { - if (name === "show") + if (name === "show") { shellRoot.present(); + } else if (name === "quit") { + Daemon.quitting = true; + Qt.quit(); + } } } IpcHandler { diff --git a/crates/ryotunesd/src/methods.rs b/crates/ryotunesd/src/methods.rs index 868eb63f..08e92019 100644 --- a/crates/ryotunesd/src/methods.rs +++ b/crates/ryotunesd/src/methods.rs @@ -24,6 +24,7 @@ use ryotunes_core::state::{ }; use ryotunes_core::tidal_bridge; use ryotunes_core::{local, radio}; +use ryotunes_core::host::EventSink; use ryotunes_protocol::{ErrorBody, PROTOCOL_VERSION}; use serde::de::DeserializeOwned; use serde_json::{json, Value}; @@ -210,6 +211,7 @@ impl Dispatch for Methods { })) } "quit" => { + conn.sink.emit("quit", Value::Null); let _ = self.quit.send(()); null() } diff --git a/crates/ryotunesd/src/tray.rs b/crates/ryotunesd/src/tray.rs index 82c5144a..5d820b6c 100644 --- a/crates/ryotunesd/src/tray.rs +++ b/crates/ryotunesd/src/tray.rs @@ -35,6 +35,7 @@ impl RyotunesTray { match id { "show" => show(&self.sink), "quit" => { + self.sink.emit("quit", Value::Null); let _ = self.quit.send(()); } other => { From 5640724aee481734ed95f6971f3fcf402f9a448d Mon Sep 17 00:00:00 2001 From: Aether Date: Thu, 29 Oct 2026 17:52:07 +0000 Subject: [PATCH 06/20] feat(nix): add opt-in Soloist backend startup --- docs/spotify-soloist.md | 58 ++++++++++++++++ flake.nix | 13 ++++ scripts/setup-soloist.sh | 145 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 docs/spotify-soloist.md create mode 100755 scripts/setup-soloist.sh diff --git a/docs/spotify-soloist.md b/docs/spotify-soloist.md new file mode 100644 index 00000000..17163cff --- /dev/null +++ b/docs/spotify-soloist.md @@ -0,0 +1,58 @@ +# Spotify Soloist on NixOS + +RyotunesNix ships the Rust Soloist **bridge**, not the separately distributed +Spotify Soloist executable, credentials, or an automatically enabled backend. +The bridge connects to an already running local WebSocket; without it Spotify +playback through Soloist reports `ws.addr missing`. + +## One-time setup (opt in) + +Obtain your own permitted Soloist executable and Spotify Soloist API key from +their official provider. Then run: + +```bash +nix run 'github:Ryoku-dev/RyotunesNix#setup-soloist' -- /absolute/path/to/soloist +``` + +Alternatively, from a source checkout, run: + +```bash +bash scripts/setup-soloist.sh /absolute/path/to/soloist +``` + +The script stores the key as a user-only file (`~/.config/ryotunes-soloist/api-key`), +creates an opt-in `systemd --user` service, and enables startup at login. +It uses `steam-run-free` via `nix shell` for the external Soloist runtime. +No NixOS rebuild is necessary for this *user-service* setup. + +The service and launcher are deliberately kept outside the immutable Nix store. +The key is never added to the repo or flake lock. Soloist currently receives +its key through `--api-key`, so the key may be inspectable by processes with +sufficient access to command-line arguments. Do not publish process listings +or service debug logs containing credentials. + +The installer preserves an existing user service rather than overwriting it. +It will automatically select the legacy `ryotunes-soloist-eval` directory if +that directory exists, matching Ryotunes' current discovery code. Otherwise +it uses the normal `~/.local/share/soloist` data directory (or `XDG_DATA_HOME`). + +## Verify startup + +```bash +systemctl --user is-enabled ryotunes-soloist-backend.service +systemctl --user is-active ryotunes-soloist-backend.service +``` + +When Soloist starts with `--ws`, Ryotunes looks for `ws.addr` and `ws.port` +in the selected data directory. Playback also requires the separate +`ryotunesd` daemon to be running. These are **two** distinct processes. + +## Limits + +- This setup requires the user to supply their own executable and key; `nix run` + does not fetch or redistribute Soloist. +- The Spotify API and executable may change or require renewed authorization. +- The `ryotunes-soloist-backend` user service can be disabled with + `systemctl --user disable --now ryotunes-soloist-backend.service`. +- The backend and Ryotunes are separate; removing an older Ryotunes package + does **not** require deleting the Soloist backend or its data. diff --git a/flake.nix b/flake.nix index ceb763da..6440e0bb 100644 --- a/flake.nix +++ b/flake.nix @@ -39,11 +39,19 @@ inherit ryotunesUnwrapped; ryokuQml = ryoku.packages.${system}.ryoku-qml; }; + + # Optional local setup; never packages the external Soloist binary/key. + soloistSetup = pkgs.writeShellApplication { + name = "ryotunes-soloist-setup"; + runtimeInputs = [ pkgs.coreutils pkgs.gnugrep pkgs.nix pkgs.systemd ]; + text = builtins.readFile ./scripts/setup-soloist.sh; + }; in { packages.${system} = { inherit ryotunes; ryotunes-unwrapped = ryotunesUnwrapped; + soloist-setup = soloistSetup; default = ryotunes; }; @@ -51,5 +59,10 @@ type = "app"; program = "${ryotunes}/bin/ryotunes"; }; + + apps.${system}.setup-soloist = { + type = "app"; + program = "${soloistSetup}/bin/ryotunes-soloist-setup"; + }; }; } diff --git a/scripts/setup-soloist.sh b/scripts/setup-soloist.sh new file mode 100755 index 00000000..4acc336e --- /dev/null +++ b/scripts/setup-soloist.sh @@ -0,0 +1,145 @@ +#!/usr/bin/env bash +# Explicit opt-in setup for the separately acquired Spotify Soloist backend. +# Never downloads Soloist, commits credentials, or changes the Ryotunes daemon. +set -euo pipefail +umask 077 + +if [[ ${1:-} == --help || $# -gt 1 ]]; then + printf 'Usage: ryotunes-soloist-setup [absolute-path-to-soloist]\n' + printf 'Soloist is an external runtime. Supply your own executable and API key.\n' + exit 0 +fi + +home=${HOME:?HOME is required} +config_dir="$home/.config/ryotunes-soloist" +key_file="$config_dir/api-key" +unit_dir="$home/.config/systemd/user" +unit_file="$unit_dir/ryotunes-soloist-backend.service" +launcher_dir="$home/.local/bin" +launcher="$launcher_dir/ryotunes-soloist-backend" + +if [[ -e "$unit_file" ]] && ! grep -Fq '# Managed by RyotunesNix Soloist setup' "$unit_file"; then + printf 'Existing Soloist user service detected; leaving it unchanged: %s\n' "$unit_file" + printf 'Run: systemctl --user status ryotunes-soloist-backend.service\n' + exit 0 +fi + +binary=${1:-${RYOTUNES_SOLOIST_BINARY:-}} +if [[ -z $binary ]]; then + for candidate in \ + "$home/.local/opt/ryotunes-soloist/soloist" \ + "$home/.local/opt/ryotunes-soloist-eval/soloist"; do + if [[ -x $candidate ]]; then + binary=$candidate + break + fi + done +fi + +if [[ -z $binary || ! -x $binary ]]; then + printf 'Missing Soloist executable. Supply an executable path:\n' >&2 + printf ' nix run github:Ryoku-dev/RyotunesNix#setup-soloist -- /absolute/path/to/soloist\n' >&2 + exit 1 +fi + +binary=$(realpath -- "$binary") +if [[ ${binary:0:1} != / ]]; then + printf 'Soloist executable must use an absolute path.\n' >&2 + exit 1 +fi + +if [[ -n ${RYOTUNES_SOLOIST_DATA_DIR:-} ]]; then + data_dir=$RYOTUNES_SOLOIST_DATA_DIR +elif [[ -d "$home/.local/share/ryotunes-soloist-eval" ]]; then + # Match the Ryotunes bridge's legacy evaluation-data discovery rule. + data_dir="$home/.local/share/ryotunes-soloist-eval" +else + data_dir="${XDG_DATA_HOME:-$home/.local/share}/soloist" +fi + +mkdir -p -- "$config_dir" "$data_dir" "$launcher_dir" "$unit_dir" +chmod 700 "$config_dir" "$data_dir" + +if [[ ! -s "$key_file" ]]; then + if [[ ! -r /dev/tty ]]; then + printf 'An interactive terminal is needed to enter the Soloist API key.\n' >&2 + exit 1 + fi + IFS= read -r -s -p 'Spotify Soloist API key: ' secret < /dev/tty + printf '\n' + if [[ -z $secret ]]; then + printf 'Empty API key. Setup stopped.\n' >&2 + exit 1 + fi + printf '%s' "$secret" > "$key_file" + unset secret +fi +chmod 600 "$key_file" + +printf '%s\n' "$binary" > "$config_dir/backend-path" +printf '%s\n' "$data_dir" > "$config_dir/data-dir" +chmod 600 "$config_dir/backend-path" "$config_dir/data-dir" + +cat > "$launcher" <<'LAUNCHER' +#!/usr/bin/env bash +set -euo pipefail + +config="${HOME:?}/.config/ryotunes-soloist" +backend="$(<"$config/backend-path")" +data="$(<"$config/data-dir")" +key="$config/api-key" + +if [[ ! -x "$backend" || ! -s "$key" ]]; then + printf 'Soloist executable or private API key is unavailable.\n' >&2 + exit 1 +fi + +export PATH="/run/current-system/sw/bin:${PATH:-/usr/bin:/bin}" +exec nix shell nixpkgs#steam-run-free -c steam-run \ + "$backend" \ + --device-name Ryotunes \ + --api-key "$(<"$key")" \ + --data-dir "$data" \ + --ws 127.0.0.1:0 +LAUNCHER +chmod 700 "$launcher" + +cat > "$unit_file" <<'UNIT' +# Managed by RyotunesNix Soloist setup +[Unit] +Description=Spotify Soloist backend for Ryotunes +After=pipewire.service wireplumber.service + +[Service] +Type=simple +ExecStart=%h/.local/bin/ryotunes-soloist-backend +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=default.target +UNIT +chmod 600 "$unit_file" + +systemctl --user daemon-reload +systemctl --user enable --now ryotunes-soloist-backend.service + +# An already-active managed service needs a restart to pick up changed paths. +if systemctl --user is-active --quiet ryotunes-soloist-backend.service; then + systemctl --user restart ryotunes-soloist-backend.service +fi + +for ((attempt = 0; attempt < 30; attempt++)); do + if [[ -s "$data_dir/ws.addr" && -s "$data_dir/ws.port" ]] && + systemctl --user is-active --quiet ryotunes-soloist-backend.service; then + printf 'Soloist WebSocket ready at %s:%s\n' \ + "$(<"$data_dir/ws.addr")" "$(<"$data_dir/ws.port")" + printf 'Enabled user service: ryotunes-soloist-backend.service\n' + exit 0 + fi + sleep 1 +done + +printf 'Soloist endpoint not ready. Review the service without sharing credentials:\n' >&2 +printf ' systemctl --user status ryotunes-soloist-backend.service\n' >&2 +exit 1 From f706915c68a0b914756db877e0a2aeb0daef41cd Mon Sep 17 00:00:00 2001 From: Aether Date: Fri, 30 Oct 2026 00:41:25 +0000 Subject: [PATCH 07/20] fix(nix): consolidate Soloist app outputs --- flake.nix | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/flake.nix b/flake.nix index 6440e0bb..73d3a30d 100644 --- a/flake.nix +++ b/flake.nix @@ -55,14 +55,16 @@ default = ryotunes; }; - apps.${system}.default = { - type = "app"; - program = "${ryotunes}/bin/ryotunes"; - }; + apps.${system} = { + default = { + type = "app"; + program = "${ryotunes}/bin/ryotunes"; + }; - apps.${system}.setup-soloist = { - type = "app"; - program = "${soloistSetup}/bin/ryotunes-soloist-setup"; + setup-soloist = { + type = "app"; + program = "${soloistSetup}/bin/ryotunes-soloist-setup"; + }; }; }; } From e8c05795d25fc8284a0b0282816b5dd4556d8d8d Mon Sep 17 00:00:00 2001 From: Aether Date: Sat, 31 Oct 2026 02:23:48 +0000 Subject: [PATCH 08/20] Delete aur directory --- aur/.SRCINFO | 22 ---------------------- aur/LICENSE | 14 -------------- aur/PKGBUILD | 31 ------------------------------- aur/README.md | 15 --------------- aur/ryotunes-bin.install | 18 ------------------ 5 files changed, 100 deletions(-) delete mode 100644 aur/.SRCINFO delete mode 100644 aur/LICENSE delete mode 100644 aur/PKGBUILD delete mode 100644 aur/README.md delete mode 100644 aur/ryotunes-bin.install diff --git a/aur/.SRCINFO b/aur/.SRCINFO deleted file mode 100644 index 75182b21..00000000 --- a/aur/.SRCINFO +++ /dev/null @@ -1,22 +0,0 @@ -pkgbase = ryotunes-bin - pkgdesc = Ryoku-native desktop music player with native libmpv playback - pkgver = 2.2.0 - pkgrel = 1 - url = https://github.com/ashmitvoid/RYOTUNES - install = ryotunes-bin.install - arch = x86_64 - license = GPL-3.0-or-later - depends = webkit2gtk-4.1 - depends = libappindicator-gtk3 - depends = mpv - depends = openssl - depends = librsvg - depends = desktop-file-utils - depends = hicolor-icon-theme - depends = xdg-utils - provides = ryotunes=2.2.0 - conflicts = ryotunes-v2.2 - source_x86_64 = ryotunes-v2.2.0-linux-x86_64.tar.zst::https://github.com/ashmitvoid/RYOTUNES/releases/download/v2.2.0/ryotunes-v2.2.0-linux-x86_64.tar.zst - sha256sums_x86_64 = 2214e492c2402cf1f7ec6026cf94d630af6eafb2e272234876558bc0384493ac - -pkgname = ryotunes-bin diff --git a/aur/LICENSE b/aur/LICENSE deleted file mode 100644 index b90dac76..00000000 --- a/aur/LICENSE +++ /dev/null @@ -1,14 +0,0 @@ -BSD Zero Clause License - -Copyright (c) 2026 ashmitvoid - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/aur/PKGBUILD b/aur/PKGBUILD deleted file mode 100644 index ba2d0ee6..00000000 --- a/aur/PKGBUILD +++ /dev/null @@ -1,31 +0,0 @@ -# Maintainer: ashmitvoid <52139594+ashmitvoid at users dot noreply dot github dot com> -pkgname=ryotunes-bin -pkgver=2.2.0 -pkgrel=1 -pkgdesc='Ryoku-native desktop music player with native libmpv playback' -arch=('x86_64') -url='https://github.com/ashmitvoid/RYOTUNES' -license=('GPL-3.0-or-later') - -depends=( - 'webkit2gtk-4.1' - 'libappindicator-gtk3' - 'mpv' - 'openssl' - 'librsvg' - 'desktop-file-utils' - 'hicolor-icon-theme' - 'xdg-utils' -) - -provides=('ryotunes=2.2.0') -conflicts=('ryotunes-v2.2') -install='ryotunes-bin.install' - -_payload="ryotunes-v${pkgver}-linux-x86_64.tar.zst" -source_x86_64=("${_payload}::https://github.com/ashmitvoid/RYOTUNES/releases/download/v${pkgver}/${_payload}") -sha256sums_x86_64=('2214e492c2402cf1f7ec6026cf94d630af6eafb2e272234876558bc0384493ac') - -package() { - cp -a "$srcdir/usr" "$pkgdir/" -} diff --git a/aur/README.md b/aur/README.md deleted file mode 100644 index e1fa8425..00000000 --- a/aur/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# ryotunes-bin AUR packaging - -This directory is the AUR recipe for Ryotunes' precompiled x86_64 Linux release. - -The AUR package downloads the versioned binary payload from the matching GitHub Release and repackages it for pacman. It does **not** build the Tauri/Rust/Svelte application locally. - -Before publishing or updating the AUR package: - -1. Confirm the matching GitHub Release asset exists. -2. Verify its SHA-256 against `PKGBUILD`. -3. Run `makepkg --printsrcinfo > .SRCINFO` on an Arch-based system. -4. Test with `makepkg -si`. -5. Commit `PKGBUILD`, `.SRCINFO`, `ryotunes-bin.install`, and `LICENSE` to the AUR package repository. - -The packaged application remains GPL-3.0-or-later. The AUR packaging files in this directory are offered under 0BSD. diff --git a/aur/ryotunes-bin.install b/aur/ryotunes-bin.install deleted file mode 100644 index 9dcb6731..00000000 --- a/aur/ryotunes-bin.install +++ /dev/null @@ -1,18 +0,0 @@ -post_install() { - /usr/lib/ryotunes-v2.2/activate-replacement || return 1 -} - -post_upgrade() { - /usr/lib/ryotunes-v2.2/activate-replacement || return 1 -} - -pre_remove() { - /usr/lib/ryotunes-v2.2/deactivate-replacement || true -} - -post_remove() { - rm -rf /var/lib/ryotunes-v2.2 - if command -v update-desktop-database >/dev/null 2>&1; then - update-desktop-database /usr/share/applications >/dev/null 2>&1 || true - fi -} From 3434bfc6e55f7876449c535d349d7e76fef62b8b Mon Sep 17 00:00:00 2001 From: Aether Date: Sat, 31 Oct 2026 02:26:14 +0000 Subject: [PATCH 09/20] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 61ff5b84..750a3efd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- + Ryotunes — Music, shaped for Ryoku
@@ -25,7 +25,8 @@
-2026_08_27_02_44_21_screenshot +2026_08_27_02_44_21_screenshot +" width="100%"/>

Repository artwork based on the live v2 Home layout and Ryoku visual language.

From b936b8476a841a43f4f4589ed3d12bc8103c6e7e Mon Sep 17 00:00:00 2001 From: Aether Date: Sat, 31 Oct 2026 02:26:43 +0000 Subject: [PATCH 10/20] Correct image tag in README.md Fix image tag formatting in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 750a3efd..38292aa7 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@
-2026_08_27_02_44_21_screenshot -" width="100%"/> +image +

Repository artwork based on the live v2 Home layout and Ryoku visual language.

From a7d743114d0635edcd0dd40151036a0a24dab7ed Mon Sep 17 00:00:00 2001 From: Aether Date: Sat, 31 Oct 2026 02:29:53 +0000 Subject: [PATCH 11/20] Update README.md --- README.md | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 38292aa7..c39bde86 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,18 @@
-Ryotunes — Music, shaped for Ryoku +# Ryotunes for NixOS -
- -Latest release -GPL-3.0-or-later -Linux x86_64 -Ryoku native -Audio only -
-Rust -Quickshell / QML -libmpv - -

- -**A Ryoku-native desktop music player with native audio playback, live shell theming, and a UI that knows when to disappear.** +**A Ryoku native desktop music player with native audio playback, live shell theming, and a UI that knows when to disappear remade specifically for NixOS compatability.** -[Download](https://github.com/ryoku-dev/ryotunes/releases/latest) · [Architecture](docs/ARCHITECTURE.md) · [Install](docs/INSTALL-ARCH.md) · [Troubleshooting](docs/TROUBLESHOOTING.md) · [Ryoku](https://github.com/ryoku-dev/ryoku-arch) +[Download](https://github.com/ryoku-dev/ryotunes/releases/latest) · [Architecture](docs/ARCHITECTURE.md) · [Install](docs/INSTALL-ARCH.md) · [Troubleshooting](docs/TROUBLESHOOTING.md) · [Ryoku NixOS](https://github.com/ryoku-dev/ryoku-on-nixos)

image +image

Repository artwork based on the live v2 Home layout and Ryoku visual language.

From 484807cda8f036ee114c4f6a29872c1a748b0558 Mon Sep 17 00:00:00 2001 From: Aether Date: Sat, 31 Oct 2026 02:39:34 +0000 Subject: [PATCH 12/20] Update README.md --- README.md | 298 ++---------------------------------------------------- 1 file changed, 7 insertions(+), 291 deletions(-) diff --git a/README.md b/README.md index c39bde86..c3f3902a 100644 --- a/README.md +++ b/README.md @@ -17,309 +17,29 @@

Repository artwork based on the live v2 Home layout and Ryoku visual language.

-### The primary client: Quickshell / QML - -**The QML client is the primary Ryotunes application.** It includes the YouTube, -Spotify and SoundCloud provider selector, downloads, and Settings → About -software-update controls. The Tauri/WebKitGTK interface is the legacy client, -not its replacement. Package version numbers alone do not identify the client: -the release package currently contains both. - -To update an existing installation, download the Arch package and matching -checksum from [the latest release](https://github.com/ryoku-dev/ryotunes/releases/latest). -Verify it before changing the installed application: - -```bash -sha256sum -c ryotunes-1.0.1-1-x86_64.pkg.tar.zst.sha256 -``` - -Use the filenames for your downloaded release and continue only on `OK`. -Quit Ryotunes completely, then replace the package in one transaction; there is -no need to uninstall the primary QML application first: - -```bash -systemctl --user stop ryotunesd.service ryotunesd.socket -sudo pacman -U ./ryotunes-1.0.1-1-x86_64.pkg.tar.zst -systemctl --user daemon-reload -systemctl --user enable --now ryotunesd.socket -ryotunes -``` - -The package's `1:` pacman epoch orders `1:1.0.1-1` above distribution-numbered -builds such as `2.5.1-1`; it does not mean QML is obsolete. Personal configuration -and music are preserved. If pacman reports file conflicts, check ownership with -`pacman -Qo /path/to/file` and back up unowned files before retrying. Do not use -blanket `--overwrite` or bypass dependency checks with `-Rdd`. - -**If the legacy interface opens:** quit it, then restart socket activation: - -```bash -systemctl --user restart ryotunesd.socket -systemctl --user start ryotunesd.service -ryotunes-cli show -``` - -The launcher can fall back to Tauri when the daemon socket is missing or refuses -connections. Verify **Settings → About → Client: QUICKSHELL / QML**, the provider -selector and Downloads sidebar rather than judging the client by its version. -Check for updates from that QML About page. - -To remove Ryotunes entirely instead of updating it, quit the app and run -`sudo pacman -R ryotunes`. This removes both packaged clients, not just Tauri; -do not use it to remove the legacy interface while keeping QML. - - --- -## Built for Ryoku, not merely compatible with it +## Built for Ryoku on NixOS, not just compatible with it -Ryotunes is a Linux-first desktop music application shaped around the way **Ryoku + Hyprland** actually behave. +Ryotunes is a Linux first desktop music application shaped around the way **Ryoku** actually behaves. -The primary interface is Quickshell / QML, connected to the Rust `ryotunesd` daemon. Rust and libmpv own playback, media state and provider integrations independently of the visible client. The older Svelte/WebKitGTK interface remains a separate legacy launch mode. +The primary interface is Quickshell / QML, connected to the Rust ryotunesd systemd daemon. Rust and libmpv manages playback, media state and provider integrations independently. The older Svelte/ WebKitGTK adaption remains a separate legacy launch mode as a fallback. - - - -
-### Native where it matters -Audio stays in **libmpv**, outside the frontend renderer. MPRIS, media keys, tray controls, gapless playback and the active session remain native. - - - -### Ryoku in real time -**Follow System** consumes Ryoku's live Material-role palette. Named themes and wallpaper-derived colours retint both the main window and mini-player immediately. - - - -### Quiet in the background -The QML window can close while the daemon continues playback or active downloads. - -
- -> [!NOTE] -> Ryotunes is intentionally **audio-only**. It does not provide music-video playback. - --- -## The experience - -| Surface | What Ryotunes does | -|---|---| -| **Home** | Resume listening, remembered songs/albums/playlists, searchable shortcuts and local discovery ranking without the first-scroll jump | -| **Search** | Songs, albums, artists and playlists with bounded incremental loading and preserved navigation state | -| **Library** | Liked music, account playlists, persistent device playlists and local music in the same desktop flow | -| **Radio** | Demand-driven Internet Radio directory with native libmpv live-stream playback | -| **Now Playing** | Artwork-first playback surface with queue, metadata and lyrics access | -| **Lyrics** | Synced lyrics with click-to-seek and mini-player follow mode | -| **Queue** | Manual queue control plus radio / continuation behaviour | -| **Downloads (native client)** | One-click audio downloads beside the heart, a bounded background queue, persistent history and configurable file preferences | -| **Mini-player** | A separate compact Ryoku surface with its own exact Hyprland title and independent geometry | -| **Integrations** | MPRIS, hardware media keys, tray, Last.fm, configurable Discord Rich Presence and optional Listen Together | - -### Home in the primary QML client - -- **Continue listening** resumes the queue restored by the daemon; reopening does not start playback automatically. -- **Recently played** remembers songs, albums and playlists across launches. Items already saved as shortcuts are not repeated in that row. -- **Add shortcut** lets you filter recents and your library, search the selected provider, or paste a YouTube / YouTube Music playlist link. A link is previewed with its real title and artwork before you add it. Private or unavailable playlists show an error; duplicate shortcuts are not added. -- **Picked for you** re-ranks the selected provider's existing Home feed using local listening history, recency and artist variety. With no relevant history, it uses the provider's discovery ordering instead of claiming learned preferences. This adds no recommendation API, paid service or background training: at most 300 candidates are considered for 12 picks, only when the feed or personal data changes. Displaying additional artwork still has a memory cost. - ---- -## Download music in the native client - -Click the **download arrow beside the heart** in the player to start saving the current track immediately. The icon shows progress, then a check mark when the file is saved. Clicking an active or completed download opens **Downloads** in the sidebar; failed and cancelled downloads can be retried. - -To save an entire album, open its **⋯ menu → Download album**. This queues all downloadable tracks in album order, including any additional pages, regardless of the track filter. It uses the same saved preferences and worker limit as single-track downloads. The confirmation reports how many tracks are in Downloads and any unavailable tracks; a queue or connection error reports partial progress rather than claiming the whole album was added. Spotify albums still use labelled YouTube matches, not Spotify audio exports. - -**Settings › Downloads** controls the destination folder, Original / MP3 / Opus / FLAC format, artist subfolders, embedded cover art & lyrics, and simultaneous workers. The default destination is your XDG Music folder's `Ryotunes/` directory (usually `~/Music/Ryotunes`), with original audio and **one worker**. You can choose up to four workers; additional tracks wait in a queue capped at 200. Save preferences explicitly. File preferences are captured when a track is queued; changing the worker limit changes admission immediately without interrupting running jobs. - -The Downloads page separates the live queue from the latest 200 history records, with cancel, retry and open-folder actions. Closing the window leaves downloads running. Quitting Ryotunes terminates and reaps its downloader workers; interrupted jobs remain in history for an explicit retry after restart. Clearing history never deletes music, and existing files are never overwritten. - -With **Embed cover art & lyrics** on (the default), downloads embed available artwork and plain lyrics where the audio format supports them, and save matching `.jpg`/`.png` artwork plus synced `.lrc` or plain `.txt` lyrics alongside the song. Ryotunes reads local `.lrc`, embedded lyrics, then `.txt` before consulting its network cache/providers, so saved lyrics work offline. Artwork uses the track thumbnail with a name-and-artist iTunes lookup fallback; lyrics reuse the existing providers, including LRCLIB. These services receive track metadata such as title, artist, duration and provider identifiers. Missing matches produce a non-fatal history note rather than failing the audio download. Existing songs are not backfilled or overwritten; download them again to add metadata. - -Downloads require **yt-dlp and FFmpeg** (included in the native source package's dependencies). YouTube and public SoundCloud tracks use their source links. **Spotify uses a labelled YouTube search match**, not an export of the Spotify stream, and the matched recording may differ. Local files, live radio, DRM-protected and unavailable tracks are not downloadable. Only download music you have permission to save. - - -## Ryoku integration - -Ryotunes does not implement a disconnected theme layer and then approximate Ryoku on top of it. The Linux build integrates with the shell directly. - -- **Live palette bridge** — Rust resolves Ryoku Material roles and watches Ryoku theme sources with inotify. -- **Immediate theme updates** — no 60-second frontend palette polling loop. -- **Compositor-owned startup geometry** — the main surface is floated, sized and centred by the Ryoku/Hyprland rule before it becomes visible. -- **Mini-player isolation** — the main rule matches the exact title `^(Ryotunes)$`; `Ryotunes Mini` remains independent. -- **MPRIS continuity** — Ryoku media controls remain available when the visible main UI is closed. -- **Rollback-safe replacement packaging** — custom Ryotunes replaces only the stock Ryotunes entry points, never `ryoku-desktop`. - ### Skins -Every colour, type face, radius and duration the chrome reads comes from one **skin**. The default, `system`, follows the Ryoku desktop live (wallpaper palette, named scheme, motion scale, decor). The `matugen` skin is the same manifest written by a matugen template from your wallpaper — on Ryoku it is one of the *Theme apps*, regenerated on every palette change. Any folder with a `skin.json` is a skin, and editing it re-paints the running app: - -``` -/usr/share/ryotunes/skins// shipped: paper, ember, mist -~/.config/ryotunes/skins// yours (shadows a shipped id); skins/matugen is the generated one -/usr/share/ryotunes/matugen/ryotunes.json the matugen template -``` - -Pick one in *Settings › Appearance*, or **Get more skins** there — that opens [RyoStore](https://github.com/ryoku-dev/ryostore) on its *Ryotunes skins* category, and an install lands in `~/.local/share/ryoku/ryotunes-skins//`. To make your own: fork the painted palette with **New skin from current**, validate with `ryotunes-cli skin check `, and submit it to the store catalogue as `ryotunes-skins//` — the format and the checklist are in **[docs/SKINS.md](docs/SKINS.md)**. - -For the shell itself: - -**[Ryoku Arch](https://github.com/ryoku-dev/ryoku-arch)** · **[Ryoku Discord](https://discord.gg/8KjBmUEyKA)** - ---- - -## A small lifecycle contract with a big payoff - -| Situation | Behaviour | -|---|---| -| Main window open | The primary Quickshell / QML client is connected to the daemon | -| Close while music or downloads are active | The daemon continues the active work | -| Idle with no client | The daemon exits after its idle grace period | -| Reopen | The QML client reconnects to daemon-owned state | -| Explicit **Quit** | Playback stops, MPRIS unregisters, media state clears and backend integrations shut down | - -This is why the UI is a **client of playback state**, not the transport clock that owns it. - ---- - -## Architecture - -```mermaid -flowchart LR - R["Ryoku / Hyprland"] --> M["MPRIS · media keys · tray"] - R --> T["live palette + window policy"] - - M --> N["ryotunesd / Rust daemon"] - T --> N - - N --> P["native player crate"] - P --> MPV["libmpv · audio only"] - - N --> I["YouTube · Spotify · SoundCloud · downloads"] - N <-->|"Unix socket RPC + events"| W["Quickshell / QML"] - - W --> U["Home · Search · Library"] - W --> Q["Queue · Lyrics · Now Playing"] - W --> MINI["Ryotunes Mini"] - -``` - -More detail: **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** - ---- - -## Performance is part of the product - -Ryotunes treats background efficiency as an architectural requirement, not a cleanup pass. - -- event-driven playback state instead of a permanent 100 ms global frontend transport timer; -- daemon-owned playback and downloads independent of the visible QML window; -- bounded artwork decode/cache paths; -- native playback state remains authoritative across close, tray, mini-player and reopen transitions; -- Internet Radio discovery is demand-driven — no startup fetch or permanent station polling loop. - -The design rules behind the interface are documented in **[docs/DESIGN.md](docs/DESIGN.md)**. - ---- - -## Install - -Ryotunes targets **x86_64 Ryoku, CachyOS and Arch-based systems**, and Void Linux (see **[docs/INSTALL-VOID.md](docs/INSTALL-VOID.md)**). - -The published package is **`ryotunes 1:-1`**. Its permanent `epoch=1` orders it above distribution-numbered 2.x packages; filenames stay epochless. This numbering transition is not a switch from QML to Tauri. - -The normal user path is a **prebuilt package**. End users do not need Node, pnpm, Rust or Cargo. - -1. Open **[GitHub Releases](https://github.com/ryoku-dev/ryotunes/releases/latest)**. -2. Download `ryotunes--1-x86_64.pkg.tar.zst` and its `.sha256`. -3. Verify and install: - -```bash -sha256sum -c ryotunes--1-x86_64.pkg.tar.zst.sha256 -sudo pacman -U ./ryotunes--1-x86_64.pkg.tar.zst -``` - -The package installs `/usr/bin/ryotunes`, `ryotunesd`, `ryotunes-cli` and the primary QML client under `/usr/share/ryotunes`. Follow the update and socket-activation instructions above when replacing an existing installation. - -> [!TIP] -> An AUR `-bin` recipe lives in [`aur/`](aur/). Public AUR publication is pending; the repository does not pretend the package is available there before it actually is. - -Source/development setup: **[docs/INSTALL-ARCH.md](docs/INSTALL-ARCH.md)** - ---- - -
-Repository map - -```text -crates/ - innertube/ YouTube Music request/model layer - player/ native playback core - listen-protocol/ shared Listen Together protocol - sync-server/ optional room relay - -client/ primary Quickshell / QML interface -crates/ryotunesd/ playback daemon, provider RPC and downloads -src-tauri/ legacy Tauri host -ui/ legacy Svelte / WebKitGTK interface -integrations/ Ryoku / shell integration assets -packaging/ Arch / Ryoku replacement packaging -scripts/ diagnostics, release gates and packaging tools -docs/ architecture, install, design and troubleshooting -aur/ binary AUR recipe sources -``` - -
- -
-Development & validation - -The dependency graphs are locked. Release work is expected to pass both frontend and native gates. - -```bash -cd ui -pnpm install --frozen-lockfile -pnpm check -pnpm build - -cd .. -cargo fmt --all -- --check -cargo test --workspace --locked -cargo check --workspace --locked -cargo tauri build --no-bundle -``` - -Release checklist: **[docs/RELEASE-CHECKLIST.md](docs/RELEASE-CHECKLIST.md)** - -
- -
-Troubleshooting - -Start with **[docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)**. - -For a useful bug report, include: - -- Ryoku / Hyprland version -- distro and kernel -- Wayland/display-server context -- GPU setup -- steps to reproduce -- bundled diagnostics output when relevant - -
+Ryotunes have community made themes within the Ryostore, downloading the themes from the Ryostore adds it to your Settings > Appearance section. --- ## Contributing -Contributions are welcome when they preserve the product contracts that make Ryotunes feel native on Ryoku. - -Start with **[CONTRIBUTING.md](CONTRIBUTING.md)**. Changes touching playback lifecycle, theme integration, Home rendering, background behaviour or replacement packaging deserve explicit regression testing. +Start with **[CONTRIBUTING.md](CONTRIBUTING.md)**. Changes touching playback lifecycle, theme integration, Home rendering, background behavior or replacement packaging deserve explicit regression testing. --- @@ -327,9 +47,7 @@ Start with **[CONTRIBUTING.md](CONTRIBUTING.md)**. Changes touching playback lif Ryotunes is a **GPL-3.0-or-later modified work derived from [SimoHypers/LiMusic](https://github.com/SimoHypers/limusic)**. -The project retains transparent upstream attribution while pursuing its own Ryoku-specific product direction, interface, lifecycle, performance architecture and packaging model. See **[UPSTREAM.md](UPSTREAM.md)** for the detailed note. - -Ryotunes is independently developed and is **not affiliated with, authorized by, sponsored by, or endorsed by YouTube or Google**. YouTube and YouTube Music are trademarks of Google LLC. +The project retains transparent upstream attribution while pursuing its own Ryoku specific product direction, interface, lifecycle, performance architecture and packaging model. See **[UPSTREAM.md](UPSTREAM.md)** for the detailed note. Distributed under **[GPL-3.0-or-later](LICENSE)**. @@ -337,8 +55,6 @@ Distributed under **[GPL-3.0-or-later](LICENSE)**.
-### Music, shaped for the Ryoku desktop. - -Rust · Quickshell / QML · libmpv · Hyprland +### Music, for the Ryoku desktop.
From 8260358db397d8102b442920f4a7ea4e42e8db87 Mon Sep 17 00:00:00 2001 From: Aether Date: Sat, 31 Oct 2026 03:25:57 +0000 Subject: [PATCH 13/20] docs: redesign RyotunesNix README --- README.md | 142 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 112 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index c3f3902a..08f677ee 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,142 @@ -
- -# Ryotunes for NixOS + Ryotunes icon -**A Ryoku native desktop music player with native audio playback, live shell theming, and a UI that knows when to disappear remade specifically for NixOS compatability.** +

RyotunesNix

-[Download](https://github.com/ryoku-dev/ryotunes/releases/latest) · [Architecture](docs/ARCHITECTURE.md) · [Install](docs/INSTALL-ARCH.md) · [Troubleshooting](docs/TROUBLESHOOTING.md) · [Ryoku NixOS](https://github.com/ryoku-dev/ryoku-on-nixos) +

Music, shaped for the Ryoku desktop.

-
+

+ A native Quickshell music player for NixOS.
+ Built with Rust, QML and the Ryoku design language. +

-
+

+ Get started · + Features · + Spotify · + Skins · + Contribute +

-image -image +

NixOS · x86_64-linux · Quickshell · Rust · GPL-3.0-or-later

+ Illustrated Ryotunes interface with library navigation, listening view and playback controls + + Illustrated interface preview. The appearance follows your chosen Ryoku palette and Ryotunes skin. +
-

Repository artwork based on the live v2 Home layout and Ryoku visual language.

+
---- +## Made for the desktop you actually use -## Built for Ryoku on NixOS, not just compatible with it +**RyotunesNix** brings [Ryotunes](https://github.com/Ryoku-dev/ryotunes) to [Ryoku on NixOS](https://github.com/Ryoku-dev/Ryoku-on-NixOS), with a **native Quickshell frontend** and a separate Rust playback daemon. Music and media state belong to the backend, not to a window you have to leave open. -Ryotunes is a Linux first desktop music application shaped around the way **Ryoku** actually behaves. +This repository is the **NixOS-focused edition**, not the legacy Tauri desktop launcher. Running `ryotunes` from this flake starts the native QML interface. -The primary interface is Quickshell / QML, connected to the Rust ryotunesd systemd daemon. Rust and libmpv manages playback, media state and provider integrations independently. The older Svelte/ WebKitGTK adaption remains a separate legacy launch mode as a fallback. +## Features - - + + + + + + + + + + + +
+
+ Native interface
+ Quickshell and QML surfaces designed to sit naturally alongside the Ryoku desktop. +
+ Playback that lives beyond the window
+ A Rust ryotunesd daemon keeps playback and media state independent of the frontend. +
+ Music from multiple sources
+ Explore your library, artists, albums, playlists and supported music providers from one place. +
+ Audio Lab
+ Audio controls, visualisers and listening tools integrated into the native player. +
+ Skins that belong to Ryoku
+ Follow the system palette or choose a skin. Community skins can be managed through RyoStore. +
+ Downloads and personal listening
+ Save music locally, manage playlists and browse listening views without leaving the app. +
---- +
+ More interface previews +
+ Ryotunes interface preview one + Ryotunes interface preview two +
-### Skins +## Getting started -Ryotunes have community made themes within the Ryostore, downloading the themes from the Ryostore adds it to your Settings > Appearance section. +### With Ryoku on NixOS ---- +RyotunesNix is integrated into the [Ryoku on NixOS](https://github.com/Ryoku-dev/Ryoku-on-NixOS) package bundle. On a supported Ryoku installation, launch it with: -## Contributing +```bash +ryotunes +``` -Start with **[CONTRIBUTING.md](CONTRIBUTING.md)**. Changes touching playback lifecycle, theme integration, Home rendering, background behavior or replacement packaging deserve explicit regression testing. +Ryoku manages the installed player and its socket-activated daemon as part of the desktop configuration. ---- +### Using the flake directly -## Upstream, license & independence +To try the native application from this repository: -Ryotunes is a **GPL-3.0-or-later modified work derived from [SimoHypers/LiMusic](https://github.com/SimoHypers/limusic)**. +```bash +nix run github:Ryoku-dev/RyotunesNix +``` -The project retains transparent upstream attribution while pursuing its own Ryoku specific product direction, interface, lifecycle, performance architecture and packaging model. See **[UPSTREAM.md](UPSTREAM.md)** for the detailed note. +Or build the package: -Distributed under **[GPL-3.0-or-later](LICENSE)**. +```bash +nix build github:Ryoku-dev/RyotunesNix#ryotunes +``` ---- +The flake currently targets **x86_64-linux**. A standalone flake invocation supplies the player but **does not automatically install the Ryoku NixOS user services**. The native client needs a running `ryotunesd` daemon. For the fully managed desktop experience, use the Ryoku on NixOS integration. -
+## Spotify Soloist + +Spotify playback through Soloist uses an **optional external backend**. RyotunesNix includes the local bridge, but it does not bundle the Soloist executable or any account credentials. + +After obtaining your own permitted Soloist executable and API key, run the one-time setup: + +```bash +nix run 'github:Ryoku-dev/RyotunesNix#setup-soloist' -- /absolute/path/to/soloist +``` -### Music, for the Ryoku desktop. +The setup creates an opt-in systemd user service that starts Soloist on login. Your key stays on your machine, outside the Nix store. A running Soloist backend and `ryotunesd` are both required for this playback path. +See **[Spotify Soloist setup and troubleshooting](docs/spotify-soloist.md)** for the full instructions and security notes. + +## Inside the project + +| Path | Purpose | +| --- | --- | +| [`client/`](client/) | Native Quickshell UI and playback surfaces | +| [`crates/`](crates/) | Rust playback core, daemon, CLI and protocols | +| [`nix/`](nix/) | NixOS package and native launcher wrapper | +| [`skins/`](skins/) | Bundled visual skins and skin schema | +| [`docs/`](docs/) | Design, architecture and troubleshooting references | + +**Want to customise it?** Start with the [skin guide](docs/SKINS.md) and [design language](docs/DESIGN.md). For source changes, read [CONTRIBUTING.md](CONTRIBUTING.md). + +## Credits and licence + +RyotunesNix is maintained as the NixOS-focused edition of [Ryotunes](https://github.com/Ryoku-dev/ryotunes), a modified work derived from [LiMusic](https://github.com/SimoHypers/limusic). + +Licensed under **[GPL-3.0-or-later](LICENSE)**. See [UPSTREAM.md](UPSTREAM.md) for attribution and project history. + +
+
+ 緑 · Ryoku +

Keep the music. Lose the clutter.

From 6131d4096e83b1d0fc67c370993ce8d6a9a18d00 Mon Sep 17 00:00:00 2001 From: Aether Date: Sat, 31 Oct 2026 04:01:58 +0000 Subject: [PATCH 14/20] Add files via upload --- docs/assets/2026_10_31_03_59_17_screenshot.png | Bin 0 -> 327786 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/assets/2026_10_31_03_59_17_screenshot.png diff --git a/docs/assets/2026_10_31_03_59_17_screenshot.png b/docs/assets/2026_10_31_03_59_17_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..86fb050d1018a5f8cf04643b7f8e39536f88c348 GIT binary patch literal 327786 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 z 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�i6`)ZgbG@^IGVFnrqht4n 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

pVn)P2Jqw{O{}xtjEAo+ao$U zI$z&?Qjfz0nxtV-pSYr7ffk4Y_CMt5=2)_1QY>*?-MG#!xbF9@#a9oS zeZD7dr$?89`*W0z&OkIEd%2wkyVHaVznxb}Q@WtEH0FI$(#_i8U2LPd`FD*pB?L7y zH^n|h)9L$wuJ<{<9dkFL+nSYfCj!x`$`$$%TR-)VPMDUfYmz4=Uzoukkunm2PpR$; z_7R4x1mPL2K%aGXTQ_lx*dr}$dA zI&=15DN&gnm(@>3qHswXc7wXMgiboq%rDf*@&M0tG-{T&wq~3+oS$oO3HRid6Q$gI zJM!4bZ=H}p%hJ>~p}V`AqGs<^;@a@j7T5swU}z|CseVl==-Gy2%HISX9TBTcX0@%ne{+Ydu*G zr;3-)*HL2Mk!B)U**mrNTus)gu+Kn>-^9O9(zx&y)-m!;z*@X`mat9;?4_QWS?#=b z8Bc_g`ZK$r=%2GXKwZROW>o_9}Myp~botgSq+Tmu*({LzL z0b0o#dCJsH%b0b3P-TIwO?p^aEjG-drODF1b!RXr}^;VL;P#639^uimI^(VJV%*zO#%madwcuihQ`J;{~Hp$lvhH| zA-cTsoCIrI33RUR?z98kSrimNqlXvW8!e5VQ!ULd2d%ujWd5gi>3mQ?A4@LOQNq#M z0n8H*s<(o)CkARhUjzOF_YqR5zc@Z8|IZIiPxzm6mRfC*V&C=o@z1qB)+2NYd6E?_ zA6+@LicrcMr@gfp-zX;Wx-9Knm9z4yL8^E3w@h_n ziTn}FMJ$PCOTA1%SWv*_=6#~U@KTapPW%wj-ACirBJ+{JCqO{K& zceAufmoZks6&kmAP5L^czYMeE8=U09Hdj~{NB6)2Ut5#%~_^Qg0?Ci6E#LAgk>m<XYDaM z&pX>VvANT*XY5m4g_T&6X|t5gNqW+Qym#~Snb5nQCg?r%yUym*>1%J|ola5kg-O|3lijkx@*FT{+1=nYAi8t+cuGO3i#WK>G`rDuEN^|aZh-)=*V%#M@J>7 z@7+zf(=1@Nk(w;vTW&VRGLFVnVWwY;S^RV2EV%pSd814%qW!W!D)-etW?6hR99MuX zQchi5f%1SI-RUb^WO~(g4Ww#%4ya+T$y%g&ipE;%UAShQ$Kv=lDAOi9 z{ATGCubYeGVi)y*^TdI=w&5Iw4mZfOFke&Zq()%=4iUnSrS^9Ql#pP$^pq}Sh2h(g z&ZT69y$C-=7R60}&Mos{%PIS}UKy~Ogwjh|((j9%8Q}mlw>D1yS+Mk{PL=Zrh#M1l9md4D0H49t^;HFwEazZ# z2TbTwh-j!D*s*Ye$fNdNDsu#Xz`yI(k)(Q|JA_KWZ+TwKw}RvwIsW(8QnW9Ci|PsG z$S_eYZk;mm0DYQbeYuR}=wl7p;Dmmweygf41)*iO+Lviul{zb-1ytf2 zI<1#%=D3tUojF221U30ya=&@==2vYE?Eoe&Bg4hgYFs@JzYKqp>A$`gEvKcmUYy4f z0T=5Wk-e+3vaTL($JXK(kC9YjYaUbqT^$`OHJH1Ra;*TEM>C?-C!MG{`!^eN&emln z5$GM+jf10XWRrYiW?%nSKifz$#Zq&E?Yy9v1!!Q9pcU)v=4f4vE3hr;TGP5_d+EtG zK%3N$7uOrvZ3fyp^&(BRcviMb9p(TSO=zg4-gIJ!7G9Lyg0Zt29#lVY^@jARk*1KKv z!#%O}w86$)&miV--uP!5luIpG(NsCuOJ%$o&Cx=gCXbI9>WREwWK56+81dHD*3x+v z;-SYmJw5Hd8(0gS4pJ?#Wx2h*z3>&Yvd%F{Bl5fT`}OPBS0>E%rWu-K`GkS>KbpzG zXt*c9)x^ck9h6r$H8i+~eSfRdy0P3DZo&NuTqK^$p~S!hFFJ2%*jK5@kg>kd>~phG z+uF^E?SGH-kGs3`k0lrTbZ`ngixB{>afq?+r4>(y@LR~g(?Cjp9SmO878~eFssfb^H3JrZK+}Kceb+t<_>>2@w z!+`cTtf1(|_wNmE@D15vpbnWaE@k`?7| z(mn@cKn^-^V?K7GG0spy9$N>Tk6gCn`nQ2|q_a-D(0`6~y|uUZ9rSQODMy=tPC-G@ z^lwbTCcP)%Lj3MYj{gD@Gj&bb42ar$`^gy}#AQJf_Rv=6FZmo7`IO$_kt^L-B%QS!GLS@QE#2}0je zzXfnEcq6~6t4GJi)Ln5?duWrT5FPHAjsb+^ealW?YHIkwO9KMQ@9NBo;|9q&b?Qc| zX7EPe;q`QFHP+lEc1jA0b)Us+cC>~DFHw=sl@;3CTfYZeI_eX)n>A~3I-+PU?|)2= zt!G5B&jlP8QF@mrri|9-Jr+#;@0bCyJVO*I6$C_N(3!4PE+n)$@!cP{tY~8P%BD&H zN=j>6fUo#^0rUVo`COA*v?@~8`^|GMD;sYmQ09 z(_UCa@5*$IlA|L>>wVUt(_JVri1b(Zx>#@CK=-}&zD*T!3>8Q)$_q2BldkHlc| zKq?8z7Gr|qn&DXJ?$Rq-zHX~MqhVhfO03Dkn}(nJ&6*Q0e@L)YRt?`jUFcTWlmm2^ zOuLpE`q85QHFUuXNdcO>ppngz?G|}P3~(g^1?gAa!8PDQyJ-Y=v`gguZtLd4ypKiI zOhcVul-Ks&jm6i!bFU+(yf0{iBp+d?xAOQvD7;qNdi~#J=u>b6fLG(KTJjC5mD`W6 zKQI%Uyprv;CImg5z8u~!Q<e00=@E(ETNJy6LK)s=4? zn&;u3Mc$qNu$wbQ{WBky!}nwx(6wAwS63I!jsY1wg(@yU-S4o_6bTs^92Q$10JRTb zHeeeu5v=Gtwn`fta|k%jZ}iWRqv6!uP7^t-cgBfX+52Wn(FP`{95Tq^iy3aFw%)w1 z0zJMRX}}d@K*uF5zyb&g&LLTv@!VV?lR~0Re9^V zFfOH->-!m$LLlI>j>yM}oa1+#n`VR)83qQSB0nfWZTZj3U{b3(^jB(y*C-xMBu$*iR1{4^MLL5AO%TKQ&{N_B~btO8P#m>BS&pJ$m2B;KRdAC~!DO zlcq@9FT(1i-c||O*vJO6@okhYM7iy7^I;e{=)yRdYkY6vm6pGb4qm|WRv-GN@x`;K zyfGr2)ziyAywA19wV|hX!Dv7^$nX;A@zWVpHa3NTbQ0V#e-;;Ot-JzF4mHY)QsaNQ zW0fPDbP$^p?#bhj{W+|^>W~`y=uF!Nb_0VKb(){pR4;;?3v9uS|7U9Iy^0DhxUYU( z)@HIqR!u~7h1Rhp%h%gh(?^0r?)soH24Gf%6m2&zVd!CUh#9bqRnE-~@NMT-_f-xa zulA~HB`@ci`ihCCSmxT2zIlU&9^OeV>={oc;J^v4w$;^DY~1Iti$64vgoOcSZh+>- zod&_oDzd-_oj5(dskb+2XCFRJEs6u?FCg`Y0jt7Fq+sl-lcW77u;%#K&qg(q+XEki zKD956VRJa!($)3v{=lTRrq=quB8`jNJF@^4B^B82Ikcyzw|{+dW8RG%sC5BkZG9~*Y=^Rx%qh|fS|Uk#!J!mkEx^1ZNgFGny?79p0d3uDYN$1WN{C`fF%&Rd*4|$kyByzmF;KqAE3J+xDQ_MI{XLNQrYTPD_udRj zCw|xR`49z3;LEOuD~;2qIcM>$OPl;28($WGWeC1DHiim1jxNUkjhy1weUkp7p^ov_ib_ulHp7!|utgox<>*%0OQw(#m z16fg-o23GaQYn^sYEv=A#h8^48aZy~WO#$6o@ln0Vwu#>K?@$;%NxJ}HPTi<0F~Wt zQgIQM3mE`@!)S{!rByXq;}ih?rfr1zS*~HH&?=Bqy^*mKRCH9~I918naFOqoWx)S; z{;y6bu+ucLA2mT1RWAkO;)!655<^yQb#-;!kgIU#8JTg?P($~>;Y_ii44v|^$ypiE z1nSg*(OJ@GlRX3ZpFIpE>{aMjq^*D1Zd9<9Tx?{G9KKg0NJ&i@b;}w)KVPXk^St6> z9o=F+zOw0KAP-tnB=P~U*6pH$eM!-+%wSC2@!)8hfwS2#GI1bKBd)p#mlUito zE5kdMG!zq~8iBKLs~3=8`oF5Xe2 zv@&GQg$$=LIp=w&lkxLvHUc?kM4I?iA~sz(?mDBk{+4QB+xXw>W9GG=^A_S~{%me~ zfE@o_6sl{V&LG1cu1*`0jMjmyl6jZa<{FaoaGyvolOZk z)40}~%DVFPbxRo;8IY<{(A=B_PG#OGQ<`GB6t~1AkPV!Ei4(h;9%{tK#=%*c=6{)# z1TaL<31dU|_kHg!H^J7;P-N&V+RO%y+Z7iQRp9 zun`Z?;vJ38&dReSTdj=JNxt65$hsSC7hyM_*5xhx)nxL9zmcWO#fgKq${apjbg?+! zt}nCwW~40V5K0S>3wjdlliq-vUJzZ8yRPx_Gcj(FwP}^v!cr5~`tM}cOfi@AdPbE9 znQz?$1l2a3NtZ$n3oS$atBe;19{yB@6Q00*ym+{o%$oWNK-9S6Ej@tntI_iM>*6F^ zpe~g{OHN9Yktl+p%k)*?6F4;z}@77s6y+2vR5OV^@Nf^o_cdr`*5=^5n^rtp;Ts9pdzY<4&r( z_@W|aaG$MoM`wx$WIsW}A*g+v{Iq7sptrceTVCDf9k`|+)HK|YrX(a`aEAR55DQUb zm_**q#`ZXtf~=ePs9e5iLzh3t@A&Cd`J#!9N&L6>aVHnY*sPgE=AfB7#-q zAz-io_Nb%<=$Pf>LF^~Dw;%y+8F>w;)^D}7sXt8#76t7vE?$w7Q-d?#1V;NTy?H88 zm$8&Z^Ek>kAiyLY5H>iJulu*|noyB4kj4<6s?*6~KgUT_r2mqY1?b=8FR%Ii>xC0! zK_m*mP~P-{cqvbAf%n1*AY~wAyigf-f9H2F-xyATZl9Z4Y>qVSQ8lCyT^1~RDk-~A zqPfY=9??C-f`j?-eUUu0%RhXI{rwBx z81cIWWZ$|X?~vR50^nX$#I7^DyB~Oop?>pTA(tHjKr8)EX=DK=1ecMVb>xD7Mtvcc zY@&!tD%UarT7cRx36<*jQ zz@i4aGH4(>*iavHOA$;G*%c{{&`Upul2R&J=8KJZwqg*qMx? zmwQ08z(k?Q4=j29{k03;(H@I_;h;c%KzR&0FQJBCUHQbV{o{qFQYm!w8q9zO6LPyE zSiGC81*rq;ow(vF3)3WJ7ir=tC$3mtusIkxLt%j@K-s)@ceIQG_T7|o#Gc1W>F59; zEhInx^^{w4=s)tXW#K4G>E+~9qmDx)z)^EJjG#OM0`D1;9tLz-{26=x!=vG2Kyvz@ zl^DC6?U4B%HZHE`hf2HV41**!DnH*CFxd)cS?w(dhKJ#v?67jU>`gP(tbS!;0q7^F z?Hh9x9W#DK(upQ{Vw=BdTsDYm<+m8<(dj#Ag z0hrfv9OFVR=8H~e_Xpf_5F$oWLs;mwZ~0VSjofSCsrljVia(<2xS~lzq9fX?Kdc0Q zKeOtv(d-%p6c{l1`S#e=CESbpi=pU$NFeFM1oIT4))6@6Z)0WYJ3xA6dD96dhzFLQ zI$J6ExLzOau_Mf09^|(@_S*k^Jm>gm88z1Q@8Br7d^N_$Z}vy-1N*m`aa^g|Hpk)6 z@Ld51(9>sv*h_{4SpYUZq^*@Ma{^i5n)Ap>y|g)(KXUZ__i@{+EANE(h@LzEqMZWx zZ1-6RTq+3gs3c+SfI1CI%Fz{i6iX&6p7~`7OjlqNWB?s`A2-}w zar-y0!PUyCltgv6^&Z&DXuz%Jv-GHRSsM&BUfcq`Q?-sfAQsQ^14Nqk=BjzI65lYl zu>^}e9IzD|#6%A!`u`bD3OaVn#wlvcV_kNvUPQNFZ+ew4t@(JB1OecnB32bLzzr}l zFm5=3S_3+x9N!ZRW52Tr7C1kCq|@#s8{meWoZ{b7|F~w5`YhXV4yfZ<;msoo}p%}&Cf^ZLV9|049uA;f5}w1lZOLe6yR$re|3r9 ztWc$W$pon2OD(Om%ycV%|7;PD!%F-Z6#Ul9eV;Bfn6U7iD{fq#+C!ySTcdARjR1Gd zSQ<9CM(lF@B_S^u@zC)M^-N7o16_r?r!V~cZ@y7+adE+mzs>@a^W|>qCE3G3Wx);J zJ2+!a^C@3$a{xOoYSdTR7=wW11a>ko}gTuj5uF#DQ}MDke!4a9xD&uU z7(Z-bU>#|Bx_(B+6eTH~)dP7Er8I6~3QH(=n4`X7`c(Ym`xvu@oIk^jHZ@Vq$wW+f zy$FAp0YBWG0+RtOwIctna(YVY<=W{;y@*)YB30YqdPI*IQ0Y=CDpL2WAM-Wj+^=$g z>!2@6QILRk2{)cc3IB4HyYd+hju2fh$Vi78Dc! zmGE4XS5iw8_4L?SM*`h@kei-cQevmoP^lD`8>DJ5tpl_MphE(n3IWz!GFJBqjyiw} zFdXJC(vOd?30KX>x#bvMekpj!FZq&@K}Uj_XG1ogXPGE5ph480JW2 z)GY8LQCV5bBqXNX#)=5QEjz$pf4}rtWn%+~9mgwW^VPx2U?^nJGcce|kTua8F`EUT zDiJZUT>d!t8+(MAt?fguU?Th&2JV)4deBAzI@9%mo%}v#yWn$wyCTMqAH_Cc0S;ln z#pTHSXZp7mUiH(lpzjg8IC=+ougB^|pY>%)`_i7NWQ)=y$krW0ssFQ0?|O-02G@lz z{R8aE=oV(K6N%x}>GmXml!vlJJSw1|Uw<+^D4w;c(|YID5?^)QW6GXt?))*T5DSYW zOl{BSKLD>eF8bgCZ8i$`_pw(1RqO|SANn)>_}6KQ4DTS69-Gfoq=8xkpo~ELwF38i zH%PMJ^on>|QIP#!553puE0NCjfw2$b-5@o;)hhUC-mG<%k)pmuxv>urWpakL&9*;A z8r-hcEg@fPr~sN= z>m2cc!YMIRORraS_gC6%PELKK&t_yp){`v>^kBx!Jf&K}Il=EBM4e|7p9z&7g9B;Q z;+qD*m|90D89+4x4;;!Co1_aQNy9U3@uOuTrCABAia15V7zUvP$Tsyr)OC7}Wxa%J z3%apsb7*(|%KhrGXaC80F;s!u)y@56M?uHN4*CRx9Kk@2IXXHiLz~uzHt*wChmZcT zT`bZl{ZV;zgpq?WPh@vc51bG_Jy;*wOfP4Y97|1rIl8c*450ne(o%AuiqK@~u6Nn2 z^S||m|HuT;?a&ZmXe$tYn<4lal{1$>^3RuoNK3sR#SPyszE_kL7ypm`p6vPe&y~v< zCIN9LM|)n=%rVXz*=yGIGExbVIAq0z9h_|Qm(Q7CEIWBm#Et|aLkrqRYB~IrFRJok^$YS34i zIU=&Z5vokT@>%#?k*D|M1RuYW3~jSEN&fYzSCUTcBCir{LUnJte75(OWdAT zP`z#XfkEDHafiR@CvOh!cz&p-V;;{kM6qO7JDHH?@gfB(RC$9 z18ds;E*unww`v6ThzBxpaAYJ&x$xDiSEe67Mit$#Y64;shzkJY3CP(@VYlKhiFuU! zqV9Vn^JqAKtLxICBGWbX2b;?MttANndz_rqp4HcnCX}$y+ZZ4dsJlCRp7M>g`%!fR z)S)?Rz2n?!fLN8Ub{{yQyML&u5tRAey{dIsq{)sb*B>nZ&;*GB`o?yLiX<4Exkp27 z$zepTR5z1HX)yCiqNeM5kUrM8#>NbQg%U_J3kSW+RpsK~tXt9N&tSv!1;8m-^2I}I zAP{sCGTMUfbf>{j}#$*~l}*nU6_X7H~|k`sQyfVBY{ z7U8Uo0gl!+;?|3&V1%R1Q1N?IHokmywhPn_r=VB0vbF|qyXkTx<7Np>+{D$DKugPR zhXr&T3k#G%?MvyrAWXWQhA8fI8Ds+8wrHur6NXMa-BWZSvCYbAjq_b_Q|8LSLlA+M zQ&#@b2heQZS6o2Y*j}CzQPldC$n((uB`*m+NsKBdXH&*W^7%Hlw#*G)sVONybd4G;apNxz4kbXa z30B+WkC$XnEQ(fZT*=Z4xHvggAsYti{*sB`DH>DB0)s!T!PC6q11xjj`PlmO+?98G zm0fnUg~b~=61^38frU(DfwZ@|&C0>dc7O3wRF3Bt`~`Y$ogBzU)6xQ{BM^qH91D6J z%$d5ogH+k4DXH^8Sfe(d0ckzo=m8)e#-x$Ol42H+qVp*xhKrY1iizOEOGTZRby~k& z45pRs^vT3>TC9Z1oWQR_cvC;y zx_4<;QV`bToJtZA-M|2<2ywSZE1|cPq~XU5l?23V@^=)camA*a6F?vRPxK*~iwCqm zvh?bMg=8@@KvZPFt~1Em^f7?YZ1_a5@^c6Hpy3x05s~P9*1eHRh=p9oLJ*y&!0}$Q zYOd*-jh*=>x;{mG#=D#2JwAH8J*;%|%xNh7p?K`npAe-oGB6Y4KLFU^N(TqdfW7y0 zM0^AsU7#&n2BbN-YfmmWbE=w}3=Bp#Pe&&v9_l0j1}sL8?b5A|lRG5;bI{XtOz&#QA)MUIfFI0SM=IlR`t(f%RD^Hn`oSM+)Ulgn1U#qD%5yfG z!;%j7<%8fE$Vq0*?xzxhc<7*~id_Z&nV{Pa0Z=kyKTAtbe~=-)1Ee?D&WhthF621j zpr|l!cqkQ)+nwuB5~l!uZgF6W`QDHk?<)Xcwdu*QCV`LSJJS z6c&Qr`aiy;5YX&_fsY?KEVdY8=eZd&;zzDwHAw*Y2BgoLT3I~<;wb(M;9UpuL|~Q6 z83`{gF5~&ug+VKa57g(2;#Ue*QG=a~izw6MKg|80Nm~-c>5Ze>{1zv+GML6A>U9zV zkjMzIQNYX?X3ELKqv3kH06IQ!W6%-RFvKtdTk}d;>_zJ0`Is2Xnyn5Gg6nB- zxR|o!RZ`mDH#@dTF?VJWj;n(N-<~~^t83We;jvYd^6X%dhK)9atQR#=J>)j!=5jb{$=CgM8I() zIsM7p369Dz%r6ckG|lsmGkU)WQm)c2Kfx*y!6LS}IP^I~wjl3wO%(}@O}d`ClHBS~ zx$*P!gAn_UN{Ih8V)3K|P>t2qd@Yw7WB>*0-OLFX-rIv12i)?Zp`p#yyMzO5!~Zzi zc0@=2Z8~~T+sVlZ zhDfC8pA)8I5&Fmx+Nk#dV!QRp^t+wXYUbGY>6+5y8Hy)&2PuCZk- z8s%TMhvFjTi!0Oko=#-I0(nd%pp~%=K0AJOT05i??x{MuB6-FFyyF=X}*4td2aKwCwh zBEMI%yPfhcIXRmEOBD6J zhzG|pnWXaDLzSa3T^V%IH42F+oIx=!}+(7ABl_q>4-%qB?*xf1h;A_Y5M>A= zdV6oa|Mgq1m6c^lX6Erc_nv#s-uvv+i#WL3XZ`8DY5_o+O@ZCn<)%w(*bW=~u3H%!liV^88$Yw$Z^C~A!^Z^QwEcDt`fl){ zb?k&e@=Z2do?~wdu|M!kqN8XaYz=DxTC5$QoOzkoQlV4tAAT5yAuxuo0@X5 ze2a`cV!@{&v`gvS*NaeG_)>i2vUi#z`sgMp-3qBOOZV2 z#4AXiCk+D*)4#qFEQG2;D#<_+6&Dw0W~K|l1un}c0hF>)0SONjq4+qsGA#J-C_-y6 zBEYN@+aeMdf)U{(+iBqmH?8XE82KmWuE^bgbsWHaCSHm(hh`-+{oJFgEaRb;-P*DX z{oZWOOkVXSF=I?KPwkhF#hb}}5q%R$=CQAmt?`GC<#HTPJn=CbyPl`5hn^2(3~3?= zPiS^AQ;L{IryG7`v>$PxCzCO|^S-5tT})%dLA6Mj*Iz+bTVWatqf?=!Ywz__PhNK9 z6W(VzbE-t>+uADmI+dE-)Mv9Jfk1x+obI+O!8TYQx!xH8$zD^%AUKzR&LZd;Pug<` zZs6a1=9Ly6;9%&(Wa58tcfcaaw*Cho*TEL!e*75pBJ6r*fi(>R1wj{Nu%^j%ov?jx zV%L*QHt|XO={sO?RG{&1!OXtN9kUnUBc@P%Z|3nM>n$gt1-~BkuV4LE?7Yg+ZC4z1 z;*9U^zu>ZbO0njqN2w=zcw&l=*C+oTc~?MCtT5d1D;bXPKbFpFOpCZnpjbEym@$`@ z4k@dGCx$04FH&*QiAu5{P$xey+!MuJ9Iye|UDw#=frrgF(7GL6Sb8ubLknKKU`&c`kh;}* z`jA~o7GR0Hsu7Nj(R(ICBS*A*)HK=BmohO<%G5zlWcO|dLY94umA8+5CAbI8f`XW3 z)e|g58z?6Ph*2Ik0(2mr0^ey7I6S6n?``@mup;zQG3Oqq7p!MrZ1k0%l1}x-VLq@# zM7&sGRkTNYUe1M^zSh&RR{_U>DFhMJe&-V>650%!%Vzc1+1a5Q=lcn&MHhz8_8nHW z@iFdP!$U&?XZ}nm4-v_VQR{~NfW66vbqxR%1+d??n-yAcOasHk{8c4PoCG$p_hgnk zUYc^_hdJwmA6oNnE*T!eeROx5r{~z>hfpfPb*PBlFBB1-^D8NV{CTcZ0hAhcDT{>Xj zB3Hj$4uT7EQ&)fgAz)GtRt+!F*2zVvsRCGpbD)fAkGIKmF=bBVl>`TxR@qe3@JmWr z@M}6736s=P$dXM6s8fb%aRF-Ay;Z&1kej+hi|ZCZ9>8IDddj(Yll88!!+L3#8z*t5+3}gm^w;HHQ0dv?$*f3 zX~%W$P5<`#;CrP9*IOqhrn5r9UM=ZC+CZ=Exb*e=`H!bl`q@F3N0p)?Vse_=(>gl9 zY`5-u@Fp7Vk}Gak!(H3n&_v7dG7d<_f^E*VZcpg^=c_S*9q+}`YLtz3b zH^(NIA8szaZc;+>*$g*YJN90Mv2;pZV5F?GueP%twv#gDGLJ*#&f$$Kr^e7^(ge4BvJs59kYn%U> z@mSE+gTt!Kaa^Q5JAR9PE+xd4mHGQ`2$hzC)^cRtK1MC5+bQUViav{U(N6a;dWW;Z zuw{sYIS2uau{{}K>Iib#_O_0Jy_ z47Q#MQRRCluN|Z(`139HsMNkWIS5X=+zZZWa?V8w3*ga2Z=GG6z1O7wm#^gC9ewGd zUZS%PCtW)E=Roelpv(p}$Q)*m!*p|j?h?SS>8AD^9PokD&|i}D8z=(+q^MES4Soc~ zTAYLh8U)(fd@B5bpxGWAHW1ukV6MxFWMF0nZE|P+Ii9U5*Rg1C_lj37QhJ)Pd_Lt7czm{@W$RHo#dUpGQ>{UqQK+Fo6fVY|ty&9WbMq`w z$HloFIG_V*U#H&#khEF*+rm<(olYJ_@&f-H*Xp-0d1tl#@o*tsCYxs1K4js|BRgxH9}Jji=rC`592Q3gZ`#gnsmp zhug3WR(gu&&?R(}dU|di`${WRzkKbpMUFzDtn2ja^i>v<@wb)xllsBnEiF621~0Gg zF-#-M2tt1q%@GOaUf4J-4HeDRJVSDi`G>Gi4-gRC;!?VZnW#JkXd+AQNMHS0!UqqA z2mq~qdMQoWLxD88g>En(*QJyN3pi?#mk8W+cd=|;2RJNDVnp2Z037rnyns1THkk*3 zm%&E&Ab#YekGZ?|bd{9VRI1zCk(@~X!`(&@3N=b#c7ucd2WM*DH9-)HslEFfCDGAg zP*g=l@_@24F#7c@3MniFr{F<8bHGsYzYHN>jniaeOMqfaAjLK%A7lRYwjzD_=-!Eq zSIhj>F%#0ahX41kdhF1U@<)a7j7KNDaFw@&@4n3s5!L%X zTfZLpiT<(+*e>Ib+6s}~4YsNU!Un)%gX#hli~y-%!FLy91WO84X@UoQf0Lhmk`~J9 z@h~z(#RjZ{dfP@Q3#3ZG)83ReakT55Nvb7izyTozh|a~bNn+(f!2ATd*k1YvNRDxe zwHiITVRi#jug#;2eq;eVffoVlS?<9?V8w9(krmLkwDRR5`BpNy z{nu~d^eIeX)gPOzqEa!n3lo z<+QakhpAgkA*Z)NCh&|k-V!Zf;6R=!$CK70pi+}Tgo6XJAz*gHNeKM?t%|_0UM!ed zr(dp1@c34gR#J1uf5C6XY1z&Zy-t!74GJ|HVuq3lZ>|% z-`SP=gvPG;-QJ242s3mX4QA2gq{M&sR<2gDj4=_Xc0$wQsSUe^qHh##8kj=%IJpb+ z;)?|ZZg7+q(xN#O>no61E@Mz9&6Sa-JEU1YRaJgcv`vRuz&vLmeR57KB~U}lbL=0; zyxd}p7$gb2?=zEJ~R@ zq|k;F`EJ9fnr`+a08m&-Fpy!?a3>E~fYG5b9rB=xBUlA>vU(+|n)^{8{Fsxvr?}l` zxHW%PIeZ8aE|>rPxQo%fOIn)>KC%&XdKjcAmV#{k!#-|fRai7&#Xl4Gyg;L1$`Ra6 za0K^nBv?N9ahx7wPncP*qvaGgnXsjW+$wqKI!jb&IriIqRkU)7-X(ph);pD#CrxSX z(8u=Z9z7}3#}f~jUMH`LqoF~O7MF&TfUcf#5o%AfY>G^lOln`WLd(0UHk$bqSBBOg z@HD6IIL>30Azh*9G=D@AGluCCpsQpjO0g2V!+2>?xEwtJ61qtoL zw0{{A%V&*p^|C2dcY}hYhk(29U$1G3Qz{VqaAVz$P=}_xsOFXGLx{yX(!v-0-r4Bm z2CK0GWOiT_MJ%B5Aa`(8gy%@E?s#mnpXsqlKYyZX@~{Oc4I|CP85o)61qY?uat5sw zizd>;WooB7N&R7p>{$aK*9DiqZ=7eoyRLvak*AJd?b4u^ibiQO-y{&M-R~4?hwRA* z1hFrQ51ui_au~Zc?gjh^{OE1`>N=hC`rB(db^xZt_t$h!hdjoW`NkU~($AJQs17JR zrKqhf8}L+tsX6UEP`QGvv|85!Fd#rjrF+DxNDh7mMhY3CWYynOj0mfM_y?i6T6fI= zFMG%GacM6fD|Rd`_@_`S4^&FcJhc-O+&n|I7vAyh6^Ew-k~gPiCKJCs32<`5X7xZi zDFcEM{&N8QdNMS4dV&cNU{_PqQ0sokioS24n53}KKCs|N5BF|*-ciXfs<(Q4gjH5k zT|JY`*#INq8n7%#=&tHXLF6h6z6%45bG5)@onV88Sew{}8(F4jUXF|14ZEDYa_TeU zBFjLfE@1F@yhPv?N=L8VKZ+Nh;CYj$M=Y3m1k$bG3i>yJ{aL$0ZN|B?TMz#gNYnI1 z4SFUW$6K?V?}QKjTXLti|cE`*fB&=ssmQvV*b7n%uwW3LMpZlD$xtdhNHR1sumo=j zW^{lC)*k<01JDL3B^bKeA5hN(72 zo2DzLDES{GkL>$1qdd0$o=;|};NJex1KrZ%(w|``7l|9}ou)PrZ*8jRvM3qqYjjfQ zN!d4~kx6O_xDxB=xKQ~VQ_cM%4C>{$VBzX)E=ws}QBeT~5HfiQ3-LZq)_U4g1ezIB zl$1moLNG1dkB9j6c*cTyrh8cZrSuV5sbGp}2ygB!MZ$v+YraUG=;kexBxDjXQV~kk5`sO?qr7I%QOKLh)8RLbZ z?mBV5=7DquKl7r6ZL~Ok9-@+kk8~Zv1yrINh@ZO~#qUA^kfqC=G`J4RGne6vVXFi9 z6Ou237EeK)#@nRG7;gH-kSXb7X=!gN6{IAob)k5gE*yQ<9`Aszj`%a$DcDMT~nzPqkB*UIqL#M@Bt)5%$d&F8B z>@u`T?n$S2w7ns?Ypy=iU}L4{_MP0py$yz}gTWP?NzxC$CD$4z&? z`PLw2ppt26r_NnkcO_8KtWwTWZS*sX1vzL{OO0~t!YA||1}#|wq9Q(3*@8P}CHly+Hf3xhfWK&rt9XN&!l0`t0iT=Yy(xrlzzVmzsWp za=NWuwRXzP%}-q8Ya}`ILc46U?pl+6!I&-Z5Kl`>ZE9$cD?&do#5kmt5^FYT`t6G%;9S~5#hEAiQ!8oT<^x+2ggo!fr6)bGM zM*n%3~1=xKP!^zbTK+6CE+7V)~N2E-{FqKjBdFr z7HNW`QA-;%7rBBk0A`Yaqju1x9LX6uq^wp8dIaTDY`^H_C=^xOXXXC@m3pqfR-1DAm5#1~ap9OffH<&aj|`3VExerKeeOb5u{|>zmsaawqlIMsom0>;f-hhgoj>%BVbHth1K4v zvdzLMP=fs&{>1r|lv$#1wt|jxXya@pU z`J@xrLk($pVgI#Le-#)xw@G{GvC?jeq=~tI&^{|9f+mAI(A@mL5!ViS_mu8@jpW-c##q;M*R@j1q`-ZDv5#N`En9;)-cq z-~okyD%CJ3Rm!QO$n3?Bg_#IpEZa?oa8i?0vMydp8{;r;M}bWE%$${nWrl&UQ&mgL zvyP>rG3$fZc3JHeZ20^6YI#|O0Kll5bI{VZTn80@MdjEYmbOswQ^Lldaq=t!0|q80 z3i6uvmLEmA^Plv~&BS?xD~!xROw(^uEzoBpdj4abJne~5XH7Z(^G{EnJT{vZD)$s` zF{KL+MKD^lwa4xGoXSXp)&fV*aC%gwc_uZ;I57?S4s+S6ld?47U{8a5W6G9zfw$k{ zDL_i7^pZ}`e>aN6F*4_ac-6Euv@+c_FZ%G71p&*I-F;7o!E~>|7 z{1m5zedK#qb!ijl-l4~1?r`+iy@L)ONyIyEN~b@UK4;CeIoQ4ATf`AT9oXmIKeI@3 zZveMIX3Q~Sr%W028X$=18Z)&9R)jiGP>(t`9vG%|oJMyn{{kbMQ>myd-t{jywXf{& zNBT?3f6`q8ERz5ovE^2mr>BdHr*9bO1X8 zBsxGsMP`3%}$ZUxJ^q5EE@PrH84K3wNqbd z7m?~)?VQgrw#XR5%?&zd<3_J|nFOg6X!Q8WVu=-#yj`8a_;I0B2XN+spxt9p(j9o7 zur;7q$icCN6&tXCa=i@h0R`irW$BSv4mP*t7Vh_N*B`Yk9Ie6ukx15EgvsSf%gD-D z0m)9cv>;3oT3~h?-7qu12yC4|!T>>l4r}bANA7+Sza8zzZ5zpOH;+6dTRiUDeB8MW zkquUD9Q<|sTx@}-oqhafhoxen>mi*!` zzbv8srR$CHK91SDVta{yS-k zsToiYQ-p&NeO`|9%j2uig}_{;=;%<^`GuL~CjAgznC7ta#(6BiQw`-GAIau`EUl;C zp9CG!$CU}bdo$GZK+(dsMwvBNp7Ji?nRT_9ILSK}UOgUuasJ`_HV#-a!8_i}qS=y% z;Az_kWP))0X=1Osxn8AShrW1)k)31kG*B9deZUhB1ulvJz1vcVLiC?+6B>w%lif}j zEf&rM>fZ#pT3N0PSiu9`^!N%!5H_u#*{jJ0jbp?i;0bMd`q383@Y1P2OvfOgiEY=1w7zUD5DfK0SD^Cj1DOsXmAH8m@If5>>&P$c;2(a0ZM*xz3{+ z^YnCS_4m4pUrM=5;)QNNGbscvdQa;075$Qy(2@ZtJmIn z6^myNpUFe-%B32Xf@h%wlsNmR0~C~c?6?g~zn#6k5elC_ULQ(b>;aAoRwtSE1CPlDrQ53C-fgKl$%vx$dj1e z4;>)yxY&MV8+)(8;qaoey|aqj_ev6kXkf)`x($Ny7|H`oc5HN7v~X}pDUTpE&(o!R z0CLF)1oZoK7vtRS&xe8s7R|sSWZ}D8z&|^riQjd28{E!(zcTC130WQt^a87q9M8@z7NW75{Tuzt|B#+AY%>_Zrop*(Hc*s1 z?&$Ro8nDqBO>UstE}L52-%rpe!G%PO5vTNL ztKy;K^xHal!GFpxwN>Wc+uKW4fSvxrI&FISjhV4U_OA+?N~!ZuXJm3~+6jojCFL{) z|1>iIsOWSjIeyl^DFibEqae`VRo&6Nk1#fESK~>RP5m5v6|PvmNE;d?Q@~V02App- z1;+;{Bl`LTGKV?C=&ik4|6YjA4t19t5Zocxpp-QhwlfeWqARD)cBJX^uxUWwS&w}I z{RA4TAP-!sPBY&^aMfQ2ecUAbsrMy9x(L1Aq;F~C65KqS;r*QhALq=TL?*I-0nk{5 zHj#Niyjf6$J=hBeP7(e*eaFAq^s`A?n1&C@Qf96#n(8e@K|r6&=4o*RId?#t8mm-1 z45|KB6+gu`7UXD_2BEhZUX>b}n6PEP02L!R(`ZA^wvM2(?zG{$4JtfYqQVM@Bm~0d zZ+d7`j#By9=ZRhTx=3e&em+@SV29c=-e$NAh&I~V0#xH$Fh4aCYtqG9mQHZ8Ai0xO z*%cpXT8vY`MMXX@(tZ%W4T@}TG&F4(^m_Gg)BwDfZkXEFgn1}g#RN6-_bi&4uJdF4 zu|28zM2!f%bW}Jmb!JO38^k{g4tC&R|1)KtEM=nb-^JuR_;)R2SPf zlk9l@%=6Sq2C{i_AreOsfC^)6GbUB1bTLZvmsFF?1p}W*#gfG)Z3Te_qB-a$P??J~ zcayF zU!7LQFf7?@{MzAJzirQ`dVPOpr-zaI0&PO~6ZW`pvy?`?+R%{CrPifVU$Fa!Q5aOU zFT`a3!mC_0aH$W|ny|JP2V-(Paoo=LD&qBt_bJ+oeNViU|KJbp8%!&t`#E5o_wLH2 zydT|lKmEhpzD62!dIu2a7ID~Km@{p-K&A?lL|_cIW#{*foTO3U+Fh;z^;Uzm2zm*V z_uc8Zamer1xq%SK`RyZL3Dvy7B>2yv}Ls{b|F8cs6r)DqGb@8r9$ZJPf#GiKJ#PMY0fv%0zNK`kXXCAzAty^z8^Q zErPkbbJ;&1_G}D(tFecpqNRQ&nxLJlse2cLr}Sgx$^@+&-`@^4u2kD+xLb>9ncOCJ zUwFMfZux;M$nwpy{EU4)AoD=^3F9M_+B(V$Ti$Fmg2=TGQfz5`W@0~ARUrL(H{VQf zZqWm{mPXNJnbNM}-Of9$v!{-ne2~AJf-HqbLWB1(7vEW8jh@rh9TJ#*o_}c}YAZpE zV~C1In}mHA0wNE0G1>gt7!z^2kRYzmF5aR37W0>v^5$_`yur+j_q8Py6+osh9iuQJ zWBWC)zrTiZB$|7mKPg5~cgQzKjVr}>E)!p&hEhTv>d3 zP%9Rs1G!4;gsHDhvG`wi(+q0st7O?SPb}W9bbTdZ|uXg2MjwZR_v-_!59*?8z1dOl)0`|p9Upxf^r|$SG#N4c8wdyv=R-)Kw z54N6A-~Uosp}|c(GrO?9*+lNKLwm2#vb+96DVIo`5(Ix}Way)U$XLoFv^S{`Ycm}=QJwOi`ySWGex!=jAXDhC0@-K<`8W44IL$kB(Z*k+7Zw6F|Gycwvp)W#eet?M&GJg+B^ z3B|D^tVG{8vG^gK+DV^hIC${irB5Prr8tMer)gHDOX%Y$f92mt?h&G^)h)v8?CeAw z*Y0pDJw0@*{Wn2E{yTZBPBfsy#M+qZeecL7Hh*o3WFgeTdKivGU!DF8vC#wH|38kM z!!4Z^|IRPq6G<35!Bkzbaj<`YQTVjti(7ecJNB0l{E*sWO&0atlR-eG%hM2!D$qjW z%zQE3J`uej?n=-RhrSEC9O$bK06_rMy+jkz69WEH8U}CpDF6fMSQ&w7;UEkERV+HF z6Ra6PkBd}HB_I;Rntfa|hTd8*?4%`g74Y}n187a2_{&+6JL9;xba$BBbXvQ9{Grl$ zm8!@NX05XR=}fUGJ-XL`d0B?iGivYboab)eVbAP89Z?$?@S*_Z2 z{ppl1qqa^{L%W>G-o%#dHL9S;NExoS58ID-L7Zb)xVJUO~|)7;-Z_7#8(D zT9&SST3Y>!Y~P0sn!Bv(qZowPFrdqkFw%FXa!ZY2Hu^)VYHlDccIQ{IGJ98Wc1B?w zgz0pRiH?g_b&MNW<@?#Sd2&(}&LC+;dk-KoGOo|Zy=FcMX|>UX2TN|I{}=c~W->{N zNP_|w^v0Q+tgE0ZZQvE|>qfjk*ysL-xV{lSHfNHMOJ0APyskl+ZZCJ;kuii|XhPZ60}fQY(Q%wU zp}x@OsjzV0_Ma4QBVIwlfuX;YF5xO=~jus|OJc)8V zl?{#j1h%Dsjt$8-2jDG1rwlgz6DB@k&J;5G+q0Sxug_x^FmamNsnpbfx;mA43mx3_ zz0pcofE@pK_lmPKn7k2<&7BcOlp0S699$N?K=I<6*9>{ulRWC%*=6SC7Yxhe^v+Q# z(M3}86h`gk!tcpI%+h~-oZ|gf#eGad(iG^y*ynN(a!YD|>&OGJZ{GoSJec;xJk!d; zK?d=%H7D8<4IThcnDP>}FU)|$~D7rO1 zJtxxwK;2R9Yq{cys(_iSmZ!gN`9Ced7lmNT1&hs`8gFZ_78l+D=PwNukL&!Gil%;7 zwbecsN?z%m@&G8xOUn$PL~Ng(p`B5h0YuB%&tZB1rYbjUAt`$8j>N>{na8#~X9K?W z6@>i*xj}X2ED^74g)`{NtMW+4L`p*VJMm97_#XCLx*VQ2M}Pw?mU&eSQR~GTD+kw- zn(Nlzft0%E8B?Ff)=56@h12gPOtN^RUi()>i7t>%6E-spdy3Z}w%ljt0~L6(EYJQt zAPkWnbS>#$yuKZWO(McMcQN=iaX{p><{ffEeJTk6^nz!S#}W-*&^^ULq@(X<_&{>V z9ROLPrcnU-h`dujqbJPV8+t%W!Axl5J=Q169^EHGnhyn}SZ_V8Smvw^0lL5*OVx8lF>RHBjT{I09GYZG-!Ep-@c`!bwCUmgCO zmYMO@%!1$0KhuhKpOBo2{C>hPvm87$dJ^lQJh4W)G`~JKRWIXhBt_z}C?&GIypMmY z&tnsNMX`i6r5(4gXB$T!0!DB^9+-34@RbUOuw;TJ#u206*{_^_)F?ybRsxlx?{gr? zDzJZc+b(nq2g>P%tsB7m6N7yM1r{q35};ubjEHxg``6foSfS;LFH_Lz zFP=un#n%p&VHsP+JU=aK85kMmsU73GrjLcdKUkdouoPP`p0rGTF|p<8Z1m($htOYZ zxG8NkJrKo+=S?4fEw&m^R<3GqK*CT`Z@0ZFMXifT+d5DHwR%spwHIQ;u*XzXy`ALr0$rRyRvFxxzcZ@t#Z{G{>) z;ms}YKldXVWgw)iSL-I%;7{ev;LYW<=e{H_w=GeZh-|x|1!x$+`R3BbFQH^|x3l(! z;F&V5l!0Nc_u?R&I>%5tH1xo{D}WTFf|=Cyk;KnWjqg@qM|Y35NVQ_NzvchlZ|Z#I zwWnbx0G2p7&>iVO*v>;c|R}gj#~@)xH?Ay ztq^)Q*jAjdoL@P4p)cOY)tlC*fdCqZmAkHQW4a19aptww+&Q|~kqjJK?Sumn8^BaM zdG-GaW0ILXv|`R4#-dSP75NKw&lZ&Bc&t@G}}q zPG0XDZrZP2|Ks0%aT3^vGfzAn^I$WU_Vkkgu^9-d&%ep@+M?Z|(ZuN35p!AX;wk#N z)Ve7y6J32iieWulfs8HniP0}%lK_y<2gN$Sf3*bimH7L90`jyf!S~@mZ!Zp=8gs7r znm278`iy}_B$vxugD$EI-wO0xjy3p5bd(9`e96s?Cm489@U%+OyJfdn z-+MCTboOJLI#Cc)yQ;;1w+OUl$?h_s1FO@%K&}M+g~7wF|7o7M5~K8F|?RHc6+#ubFF+rhB8cM7=>xlh@G0Ofl-84bp!#~B*4QK9EBUUklVvR z+nZ<0l?7m3^piJR#{!pXeKk8xn4e6XFr0f-K2DR-lT(1$?hh)i-lRv+7myOF($>yERE)lUtY zB%C<9ACUXvK}{2W=Pkt;`)@$r4u~wNef$t!ogVQxrJ{)_TUJ>);TX;|vno&T7fcuK zVnz&TQ;^dvU0;{Y44xY;|Lx;vICE34{7~7f6J&m=5XoY=cIHEc@3cQ6TX=<79P;zm zhA+)+Ua`RsI$rty!m6t6&$TjO1P_D;R#>l#f)1S^oxDUFglCQIwO&y@J$K=#e90O4*=YiVO8AsQL&f0wawf> zY!X_yf+tC!f&pq8fIf!`u=+5wcam+M+m({ z(67%oYnw?OZj-ZLPW>Qph^4NF6vy|fxSk41)wt38Mql|un6f8MaIu5erb~ITI%odg zg^Tn@&Zv-t^!_*Egc;au+BBHbXFNBCaP|9+pLK#-V!!kQx*aC)80vI!#i`s+`cnI@ z%7pWLksEvDM=q|Kyw|Z`Q@`{B9V{#{Qo2!DOE4I!s2dkzF5drw@`ElnV%u`*@F6wf z@3-I`!P>ADc<5)WE!`~L1RL+wJ)Y779wO=uz5n%D)7}D|PhtLnqXSmU zt&1Ne6#&_@O5dO^pCnJJ{T9ilKK;MF@(~~9c8oN12br`Y~h~^=#JiKx~{%RB|+EU!aY4-JG4%N`+oEyI-uT@Bc`iFXx!Mf_9@xBf;fc)gnJL@o1Fh z=HHep&G_cH2rS%Ob`|dl-0Lze-F#2f4U)LcF*3`$JTy|i;p;ALBsWMnA}-os$lf(I zMFr^7M5_N}AVB@M*%G|b5~GyFCwK7ELwc3}Ch+Gg^kx%Ji?NOU^MkJ74Ilg@e`vz4 ztcV)Kd9fkUK_(8Ycuqp=OzJ0vCXMX@i-*uXGc)Z!k2)G49(`?5zK37te$u;l#Kw6A zIHU|JMOW9)CcKpt0Wyt|wC|%WE3&l2r?Qa!kc9Rzd0|x)1@DjyFW=+Cf<9NKmbQtb z#NC;hk>d9OQZ<}gCnLr3_lk0{E$(6tVjRqpe2#tfE^}{Cub%rgj&a-z4%!Zoqvj-G zMEUssj`)b>*eKKDcHP|;OI~-KRCy5fS#wLF{F06pwM6ygc!O~@z(DIOV-oGpO7dLc z$8Sc^O7q0mGOKwB-TRI|G0}em)iB*>l20eAbN5f~>?N@<^`vvQOFJ8-h30+gCyd+7 znRPr76Dgsk25S1t%iNm}z_^=)l;rfH$aoGoSg(0DAQ8*QtwmOYJ0iY=n z?H6_WP#U{7FC@SBj=_D7}FJe9rHkWk%?C?=#6>uMkW# z;CBG@<)nbn;SkGC)6$JS=;{=2-S+Pj?3e8j15gU{J)nS1N-6g1J%nNl(!LE&eoiFR zWP)@71|kB~vDJ-@!ka8Fx)-~jTx33(lOt!N$5)3O7;YRzZOV{we+`-P0Qi@!M<g#n1 zDoO3kgTI~F)T3!P@MRZ;R4=GI@JE;~8UwBZ{a^=fpp_caWpn=fPzbsAD9S&vlEUd6 z9(dpvuw=WcnO5tf{oyE; zZ*vW3*+O;>dOh(F)c7EOqQo^EX}wi+5xwFEnBEA`i^M_rodw^S;pCaW^%4g|-}?71 z#_l&5&A5mRYY{YxKh!t)X7LeGZ1J_0Nvmmgw#K+0A-zCIFNh2l?w)7oUK0z zQnX88-e+fdM5w4DOLT|(Wc`akPK1{XEuPFb(1ik0bGgo<{5~U8z#AFseEk2&r)e_q zSH;PCH&n4VBa2y{eJ%xfnQA-DZ2Z+uxL!!ghf$Ln8Y!iQp3N;gbN0+>XnymcE#^^v zw_im={`*^QK%uVI`ey#>yB&I=zW4=K`J}2kWe+26WWa!gc`F$oGn4{!)O07^dK&EQ z?ybN|kqG*DHT+GnJaaBupeN^+VK9rc6xisR`18H83nU} z7;Lp^wWaA-COn(!uk5>CJ2^vgy9=A?_6|44u9<=3^1IR5n{}T?Z`WnLK_Xn}S1v$T zczz#6xA_VVscLjc&s*mQTU!}6;~4@<(Mpu}!y=Xv8o8`rb;(vz>h*fq!^i%MkH*8r zraDIA6W~{!a1TCMtr|DP-%_l*cuG&^H&Q{^RkW~Cp4g*yn7_*-%%^?jmbm&(Yw*k_R6FIKC^YghnrSe3lz;LRBU{NX80hn*B zgKvmuV&5@2tB><=Mq6*b;=tGmABG!2R6)C_I>L(6V`dgDW^q;hMWhj zsXA6Rp5L0x+z!@%wH~q%V2UQu+q83<_6Z>Jp}ktu;Yoe zvvh$)LmSTV|7gzs2r#O(zhGZl{EZPz>cOK&*F-)f!g>MYvAu2=oOTc+pGwB;Yi)Dm4MbgRe0uD#yZT+EvjNwLT;4tQe~ zX5NnzIUV_d82Jk->JHz6v|<66xZ^MI5+5FfrEud{aJt?VAxU`wWL-cH%-a0}izc>* zDL&Jt6i{KK<{#CjGHKFHjdBKXF@y|DvG7*D?h!h_C+6hY=Iac)zaOFgDE<3QUIp*8cl76 zcb`KlcHt+F<0~;8+P!I^2SP^*6IT5DC(#Ruy(()(Ujx0p=bR0-*PLl20~*hbtr{&| zTK)g4qB^b|xY!?vxTssC&fe^Vs?UdecXmBh(OLmBleZ8IBk`Ag}KA~{mSRA4n#;Qc# zQfg++<;~P?&Sprn($dNvmBlc7ni>BvY2(o*t<8~R zqo&S*>g*54x{*zvzo$&^&8wMqJM0oIZM?+J@MZIwhzal^72ghVb0F54FId9L$R7=7 zeQ_}M21p`XW8qmw*693AFC)H zBOCx5VcT!pS8Pq$*=VCkj}UDAcP~1HdYw2FFdWrJtWC!!`qfy!2hoikw6C$V1HT&v zcy{RVj+j`zcbLg`zsVe!fc2+|gM-6t*e8RJ2m6nJKu>4RwSm;X@}T&W+A{M83K$fc z#~-;(C2!wd*!L{@!;ELI5l0M4ftp!4(-Jx_H$cELG_Uqyl%u{j4ZP(-3Rj@;a#L68hm8BbYjDf#q=nz| zA3>vz?Ym{*qEIG|A6#*fks?*6I||f|&)(Hv*#9@M8ZtO{_t!hd)NF4$v;@<|gK%Le zd2b@`;=>3%x~|?DFMgxyV+p)wqGxC}3CT+NKDJdBx6!LtFr;`)h$IUe33TkSZ}%c3 z@W$ zz}wtpt+E&S@9WNU_jpJ(Qx+jszbNQhUFwi$-|~CD-yakar9dfYRd|>tG^CY2bifW%@%T4A^=UrYk zKJ~4r|6Q7d*&fq%-0E=Tk+(f=!;EE}>%7OalF-!DPj4k2%8n6S`OaAXSS_aP`3~ih zuO<)@MNAJ`B6%b`wYbkm-P&6Vh<~ANA5t8z|HE{UE|5Ht6Fi8SJ9!3&;1iBn zJ|0QBHd-~Rri&WP^-7c)%!Bu&B}gFoz#ZPzI%<@xKr0nI)fEukWl=vG`S~Ek zMoawe=L~9ibUt2z)=7Hu$z#@}E-XdVDtO1o6blye_PLv84At=h3>J@mRkdZ_5jSm* zwQxa{i4@QFKnO5X>zq)VuOx{Ai`qFb`V)gM4f>%>Fg>C~-Z*QCM20}I|6Sw18zACt z|BCW=U)w7Vg<;i{9+kPaAX0j~$N$?InGxeN@ju&+z*XhcB)*-$KigvP=kViz8U6`J zQqM(}>%_nU^T0|u+5fI9GE#!2MMS=i*&rqB!e@kPV0EE@;{2q_1yUQ$T5591`RB*j z?kP8hgK<2YXoXr4vyUR}Z9)^@ZpnE&zstS$^LEb1ygw`f9mS-_^$=CV}=A=C^2U zNtl~3c8aiHoVKEB{GA>Ap`-Cfdx3Ift4xpazvbc0JR-Mqu|`@bLkf^g29nS1WIu5h@Ei;iUCvp6uI zDLR6ND$4l_&!_o8vxHPUWIP;#Obf`9oEsh0ehSl-)Q#hNqW%l-7nwr9160&S`5x&M zNnLF=*Tf>J$FDGx7>CEdUQz#2`15PlQrt(LJx%>)(rdat*(jVo5lvb*`jG@ zWGtryURP8Y(8p%ZZlOyy+>8_LCdD7DeXiAd=GV0m<}-c^7B9c7j!r^2q``+X-G!`` z^T!ZY)#k^2LIso@VmeV;+Q+bQ^#@bj;czn-3(d8|mO>}rLZ_GHNJvCJKL=7Q0YVQ$ zsiMYM20DT(u>b_&w2tUki6XApe@##h63hn_xoOl`HpYn>FCIc@Wg|7nu)O{(y=PJ? zGFoY0LmvpXpq>p^JwWR>X$p@CdyoC6w6cjK9*V0!`9>|Xf+@PxdjWID`_G_CjtLdt@e*B>S7(cLH(*%A!09-3%DQ|KSaqDqgCl290HY5a7M z(yAe^qA##CqaN5`CBs%0Pb*Z1E(s2|6ZFWuh!R-&WkJzdTZl{vn#NVq7M)Z6aq0iJ zRk_>C_P71)OtLCLlHmu#`Zv4oel^at`!65W@iN%|yyMGVxDU?(fGIFgeruabE|wXD z%U&%~qEzxuh$PbD18Sp)r6e|@U2}c&<+84j$uH^tB%ZmK=Bxr z78Z}`GuAt;9v_d1s=iKcASze<6~GWRH0LK2rYg8^-V!c4Vv6F8Bp)0LX5f1S(9JJO zHi(^+(z+j-AArswb2tLiUqrH9|am6>5H zOxaGVKfh&rLnHH?FFio-UH`8o@|iuqpB|{M>IHkd%pRjQDOx#VExMu{_~H$mINW?R z+Gk1lMl_-r74@{Fu8vk`B5snrUCV4(oyW!r=eb>6-)X{AK}AvV_H4u(ZUNrl=hQw! z-9$W6XJbZWoDO|BxN)$vO1*D+ki)_nNJ*p9pjiD#VBcftfUx?1ksHA-LzDm`c`jRh z4ujKFVvk5d((_(^IRoa^=|^j)6eVgR{9ms=lstn9vksiYe)inhpI$cCIG=q!Mh-td z9U|KAfy>2wd5QU_fm|jZKvc|wE62E1vxLh4N z7ZV!W)y=!9;C{Ax5x4hGZgOQhB^uF)`4uqV^-`i8o(3TZ(M@KsI=R5eY}L2VWt4JI zxj}WuQ5e%W1Y;sAW^sQAh`q}=R1X7v!DUOa5I%i@MSJ*RTz*6yaF@=sqjhI8g?C=~t!hlOk!3v~(>+T9e%p1yX+(DxH(IB{ z+b!-Wy{t5PMo6FsgY=X_#6&oa*Cq{@z>DEP?>Y^%N3wXMRLJ7L6#?tqw@WBzCB@_- zUP-M!csJ67U}Qi*BzkrFYPPaT1`;)8Suvow!*}HJf(omGIc%;rsb@%~&xXG~R3H9b zkb~-EUKiq2Vop(EVHs-GG__LGR@ZPOP^Q!@fBv1UGoIK^{0~u=3W)gM0sGD2%a@P| z)`%CG>rw&R+b~QCofnNwJZUi!X{c~lL8nX6zH-qPM&|IMMvygM#A6|i92`>CVB=VQ z9B^P{G}V0{R(rHcBuGr*Xb(vwnDgmKkGViVLp=26hSB`Ub0yr*E5@Sl7?(h4lT~@% z!p{!HWY(kVlZJaKaS0%#6&ifyaXV(|(@{yy zB5#AC&;NffK=SX|N5Zn^wc4c8+I1e8T{wYzIbtiNkQ1kS;YqP2vpUHWl?m{n{C*l> zA6gUsL7wY$@F8J3bxSAn=&DuF!NJW(sk~o1gT(7AJRL{6rmBlX9v$6BEnFMFpYTme zOgtghkx%&yn69k>1XVNCa$aEz<~c4SbV%i#j$^?{Y^C~`NanBF2#(<3$Hm1=KR!eP z3soLmQ?C)$$gb9)-9_^0*V$>4w|le%7K45>N>hdfwB~khF-d!_m~SCP!EoQ*t!l!i zI>wMC#=TR#qHd_el*o&xF_D6l24$Q|kGLVqjbmqq^Od#}TB-YO_sY_Mn zAJKG75AMc#Hz`$5gN{;TP4a~H)j4`ZPbCqVp!>E(Lb_;UPM4!^N-8NU^!blwLHPzS zXO@QE8q1C@6%>odkKp3B_k_UYP$BwqI>e}`DP)OYlsJB`KM}E)S7HCLT$|R+a^m<2 zZepu@rnf-((qop?6jpuIiEpcdq$dW^Gcy#n6Oj4jf|3%viEn258oD4>>gutrrFoAq zACepwQ+un@vNGvCKQ``>c*ZIs)RE0EC0x`p#^8Px2^|2+=EUI*Jv{?+$A$=G?Q$S`C}PY_MVytS&Na30p;;8+qi*?u(TN+9*%tNj*j zPgWJQfQ=1BEVzgd8@t+^$Mf*zy|6uThjL}B+W7cMOhTh~By#o@L5_0i4~aZ$Q+?8M zGp(~MaM1&@2osw#Tf;NHy{kGbGNo~xD`x<-5-L=w#~=& zMcu$6;@;1+!a=RQFL+0S`l&~)KaDYz(5-lSd zP;QgN_Ka|wx?#Zthq3*Lvqh0VwHxoPn{`HTuuux=CPBGM>CdPSlcT@o23c*oaGvO= zP+>ELa+hp+d*rAsd7ih1LIYRRk|HB9;WG84tp~juPA=;_=JKipO6#2-&ej;52e7V0 zuor!Lus7Elj1R<}16!jHO0_YJ1t_s+gRi`nf*t8xkM-t!%|G>Y+Yj9Q)aVNU{d3Nm+N@(Te8Sj5HKz8@n+Q-~SxmcbeSwRh%8fF$zBvh2-WJZ~0mc77q78G9Y^N z#S=IQaBuUp{ZPoO$C1>F$*-u0)fNtago7LFxOY8(6N{tuJtZwYXXUn~>9@@@Be>j@ zwrR+xe8?EzQUJh^@_a-@suG4M0aoC5YI4)R1Ll7OzVo=dt# zx1;j1Z^PN8s4B~A&yYM_1PUG0DotNA3ZP{n#(rr2SX51&rAGrTpsY;cu#;Jq5f>xd zubO)7RtBY)(v7e{{N=_TRKIyf_QpV>WXLspd_jS9A1|Y9Mex(i#lsCUvQk|(dpVP(c=8lJ~^w$PmHJ3 zb)Lw^ccwW#+c-vw($aU7j1udh!DJI$To^+(9}SMEXjCNs$o2z!__hmY$(wUhuMJ$K zfk^E#()sxo=yMW%y)^k0R4^t-5lep`IVWI#+NgFO4@l`&Fu{@o-xH+6lYUy!4au}v zpLz7FxcXD>pD?+ZC}RTa%&m{i{2%7YXy>>5`j=~{lO9giE|gs9%-+8&`5(h=41bfq zza#j+52~!bJaGNdc|dG_*;a<6*rK%m8@yIX;x5Jd?{rH+9%03zAA@V>Pm4>6zvxDg zH%63^gI+gL0%psuu_ug!bSYGln)pjsw~C`213U^tw&;)JPt}Rh^rM&SHP5H>Hq|SL zxxUJN^CB78f%m)e^6?B-PHb`~5Y4r^MjF*->OlkazY=(Gl;vxd>W)G1WN~>FmzMrS zM|x0Z`xrj0oRVpW3;$$L6@OoK&id2o&-PD>Z(4t6x2wp73vyq9>SpcglAsBUM@MI@ zhP2S=X_H6y9~lmL~p zB4ol!`+3Z_t{>SC79TAe{4RSqXz=*!>+bS1v&|+7YRZ+h8RDL+!{g=L-Yc2?YILP-%RQ@1G4|{y!Q0m9WiuOv>ine zLB#6tlwG(;P*JR^lJ*!PdffD`^JYR=W^KjukKR6=oEEb*?J}s%eA!I%-$5iR8IJ~G zrAt-@A_Wj_fp}DVk}h$7{X9I8nu4O(fYovLF9}dbj3y7oD?l8BqsvMmnlKz=|MM@o zo6FN~^}oXKL65s0Gp@P5ly1jPB-gL0IO1SivDjjG>D2Q<>tG4Jq)6AnZlNHD^fzg; zV{ctNlH$h-Kr`CL#=Aa?Cd}&x6W;(m=D>)GHsG7WY~!zisvrh-wdS(NuXbQC?8lE~ zb>blS5x6%@^eYSjCq_t*~sA^@;JYc$&<#$hL zjiCO_07jO!{`DtBm4w4rz8FOLxQoNpvP|E924|gS#f(wQCB+0DP27Ncq5=~^hvXz* zLMgEq@5V8j=$p5*{0kV|CHY^-<#6=J;?0wN)bJAW&HkwTo8)QbDkV<+0R_EJdU{#X zlNKS5Xtc;`1UqEvXxDYSOYA9Q6RtLbCQ{}?g`-OjYkSGg%Nvp7(Nx-+* z41ao3m;dFwRqe;4*vS{cl~GZ}R?%jR%Yv!}5P0W}z2c1nt$R){{X>+5{0Am&HJpc( z6^*Kfbte92WsrxqZP4xlD(~ax>_uI5GsW{v({D@8wCLRPxfW~2DW7J6L)TQ@}vEp2DnWc}d< z^IWsz(-ERVUSm2KBmuU04^c&l3bz4Z@&iDd?64VogO)SOl+OR zxH=(+iBi5mG&BfdxGNe8J-Ry_UOHLU#9?m69azaIA4if7EH@^zry=`8g%d!p2spUT z*w|^VN5B49?){`%3kz(|qjnczsW}KcH%``R26wNS;a$vC(yNwva>i`_QxL4gLl;CK z`ly^nP@!TL^CK(NH&XTdGdoHVWmXCR%5 ze@3BJ&mKuC0uU|2R6SXs>qVgu#x%i_AvriYb}Se{j6c<`-hM`n6)d~4VHpo`Fpf+s zo>&wgoH^QTxH!?-D5m*bCLF?Mc7dB?na9erpoQBZH`5L9AOW|2E}b5FY@obau2l27 zz=%x?np_Ks20hayO__T>Iq5U{itC3lMMo2H5Fx1k)U1-fJ?(`NseTG8apd6e5Vwd( z=!aQ!NeO>{aq#m%#Rv$m+@X;Z={UZ=EtvFaIqBVwS){v@dsSY@CU_x|e|O(bX;tO9 zUtKLd72mrLDyypL@Qj~<5_>Wx%>*Bk>Tk$3(!;Zfl2Kj??i7CO0uETmO!^DdZs&Fa zs2a(EMRa?CW&OdNv#9eaalk&nzHek&>%ZM8_U_5?-+2C(F4+wiHRMJPQ?S1@H9nH0y75(0J+gidjr9I>-7&M zx0Du4U2Z)kUrM*kDQe~$xe=K{8e`Akaq*F;LB`Oqw*v#X1&+k8cP!s$^xxgS^6$RB?g$}Vzix28-oo>fXwY1z zy}QV5@IAvVZ)y^FL_n}BOWe1MHJj8O3;PxV#r@;`fkZ92n<}|VZ+^ID*H<&Hv9qz$ z+tq&prim|Ha=)acMagug>ucY(8qle`#N@R8I^qZl$Wt-%n)vU>PQ}bZZ;B4M4k$|?s9c|9mw=_yug%cU6 zgdPA740qJlUk1*&myJMy?Y<|JvT2!?-uyTxa_6dx?@91^v72wp&I%Dc_Nc<|&KpQ~ zi6Y^;Rr$|;xlxw`kgomphRVCm4Tt%m$D;sg6YGC2{kqvDzU?a1f2{>i?6dCIUF&y_ zqNJPG)tl$-Xa2d%d3ni)P7&X@Uh;M>-gJsP9sL2rcWX8F%^gaduUB^67gq)BTZ64K zW^awaoc=~S@AY=>^RRdxVdluy1XnPRvf6cA1Cs;3se4~2qUxe`y0CG`Ab{ZKw20J(*#I>Dm#rKKo~V7S3H} zl`k*;2)-ky7V#qP#H3v>h)=Fss9~dtotT=9%tMfeNN$Gc6A;YOM=S}WQb6v;?jF%Z z&g8O0P7yWT61_GH%)PL=Z3(}Me6KrCna#j$$PsBWf*`H)6@L^|L{I&w$xidD2u_aR zz|0X)*{z^wpywL34Ffqb_mHrZ$5!J)m{7GxPOL@7Z)+X4WVYf4whfnVMa>R55Uay5 zUwtmah%iEj$qKlLj!3-HF|9;L3qwlTwor*ecu87Q=s-kf4oOdr>NmEmL-7Ew>S0M1%r;OBfWRhSb{(;e7blciko>R|~2P@6wFA~KsSuIs_H+sGs8r>W!1>uYOFQbxi zwmu-dKJmZ2s}~ScePd6{9P@PZa!Np*@)=|8-E#DFT^k;R)kr&TI2t1-&4RksWWeJ^#hjXtI!F`JK)`du}{1!t5oM%)#O z5FmNw$fs2EJ5~YaU7PuQNy4H012J znLpq5cf#cn1WG~JRpcQt#Rw2i;19LpF1qhtzPet#tBgT3MlH6*zBeZVaa5T#81{w2 zFzFq@Pq;=i%($;2bpK@gO8#dA(=Vgf*TDNv&vm91g7mE9shzHf6lQsz*5A29H@V2U zmmvrYZI#rjbuKR9p1x`_a+;T)?B35cUXZ+MJC6$Pet6%i0|^NY+1^C}esupj*1D6` zvkg8K>?v1#simuhvNZb-gQZJXOM)D&m|O^^bY)z|V#el=nx%5Dgk@hbMdOpED`Tk% z>P=EgVYaqE%D3<_*=*+rdAkJ^DrfX#x(%%Akx6K>l z)$Fr{M{mvFgE_y&TfiB7B)$|lkgCr?inPTmBCxm4z~D1gos#A2SJGxDx!rzq8?07g zv<&e5V0sMy9K=1vWnT=Y7eXtod3CkbbzF%KtvbeM5y*U z*(Rm*{+;4j0H)Wt*Lld`_iIYP?!f={%^l5bEi06h=s{yS5Kx0PW({ic0WbGWtPCG1p@*uWugo-ftP{I`<7ifEM8G?QLKSVN0eg(oEMZtxtgM z2y)~s;!I0xK(krL(C5Qkuk}0J&>QKe_kz2sY@rB~=S$`c?MkT} zheK1ZExg`^_@8h3zjJdVRr3Ief!Ds<&t6~kurIH={aOor)O^-%A$GgNt2_X7-3KBS z{*jx*xtp8z*BMi-$R|B=@?Ugqu2-T30K!Izv-wva5eO0g`5cVNH~U`zX7-1oClO^% z8^2j^;kh?aWAOx^9ss#;_lW-q28br_2k$xH&Ld(t%44KN&c1!*ctP~gh?nV&Hi*S!3xf^t0Z0e=94uHAGtuIo(*Rn!%EZ>* zHt6&Kdo;j{XqNu`-wR+qK(k+e%pV>qP={xxN+x@X)Cs`!!RkiMh^Jc(#FX$5i(n{7 zg@AlT3JHh+bXSI~Tl(RtoS_Nz6|sqyoIjfRy0vRQt~M@{UA3Qj&HaQbu4ARFE?uFd zSaFMst9^fM=4KazCw`Ged%N`L7!St*v1d#TUv4@#T-ll?0Kg|Vx0o}r^N$0x zzCjl0Uov{O4!Ag5jv0W6lwEf!uv~-|?LX?{F!D-X};d+gYM-IG?i23~X z%rEweh^QHvDFBXNau;C562BaaP0RB71-za8D=Q4QWrG_oOr3zr1W+bO=3J-Ez4_$D z@7n!F0B|BFFRxIX@V=3>pY!oygXrSj&A#KtpcNq!9VkMZPfkUEz#97%Cb*quD*^!W zZQRi&P=`I^R*6&-zsfojKmQ24eWD`;B8x-dbJS!ZX#i?EAbc0UD99aMTpSUy0Spw*JkdU<(7fePV&}=*xS2I3C9lnUe|9#=G>HOtGY+#mZWJD?PgiDD%)q> z;&uK0{{8TBuBpTqrG_NFnRcbVCLKh7Pv^hX&PWT>+j#TZZeS0$2vrC-GvZ-hJ^$}! zH3Wd~=^xwM31XtMA-Vg-+Ry{!`dI^zQzR<}doak|8!hwI*^*g_Ba#M8_#K^*jT?2x zeoNnfhhd|g93TYrbq`uTb64~KfhB?VmRjQy1NOWxpiwXo=Q)&tic{ zZV^avDb@^r`k=)IJp%tpI0biO6$M5}th#=NROJAKkQ*yy8W|T0HwEXdsQ6WZI8!m= zdeZ;ernUa^nQL`SS?To}-d)^%91V-298c02Uu_8B~u#=>Vs z3_PZZ5;aGc9ZdflfxBR^a)SVf2UOC-!lJDteOf`s4her;E4x09VX2T0=6g$udV=iV z8(-{@9CX3R;-HbO_*I-(YUWhLJ3G4&yhZ;ydUAwG%Tl`}pNe#fMMO7Yw<)>vAM4Cm zArjekBJj~Q5ZD{&W?L79ed~pcd&b~ANl<(b9ab~>)8Wc^O%&ei4Tx~fV zH{b@kr`{O1sk*xhaJC&2RzWI6=^vT&91L*2mYR6cdj0Rx%!#d-sxT3F9Du0|m!8I$ zYPIcdh0Lho^5H5`;_o4ccnh5g4`g>_R1}Vx5Pzw1#^TNVJ`ay@N86qME!tVnKG@Iw zFUb7c>)LLF!3RH=n`RCS+Tz!N{=sEz*N3m{f7I7205C~M@X_GVNWnG$g*j~u@`}1I z10t0#@Q|>h%fn+FuFttw!qM%oFQ>r0v?dp7*Sj3uGyae2LKn7rQ=Co4J)6dSa--`0 z-puh@kDZ_Kl7DN7`Q+*y`qskJx|S9?*qvNnEPQuW6tPrz7iIWr4!N<8H0z_+8%jS7yQG;mxpUKo?9i$xPTIr*<| zlVCnl@a(0+zCoGUWq-g=^kz<%XQY$R8F#2q}@3jZVUzAkM5i7kS<9P=eN zci-%OTd3L0wHn}iIWNyR8{M80`CaWb5l6`SZJyX8|FTy&+|CJ!wH!XgPl*Be^osH~ zoAYO!T5PF(g%kGzf)3}+5$DxE&uNrZKo56gc>$Gh0%R(5WpOc zGf_n$!qeY&5!tpjyXX_WJwsuaVbTy0sARanBmXmL=%LImH{og9L&N-F?#mbpBu9-6 zV=}IDVUe>*Z*7jXP0g$fV;?7=Jcx_rqUIhjD zyDz$5+0(mX%osrhPTa@zLtvU#n7o-PJvGLo5X=X!rUAvCo}K&OBv#QnR(A(#`3HhX zI1I7^klP`lp_Tt!!HZpWx92_^)Q_PNAF3p>RR3;K*pFr|+i7rWI6n0i(T4$;`ubpK zI-jk#DhN=Wqz>BUX%E4v0+Mx$emAbISJ{ip7n?b&{bVL{Us!mM9@Guo8!^$*jyqc# z3%+;03tm?Mez&%s`Wi6ZfpZ4M14iqT@8g>eY)&p-k_><{oF1owOlviF-W-Ru0K1lVz2VXvRYon6s~P5JqG z_pK=0%IzdE`;*Mj{FmH&Kr1PnT2f*kw&x>|>=a+o+N_xOBZ~HXt1oc+>GMF)#+bQrocQ(z~{*+6BC`1Z}qT&3d(Jy|i{gCY3a)_K5ZZG*1bub|!- z^oz)3N&ovF0GoGVQvr_D6f4s|2&21yN{vo0*N%LTai4bUSnIu`@SK@DZ2&Wz1)ZJO z$MWK9gMC`f7pJq=8{g|dv6Z64Dzwm%QdfF)6Y%@|J76A#QdyV zaWs5Id$3+p;b26jpR$cWtZ&6L0>5-3%#sp&AEpqUz1MGkfA|2h|BbJ&k=OD=KwKb= zH0#325pvF0$f4O=Xqt-8_plLY-*q3Lt`y(l8;p?CjGPIF#%%Q>r(qw&FssP_mbBeS z#O>}CHQ9&xxC=yLJA)@cxvoh73n?$>06GV$AOt618TGF_RIY7E_1!8C!t@SJ0@HX8 z(9HE;hYJNeu%=*lt->I(ooB{a1J1v{%Ki!k6+v7D!bxkA&QT!WW=9t5R5-5pTNXAR z7FNGJeiFURX;5Dq&S3a_cdZy5=MxuWW5c%hSx>`ZHyL=RIWN6q=rGA?s2P%#$0z2a z$G>nlrv!J)LTB$!^wAgD-e>!Ob6@X@;0F)#K$P=3DjFvq^3w4vqvE@OyZ~=!<{1j0 z4m;ali{<)VQeb08g1%bdyjf3uv(mf8QdVYaNJ{H>%)L1*)_L!4rle#bjZB1TS<^%RH=an@_;MeOno%|qO?Gt|dp40A>S}bg)=TWGx z1>L?frFi&v#ol@2(Uu91o2mcb3t&7vG@slZe?CvCVgDi4ZScQNNIPbTx_P?KK0c#- zOv3)2sh?d)>Gq?vlyJK(0HxQ~DKhvDgff62F4-%TTlkFljdn6Sfmg@r=c-oN(O zLj{Yh`vIo0%}3hv-~V11E$5X9N0co$@Tcfwkp*aV;3Uxh!S27IEGT>#mUsb4|l~6*=w)w@Of?Kc+QV>m{Et z3Tdjd=i%iI-W3LE{lg24NmjbdaAB0Cq^RoWX(hfYM(!3<2dl6 zbIvP2pB&yHoK^nrw*KSG|CGlMn(X;9c#PKrDKBv z&soTBO@=h`7_&rBa&&E8Wci&Ej~IKzxUhkws7LGPx+VdmLRB3BTll?szNzibdsyrU zCom9=`lS~!y-^Pw;G@K*?B}L`P?VL4X<&KggtDVoF@#d|*(`V?FE%~FKJT%AgIDi* z#jgNtl-&T-*$`!sDOfJmpov09gIC?B)d#6)J;Y*G4MDiqm!nAtCl>2|6(eM>2*u|B zu>IfG%lSd&2+&JVR+jgoDt0zu@}#^j&Eoo?MRV0xV`uEv_7%f^4&MUK_tdQMexLam z+jss`-UA^u4a=}t?4Kl8^I12y*Q$jRYX{c%%{RSes%ne)odpFfWy&DM_WiZA1__9G z0o@#w&FA@E(cCy2gQf?t+3BaeUkFiRpLt4%PS=N(Q20&xwu)G_b|_fhoYg1S;i=Dv z^vT24e7-7{4)|t9&A7HsHy>iU4;`>_j5y?LjEW7uRyWswG)sr@3S}wd-F6(;swIFe z?DgG-fal42YWf?!rMA1n)VWsYM^m1Bh(A?Ou-VbXnvr8vLBV7THzV)^qXIR+jywm4 z-w)v}Z@sdDi=5DXWK*_d$rR3dFN~_c5{t& zgnh(E_E4OkUoKD2e5E}Q{;y9~#s#B2qir@FH@Vxpfn|<~P{3_Ga+jgsEmC!^P1V|Y zVAe}PNrNRr^=^r!laUcU;H{@;c%#0&h7NFkf`fX>72F^b^uD&q;8jQYn(NyYGVqxV zZ=@n=s`4LTDew@+_GD=?F1Q#!QuBD95z5St{=~Baw3imsp%j;C3aBKgvPB61Ma3jr zu@$J-!{aN73Z3TbyG7SKqy|KE!GwGz`DX6RP`j<@n&-r?8(qR%7ih|X6LXb_NHC@| z*fRG6RK1SdWXLjmq})g{#T>l4`}_bf5EkiHIPd$hL|MQXXd+)SXgqp^@kd4Oy=EO0 ztB4S=>+TcqeU`Z?di3sXqDkf!QIC?Ai zMgYML%U#ytvNhJhSd?D(li*^il`2X3SCb5;6bsOt()-RC%K04p3y+Ce(8(pFvH-3T z#TQ^}>)k^`L9;UX>NQj9fFok~De4mVli8m!KiHe6-8{{1f^w48JM8Hf7h9em{(TX* zG>%Osf(%CgC&!(A0ss zM&HbUTQT6vdj(>g1|t)5^E6OY!|GmX{3HaDoI2D|k*9V)@}PcLpuUjyzf}l{bdQPe zjq3|RZUD>>$YlwZ(}8sTNHDlqoVem2Qh@>lNwzxRV^kxp?L<|DcpUDV`2fR6<&+!!awlqy?vwZgGAHAbC?Pxc$y>3(S}) zsHtW6HP0hde++r>=8o|4y9F8XM;6<-Y#wF#pXzJtte;mFfZG%*$Vgkhv%?)Z+uq#< z-yC=}VA7g>yp(uz1hh`^ff4!n3sI%ub-37OS2$7gwc1du0%RILe9ca zNhYQ7{#%--RO`p3iBYkO%$Y-n(W!5k68q@-5c_6Dn)6C*jBKbBdMx2k~&?ZiG? zJ*j*lU0if3ur^m)>KzkIA<=lGnL;7EOmQOX)@@g!FH$~O>Qj&LQ-gG3252lNK*3jX z%cJB*{jAQyB`y%US`R=d;7q+kRC=)oH4 z=zyY0Gb$KRKTBCSWrP~1<46Yvx>$^ZGEEY2aL7ZxeV7H+hg&MU#U{9SD(6x3tJfQk z7O!YVz!6jhbvAcF*NMMaI%HNJ3383>tQYjSZb(`}B7P}4%z6yjn`6+5dnMiHxxQq& zUS3^#Y#dwd&9w}GOCJbBbmt|44WTI!&3DTd7>_VKw#R|ek$6%u85EkmnwlEiZiG!` z&uKAEuSkVf9EfCnM>BZ2{`Ey~cj=cWg2a19TWFb`qXa{??3n8(y0<{*3Zw|4YQWi8>r?9#3?dy7DWWk8-0aM>OVot7JBXOk}%VWHHFG#<*zHE!2J%1s_bDH6 zpOts#7%5UB6Y{$zZ!8B+%=FC6)l^`R0N?0umBhYBTE{1+$d02CaOpJTTc|T);o=${ z`MP+H(dLO5f{R(2n4rk&dVuY4dwZ|gsMO(d4FCRJA=CPhVnV@tI_0LDNISW?3L?_BW0D5&tDLABX8shuLAB4 zj_SgH4!hM|$+p*eeBuik%3d6k@(2wcqM1@{K;0*6B60QH@8suEXy6&OOXNZRB`in zZltrdAZi9lbZM}+uAVWIQ&1MxH34!)gVTu;hyOLVfc;`m)pJnwJ723-EB*8DH)1)L zv#l}4guEH_0RYx9AUFaX#8^qL(@jsbI+yzo<~>L|ex?6U;o*p$`P26nxgP}l#Bku{ zL}!%~V0AZkU#Vw0j$Da8m{%xxkCC&#Je?Qz4z4CK-yR zK++Wps0M!90CqRYa>ExntQRRv)5X@##;HIn+k$i~wtVMc`)K40v<-*%!R!_qhnRDH z2QS;42UcUS`e*kHJf#sZCi`$|J$kV@98nK4OMt(eG(chpF!n`}WK$#+_+lJl zpt{bv=n<$`({V7$RjG%Axw_5STHk-aw68l*!A0z9_$ki(TOZSv_;E{6TyH+Ry%J5! z=-^Yif3I!V*PBt%G#u@JD8jgd2LQh^!sV&sN3<)ch5`z}fIuhml~)X$or zks^|q?msxUTxf7(1O4|vpCwSGJ~fU7Q=9{Q1m%%Xuo*qpmeqg!7nBW@3{KoT*Dye* z(q{xlpLR%Ip2=&k3qrD7@mP~CPcGh(mT;@RXf-++iTQ6DtjV3-v)UxfLdy}1D0HC- zdQws;9pVA|()de{1Ew%J61Vv(Jy5IdzyP@FGs5?kziW9T;LJ=Hv;ODuF2--_yn;6` z2A@Nhrg2yAueGFon@8rFtPg;>L}_4vSg9y^95>hJR;X@&!Nkjrl)hu>_=vN0X|Kf* zqzz)8as;_=?iphRqo#b&vj4qtRdKi5M$e8lX`Ku@ZfswX<=o$t_4JGib%ApZ1zg3h zuV2~IiApvYbRVIC$_%Z~IYFJ>B7N>a-Zo{8L3Yu)MM`R_G{FMFtCFy=$(_5j`5beh zbFy>hF>CIVpFezeEqCv}%!ml9G)ryP2qknI%IlhKd3~b~R|!IZ>8NlIRFL%dy^Hra zig%^by?ssOOv$ae%@HCe=W(%425vb=TN&+Z$3~1B&9R8f!KUiy@%N-TWbbPES5-Wp zEhNRq=T-UzF*heWz28>GU?hdtQriyn5Pbr@j@FR(u_a5TJS&@|?3N1XUSH?R6g;>%K4J&)+vc6+eoMw`?;&&~7cq(|Az?$9 z)HgUjIXOS^^27W~v)byTpKn|~0JWu?+dJD5Syvqk9Sf$!j8QiH@y?pLPz1pPg*X@w zj43WkE0~WGf7x$--}THnHrM+W;I8q}^~IS1EdQ|Wj`oh(kMt@Y^7wAb|H41bijO5( z@m%s3?5iv}I)gmO7oA;pIVuD2Z4?9beNFpz@$PgH)J6Q-{Ox3y?C*9L&o478wofkY zNwCk@0?zYCuIWBiP9uYH%{(R$M#4ukYTH&qJ+rH2FTZmN^z%fIXCjIGoUv_Nz3!+96PN0@TJNGR}^)TX*h6blp7nGIeNzP zB|kQOEFL77!sU%N7yzU0Iu@mZC8(P}B&=jnCZ>2~T zz(u6VRzTI@&Y^2!NE&0&=Jw1a1Jm;brnIWTb7B2Fg12iy6bvzD2H*L2RlSd2espUB zfYoOz8G>K0+VOmVdR5S>M(!#sKMWE+Y&8gg zBzAR~PghIB5cRksO-%|3eAez?U8O2!Wi=*w%@|2%#uJi;BnhFjgzy~4mB7wUTanG@ z4&QZcj$7*bIB6oXC}g{4f2HTV z>!38e(~_Gsf|CfM{sQ#lJK8>)pvIS@@0LQDAC0%Tpf(X_pDQly*piHQzZjh(RA)R5G$%LjaspIMxs!Xqv;Cw|v=q|a(gPI*Lt}->I z9j~?7NK?CuF9*}@U7P2q%2T-FSAxDK4t=xm?ozX>limNU!#$DrLwI76p!3`2KC$83 z=jS=HCmg&BKKb60=3(5gduF*yx%V$~JVs2B@K#ju6ZTs+nmZkvBX9ftSpGUa%%+4w zF7jirj`CULCVxlSD{^9XeR5`tSjx2Y7bga$XF!3%9%fY%ELWq zOgG{Pg$}&W!00m7yhUB4b{ej}qUZ_UZj31_`4l>D&dP4;qJMDr{%qI zyHRw~OaH|h+x^6?f~lqdJ^NTm zXb7JF0tt*smC)8u&}@QFQb$Mo~( z>R?j>{S*xy3gc96oF~$+;(1zGVO4tFZXxzLFP{A>MY!}n)A`UV(2j@dBmV0IO;p^+ z!fziB1*ooly~p{0_#UZtcgR@~wM=gdU+n-JMI?Q@)~xV!zyY`SH7l^L1Fy z-$zk-9<+8nm3r4+M%}Az)*omhyBDKn2<`Nyjt;8HE~~yp<)fSHi!tNoZf5;c%Hj<{hR?rxM`h=ow}R5&rd;V-o88jXEM z+4g7jEWbT5sLaL2e)Ml?ne&ftxSE zSEAhmO6o88zo+K6Pk<}_Sr3PHWzO=1C{96>hq)X-NJr->a%fqR+e9J^mOw1q@X?t; zuw+#jcoQS8J~<1g1Q%isTVt5GfiE3^^Z3Jv5*7KcAm1(jHn6_x4IevQxM_d+WemI@ ztm9VMqqe~P`Eo^2LPI;wqL7aONyOOd>G%PMbu|;_^H1(iG;6^HT-I573#Dg}ac%s6f>`1CX^{Bv)uh*I^eTC-<6XV$ zU$m9#%VnE8>qxtf&6!}tO81kZ!pfyg;Op|? zh=szs(jSPdc}fop8O<=E8!AkgxcxXlQAm)XmY0cFN$7<*D!$o9eat*$7UdITT@p8INKAcx0*UN^WPB>y2$MPx4dptl z`ou^mr^+0|X!})-Tuw#X66jSAsMYAVgwNA=Flp*E$L^i9q}f%1fPp0C5ruWWoJ` z!36kCETl5p>0Sny7f=Z70DRDmCOaAVETA)W{|ca!O11Nh*oYd?##!iEOx5z-pL?7Y zd~#`>5-XfIAIApI%$>M1u+E(dH2hR8T|4%yHDS(!jZC=tsQ0GJzLRXZDkq_m<=T-8 zL_h?PU)3Kl{(ST)3d*BmNl;$$JobKBTw)m~KZXZNDdr813|~LgqpPP_NH9Gc3R(^F zc?~}@%4ea6(ZMvKBF|Ka`qNpS#6h?dAbFPNK53Ey&01N{9l*R$gAwk0FDMPBd{}Lu zfhB$Z%9c@{a4F`r;_PYv!pw-q=*7&kR>%2e%i;M)0uuXA|D6bv&JL4*Z_EfHm#=6e zMaa2+K$$3?;LJ;;fVRD&#eH6Gp=2R15_A1Nqt3(ku@$RXtdAR-LpFtFG`vEbr84Az z&J!zmZmS7wIK4bp2{9|bm$cJpC?TNlNsc47>Vo@2V4m-8OPQAnn9Zm;pz!i8Te-)^*+L2>T5?pGg9 zEAOwG=N()OLo`DgZ zFANjUW2E0%T8{EX^AQNl3dVrNLWeo5f>{_ZJ8n#9BOa9*)TNTE*sTNUPa zfIDJ#K7i>0$SL^zhQUBoYoc?Ai3H&zi3~?o4Qc5~ZQ>VrR)ceM&GsZw3nr`(o3)=m zN1~Xrb)7Bv_aJ9&O`tTfjs)fB-~FPd-a>KS;Qn`Go7!6yLR;Y~)-F@e&00G@c;NjO z)H#R}6W?bDgtZ@(ghp;!sHyw>|In zD8}b{ae&|VY-Fho7-j^1DKE_4hp6s~6T;mh3)W+s4mx)Iaeng2aFub;!?S+Mu z0OxP3w-Dgv0=ssW10!A4OL}3U2PSU7)M$8hwTRUq*I3Us>5n&U zC5A-K+@0>Uxg_X~J=%<2c!~jE}4*=tW z_+`GWKzux}YBCR0&&1fu-!^iLd#)VK!Od0M)`S>c zpk2DZydS-c*c=v6J~*HjRQGKB3*N*TVu(bf=g;ERn$YF(NhkWWNbZxbZ=!C}(i_Pe zHZh+I`>jn9IGw4{{brvF3fQOJ!!ws|dV2pJH#y0clPB8Ar2564wNVFbBWt+vc`sLX z*V9?gJ61NXOy0DXz;?E2?BV8iHXeu0Z>_u{BfVosDrk-g{gUX+#C?+#Ju#F~AYm-Q zbU?*oe^N>Ee0 z`X>YrdfAL-o4bbRsB@8Ud%6{$ZLtGY9UXlWqd&*7RG)q_CCTC_2GZ>wH4#oN%mWj~ z%^j*mCdxPLR*qoP$kh^0j+WwD8%sz{+X#WaxkwAJ_JSp5WZ3%OgiB6r94$oSXb%AQ z!%+vIyesma*5#%PU`-CrVO{6{*c;x52Arc7mxZe&ooc^TH@0?PZgTiwV{dM6weK6{ zxjjJ)tJLvG!U16#c!zT1_9a2U9XieGJXgL(cV4FIwq2Qc9?wbxIBxk(rx|cg9ocfQ zKwX>$xv|4e;{Ie2(n*f4J+ry??Ro?0HTo0n;{i;w2BzCplOQ0udpUO(ma5ynQ0MY} zvb&t#zMIgw^YE0sls9k4#d1YdiU`l2CaPrsjBB+?NyWX6V`LB$SDTa&f%*rj{BXz4378W&Zg;X4S5hxuen(OBl^7*v_6O488d4;;x-i@l8; zQK2NjrW|lI@-+rXiBN_4IMD(;np>+1X7QngjUFuZSRre;F2tQB* z&IBp1h^7s&$weE^7A&9oOf{9*0neHb4sIz14NAZS+bOdw>gTUj$U$A*`b=5|{`%1s zuvE&hRT7aA(;qbnDtmGI z(c3aNG{x!*X)rlVOG<@y9IAkZbk!U8g` zm9;ksYEMH)lhD#r8wmIdyO5y?f~Cs^ma{<66t^W|;tk8S^mcso%(Td*K7Rfs1A*^^ zfFDb~xpTqjt=ow}$?fxRq}e~Cj&?b??Bn)V`#RrGP2kU?@b4>@CE{S?*jt)x`e=t{}gx|lu}aqqlbIQfrx5G846T3RyVu5 zDF>BxX{UMu67e+=7x$KvY-vDAtbO|LPl2+}ZJ((4{wO?ip8(snvuCh&1*G1p3^w6P4j(Za~RG<25XoU0nUvK$_*HjI%NU|83}+u zMxb9~t!5#%K_NsJC8f-46j7cX>R&nMo=H(Id(nO=D6o4PmaqL)*H%T1M6N*cy9(te zBF6f36&0`p)_b;fBo`1uER$Nc!9J-Js?;?;EUWYKAwTk=QgPCU*smmiwGzF-QyMx{NjB+Jpw4MDM!tUEz@DNN2~piRy5Mdw zMhccNv#>sn#xj=$cBcnMNy1EN{QuWetAO=W6ccy{)CAQuRC1bSZ|puP$^DyT&qedj z6KV1R6B1)2P>s<~9encOuLY2Ctoe2q#`wOQN+cKchh6u>FjAii~}b z`Nd3(>)sF}!Byg&qA%=!wIbga10&hPVTr2_sIwF#D}t(}yduzY*3-4UL0m8%m^2|YvNEu8ughrVIr|D+JOy&voYeNJo>^3jKnqZ!?Qq&iZyP zqFboPPNG1ZQ~BYH#fD5&6z_<-p%&i2(csKgG)EJrB9{t`)V0ciC!)S&cI8{Zt0#Csp-C1~8LO@@K#Sj- zN4row1n=IR?Dly2T|()3zo#Vt!`IvNu1GoR;H!rO&!5}X8>)KGzW~A0xk0*JLaN26 z`=DKgm4a}I2jVjo#pII25sMe3s2^7oB4EHs3CGPvgKeR>Es{`%V(CDzfsgho;Gtn$ z{CyG}L-rJmh{1MCs#Q&Z=f|z7cTxT4=Yl0APn(0eOm2u&@e=k$U*61{1V44UtF->`9maw0#V(}`#s(w;lN*6 zSB0l6;@-zG?ZGm@+(wJd1r#kU(@%gxD8cgmdxMW8R~(I^(h_)TS4=`P9* zHz>h>5jaKV`1APen*bMUoM!?$Oi)tbls7fEV9rxL@RtC1@#z_xFsAV*Bt$Gnlx7*) z?`*g8%{EVNm`%R`5@~~xxZYRS%6S=iIaZc_vCb_a9M2)p?r~W1NDfVFJ6A17zU{wv zwOXuTM0CI$?cxFfzHz`+8^ounnQ3z$&=N$vQ&EdFlF}iSZCPBIRfy-sF5S{I&@(IH zeff_N<>qnUxD%D}n-X$9NDHjMA zXjN9j9CBJVHZ-ipYp~qqvp~Tivv@ zzMX`Kq!3VGfUgTIfzFI_V~=jV{CEhK_-bgvE9PIiyL;evCur3Sj9^fpnfPoSDxhlj z1BA5=SD#N&a*hu6F{w|{b!m6jeynaBr~>bU-?0zBqyQJYK&6)9ir9z$Euz&IefhnK z?`Z(KhKUh<6k~_qVfxeokc;sy?tXkCv0@r<<8$tJ;KPyL8pZcp;r~bT;J0@I%r4WN zqnFy?abFahb4NaV1UVd+qgeC}^owU5cqtEUn+(Bu208$WB+znTU=?C6OV(FZPPEh; z?cOEwH^v2z;om10#+;{5j8Hu=qh;0t4K7|@O;V#5F^Yj5B_vo3Nf7+}W}}h>ZUFuG zT=+D1MCbnnS}Dv8Ggwz^cRRddDtiYq18GioR;gq8XkV^o`MG?Y#T?k%MNf_}6= zGQSrDrJSX6frPW_$;jQT*&rdxu+jbH|M-zeFc{5GP(ZmUE_JQIH|eN>DJuxY7>pgT z2nAMu=9GNZ91J+jYyszMR2B#y)te~Gm;$2bBo3sSq>TGtQ9DQsen&ZLDY89_imDgN zm^}ym37TfHHlJHV_cTje^W5UT`_A%@Adx(H(=!X^E_OckzBh6JIq8K-R~T41*N*ccwtdo!9V%=KR&awXC0%}_EqAe>QIQN_zYPvn7jRlRFl}fa zbjZqPKczgwRo6$yD)pq^=G<_!mtt-6sLPpn9X}HAq!ST;6ClKf&Fyt?BpMM}5Mjgx zABnXa9v%K{E1HBAw^H3u*R!x7Aw?M@eBxRd_$@+@Y8D1)Cl{bn1iKm@PK%=jIOKTu zyM+4A79n`g9D#YKR=LK&<3vcttdJ8)2t&Z>emr{n;h9lfLqp0YwImac1q2Fq9$+WO zoE?`ddFxqA>yRRDaY*IF!Im*EMFjUD zD`c#JpjmE?mAUN`R82K!^x8Nqm6!u7O&~=+#8^E2zCO0b+b42QqIwBdR_^cyM6O^V zP7-fz!Qim+*FRVHMF58)WV~EsvxU}{ec|n6y!#UntAK#KHK?}mn>Q#uJre-zwO)~u zRH+J9(U*&41}Av51iE_rxgQ_vn;I`gw|VGiXZ6%Sm}>0Vx!zR}ZI>WnM7udyJsE1; z$YKt2tp47tSgzOW_OhGxZ3%DRykV2@``i!C6Wwk`zu&xJBf|rigJ=ZEnCg>%WC==}*Y@S*Zp)vsA=d4!bPW-V=;@(hZ9 zKK9sgo)TYc_DUk(BicWAEtJ9oTvd*tZx!D@+ld0lNH1E{Z{5@7y0*jfwSlWvK#T!p zPQWEn5;z%7>zSgrE3$z8RNJgvD!FwD6N4RcAyx16d(UYk0LC;uA?<7p9(xn!z9Qc9vQBUTPCwbUpGF- zYt(Dty$!|*EQs6{tCa>lj{UQFvCFe-zw@iRRo=bEI_6tie@LSGERD8!{m5sp%U3DT z0x7>VYG$z0bC{9*{9yIjWy0u(&l(SpYUYTg^YqUwqtVY=TsbS*QbhF71W46#_5>SR zg(1qq7Xh#30P~^Opb@4>&(5aYW6`8kZI+UghXt|{OwZ5%Et(Aa6ywBIIKbz4ll}|C zr};~x{y*c3d857@$ME^!dmzo;z88SB+HlEbE&81)40ql6h}d2*_p=H$X>1+jApocl zE7?A19LW?^gr54kqhI)7hr71Y0P8~FqN)XLCKJnf1Y#WQVh!+ck&YgU#Egf(evwHL zeYjbYG5Hi(%FKczv*aHmZyi$i7t1u>f>#|xRoDR*;vge0tN1k+u{^Qi?Ch;K_Rc5` zncPy*Jfo_)cWalE|B*5{*IR06X`331_n~Ss$l3sBQ_p=#bp^mVAix0xWS*orM_Si~ zqGwfpvD^Z#(9HeaJyMY6kx1kwEj4Jl?Vb-^t{xyZ+}pD2XP(uZEaZrRG8X{Sccf1z z1%QJwj2f)_G&#=E$B}k)1eglXTV?cx-Z$VRrb{Bc-A%Dr7c~br;(cB1_uh8IHXbc% zn|_E78OvsEQ?ha74L%j9wmyUMWf{&s>(C!S>kkYQfHe3EBPh}4lXVp=Bbrm`c{gb< zJ7eQPg)a6`V}jLfo&`$sTf(RQKN^nyr}nB^ES<$zu8ZR1AAmi+>!E-vr}W^vLN6*^ z(7!pUQTel5`K){{b^PEe2k{h5i{Cmw=G`2!oCYGs6VnS-)zm=$Yug~oJrAZxgGRf2 zYd=~)G!&G)2Nzts;`JTi=gk@g%1zQ)*gF+f*a;v98>5RR8CA7FLcN7XKhJNCIeIjA zbo7Hia#@%wM-8mN-o7FY);|}^fBXOa!}D-lEJ1F8R)oh{#L>}+CC=jdbn?x5YkzDk zW+15-$Raham{E#?>pm`+0$0x^VTshc&!)_pfy*4XlBOb<`K7kG`lG|FxKGD@9GTY* zMoVD9$v3NI-&ES&vONmasa<-I>!lCV>Q_@DzHWOjEbRaW#HjdcVQla@TdJM8+oKP| zI`3lvzj5(1$`R&St=u-xY|2sn{X3WrOQ-69DRF9FlH_7q1fNG&b=2G??+Z@7}a=>f^_^h5F9!9plH78F>;HoLRGCy-;Z)`OE!M$$Z^% z9!fn>oP)9QwxsXvbA)EresBIUH;m}h8hOk&1~Rwg#ifh*;>uETsjDc>^6Hl3PO(Lx z4PXS{ilx*ms`CWNA&D#$F2V_s&wDB?Sq!s2K>j znx~tR6b%xvD$rf&KeFqF9sLI&O(CHd18C>l;N9B(hd6Dm`!L|@c?!0PASkN#8pp3g)XI9!e?~HOL3EWoK)UT!&o{0xmv9 zOJif02qlQB&xQm0{)GNbA|VMYi9ff-kxOMGk7ljqb1(2$G~%oAoJ;%f&6&7SU*pYG zuZ8E5CnQqZL?QVZM=9d7-$y%6?JD(Zf4^Y=CYd@f#kR?(LwkYFxTdKy z-kfr-%_Y43%CEy~7u37qjL$DOSxkAv_(y=D^F??#LvjD}7`3Xj$(PXEdu{F*moxtoIB?le zverriWPN2u6q>#JH?N}riD6VGxkceOqq)ZfIjL-7-V6F3Y&E6di{9uble}7XsjR2~ zDJxkMSd1@s6~Y1F$vQzJ2cm%`n6S{}=o{z()xF!oTi7v|ez1FZm+oed4$K3JP(r+} zGzp_epw?P%jZ1B?ooXyap0IeS(JUFbH0WLKI}SI()Ub39Iu?gv5zRv%#0lnj33yZ8CY0ltZr2MDT%2LnWIsgJf++~RQKCsUm9QDbzUq5mL4JnUk7Z!EgKi%M;ap)2XxJKsjNeUsZoalD9g2O=% z2G9X8$-G#U__1eXKPwVoG|fKEL(PU3`d==9C>=b%>|fc-D%r-2$J$~hy*fU}U3&D_ z0=wsGYKW@~_PAxhRWs(^0Q0#>dWQV3jLO0Uqz_npHYI%`a=A70GA*voAEeHg&Tcuo zeo!oaJ;;}hr>GTG074)<6<0bB0#&##Ju#lFBArV9R!-_EUY~ggB+y~{5Sv10sEfyW z)mao^<^s?$GIID4PAx&D``3}RHRc>gh1btb7R|if@50@y33EvfR*zijV4Xw%= zC+tGHYG~X~zz|TZD1yl&;0R-CU713i;+#=voeTgl;4#g{7|`I9`H2s;rVsGs)HU>8 z_s97wnbvj*h!ffx;4I)L!LVEshgB#G97zDtXb+&vKMgT9HN3Vk_kT1Y;4JJ85~uJ3Z|tL+Wn9v`2B!nE*96j3AcQ%<{ITqZF6e5R_^ z=sBO59glvqRo5f|DHWt<(5t@dd#fP=D%L&^Wjz9By()2 znGznHg@u|%h`;M){-O|Dno=R71b7iwJz(e^4^VI@g);pO66^*6^_XIX%Y4wGUstTp7INYBf9KF{$tl0rjum*W34{=!vl$81PK4^j%B8`d!F}|AmiS?awe|?eZR$Caj%X9;hUN*I$4e|iUooiaoVz8qwVh4zCFNxM_x~M<{w;`+jVc%@`x3M=5MrKS zMQ$Ue6i$nZaryZKEz3yojAN+4=@C$Spp;kp6;@{Ds|{qgfelDCb@ZBMa>ag?D zrp1Jb_U=sLbzWW30T89%P0s#T*AfGN>;o{lfR8ZSCRvDa`CMGoi_zR^Z1P~c3>OEB zlS5B_+3Dnp`;B&uGx%wf4L)BznBG61YiWFFxd@Qi4=>YfvJ4ML((dmqZ3K{uqX6w9 zB3U<@2ak=Kyx9lC4A2f=F7;xgf&>H*P}WO-yz(052L8&|>~T>RoqF{ITEGsTg@w~` zp?TD`kT>dIl2wL5yZk5*Kz>0Rz~50$#KH?)$=A={F+p$~UyAT{Y1R$yL0!ns*02E? zD%U0K9L(6}mrjd9F{1QRoldnpk}L)IAHl--!hG?xC{TY=)CTbywlX8AR7?Wys8*_! z-v`!nnWPY_Uv9?Wmw4^2^FE#@F4Fv^<3e1p%MHy^b}F|*U;9yt~pyd?f96Q5OXjD#R<9t(Xh&x z&UivxQDFO8$qIbsK8wK67Li02`*pB2Rcr*5-vHh3be^tb_0KL5Kh*)Eu@Nl3tRH#{ zFqfuhr_&S20BE}$^&S{V?p&y-sxl1X^)eo>HBY~l%sR-p7oBUX^%bR<*z3y>tqO2n zP#~;`qNA)k9@$Vx`wDeDt6NvSpE7yDO4$wxlqJCv#7hAKGMAx%J*}Lwwot}|L^9`#J7hw zEW#uOW-$>3M4jrjFK`+3so$IjDf_N3CqkgYWeJKCtJ&HNs(ljSeyl?i8$((A4$I`d zApiJYobsM!C#tfW&>H6M?5$60&p0emHCPugHUtXrdb4YibZ!4Gh>g~ zV4R3*(x!OyR==h!Wf-K#AFA~rRkd`MhSTH7D6f(&ituTx6qy2+R01cRnu79G?d!ud z!d-GolOCJsi@_-A8ogJEDMAaQlyr1-d}+t*c(HWW!>rY%bb5%%wkNts@Esc7EADA5#9t$?XD){w^VPHR1O!Hjd9h)tprxi93_Cy-qnQ zF{CiZ~H$`^<65NndwMXi=gUX0LK^sgx}gy3&ee z?)g*b*~I2b*uX{FKej|?gML?x=9N^zZB3Oxp?*0LQJR+>u{=s ziRb7Eq`mb6XQCT9l+kCw0_O-epi_HtrS0L?|8jIsl2YWiw(WYi8gaje?WeluYsAwY zVJ=YuG?85}lEQNILjcIrdsHgAin3pyG7fi$JSF|`lgY}q`QO2(;76cA(s~gV#^uL* zt#%PG!1UL?;~DgclV6}y$CsP)D+z$@OQ8-EBd!>iA!|!3EFh~S3FPw%4laV-+tLK? zx7mwVHj`Js78Wt9KXWItZ8oH z3*#}a#S?&tN>NS-VQ+43k+SdtCPaq(z1bT6nv3PD5?iIV*_AS{VSuQ=YnUlqY!aoR z?}HgVii-oW%a<#>U!RC*m@)vm)+e3o-lR-~mA>|Idc*ep zBwo7-V0wgpEqLu+81w>~l+A*)xwaO&k@Ruer&h>Z+j!Zb;JJxU5oE~x{G`~q{AcCQ zN|XLphZl7%ilw~sZ<&$QMbVBfn}x1plJrO`0qY63M+Pd^nVRZfOT7pGefb%iF$lpW z@}WP*S!`0rCd9f+bVrH^!uTp+I9)~FvpL|}FR!ih`T!MB)idQjp^|U#P%(HwX#2Z7 zSs$=(pHiw5c-kO36dfHg7@SrddOzxZvI14sRHIB2YNg#{!MbKTJ*pfKy_{PBRH#nP zkm>-tS?W2Qe6zR`iA6g>b$6(?v~TnH`M{2kLL4VP;(*fd1(l(ptfp+fnX{!;XpIBl z`a+@$yFmFGsi;WOW!K~cJ2-dy{Tua!Ak5Vpw5&E&^X5D?xMzRbJ!&|zcnHP?Y2w7o zL?Y|oH^|p$(Bnw^B3@o5FsL|HK2PDBd=CU}Skt2BT4DE8QMsbIPagM*)JC0%R5hpS zM`g!)ZHIf=U*LP);k&0x)+ZfjQ5mxo;wcsM_&$L_e3?+zuYdSRPky8Jyz}P9Sy@=` z2$hhn02P@EsjB)``FH5u1~{+H(>$6OaQ&EU*2~_%$0hIEbJYi|YS(*ET}2Ygc!$QO zrn5DSQ?thotvNN)p6lSZ7~SU#tMe;bwE%46AYZFQqlS-f-ziR{AH;nk3IZhI9F!wJ z<)dnQY#mJvmixb#Azihc{i%JRfjSnYc@PYytN>>L%B=+9&z3zI6?2)toZk)ZN(i+M zrh5Kdv8ZNXLOR*HFb*TB-^&%l*m1c=wJa>U>f+^5_UK(cF2u$P!Ikg*Iv4SkjbqbR~Xi}mHVYaN^c%i zQ-|+)#;mjo{Bxto94T+^Y#u9m&03AxCX%z2`ZwF;s8#X0BzJPeI#_y-?Zy3zEAAxH zD%i>%CF)4;-HYs7*k7_}0|y)m|UvH0bSQobI&)E}?wU+mHe3q5k?5g7>Wuf@|t zf1NWhsH(y*2x2FR(s#bOBSxcmeAeWN?ICABcNqy+R9JHvAg?A=WBO(Z!X$p1#Ux>E z$eoO{d&K+)=PU#O7-Cc- z!73*YDPUbCph2o^;k(YiN<^~N+=w*+V|hZ|s{L5qp@l#a3bt z!(}L_RF(Sv^9VQ%6po5m_RNG?OusPozu>&NMBKzk-Xu#}+u2~Ns^U{+o?WhNspoD= z)>KJTvmc|qX#obxLLB7SsZgWwCsxd33gfD`I9G(+s_kg$i%lWFUQg>Zv$Vsk%LB(IZ1M67C0z6i50$^yGX|Q5=3Ft(J82Nyu0W1BzaaW-YSMW1dSt<4 zQr@ejnWuR-m*~qqbmv<;oAB@EyGuSh`(_R)YE|JFueqxwTFHy`(#x*Wyz2H@(4{yU zcLwfU%mp+nIr03t{>Q%a{fPNyJ>I|XD*P+xnnqGEt1b`B$s}Qg&yo)Nw;;512yD0s zky^h5j*Pp3kL!h>gy#v33V%HKaPXoUv~sN}{EkMNE|+a2SI~L>-N)OHqBvYI{SUJT zA40)<45tfNjhDP(%JV&C05hZYuu%oN?}*2s;jOPaMpKj4=>1sj(mri=k_%dC1#B;g zt6iDjO^u$-t*O=_y&EGjR@3z}4!V_`2jM_*rW6S^y<3SwIG88H)VK^dB+9t6s}~L; z`nWf8?Mu=Xy4>=-945AtyQ?Crsl@$G>elXL)8hWM*iHiC?>i4svZ(vF{r0wFv_ckD za#MGb?Ph&Ez2BdXn|UFRPBfwaHE!qSS?Okj69bf_#W#)~Pkop|!tnbONd^c$J%xJz zk2{*w#k?{^)$MgdBUGrfKl3LO;VtI9u^1FC zt1L`52}1BdrlZp%zG=}HoVO=&VH^UF%U=Hk9ExrpNStK>^dhW~c=2lY9*Apab&^-4 z?j9bRuR)#$dcsJXi%3Pw^}?$LIu{vs`PN)`w{)_)#Ce2cl|s4RYL3vNnN3sQ==P&_ zDT7l_pv-0Jca8s|sWFi}1Le5zmWR5w@i>AW=WOrVZ{c!~#{azQf`da~5bX^3d~;A< zJH@L^WHWluli#6K_}A!Ll3OH%w!&%mKwV-D9II?_y8#qs==43&8^|?bN&QGO^&0^p zBKkINd5;CKt3`?>A*8Fz`sQbh{7QYtpi=;%>UuZL_qa}d zdS*c?#mWSzBFYyM_l$$q9nmu>=zlhiI9o5Jt8K3fHmCGG-%~$pTfCIXgvO>1r>$zx z!S|ND@*udm7|p85)78TGDZ8e#CHO`7vg)kcw8PfkLfg)6G(w{A7U=S-f*XA0WovE9>PL<9fG_g;$ZxyRUTnmB2f}5ZO(XEoKc*MuIcC)UA)>_+)oQQO9MrE z0)&8dGR+G;AFto+o+YgIL7vmsMmDC`#AyI$*Bn7kEpT?3Di7uHUxs`D}pdy zw12fSLap!d=an{~)=9LNIC~7Y3B}o5MS>4_cRVui`q!&#cg<>66#IEfOSGw zxo_r1K2*aiKm$#B=g|uGQRNGHw*ZBDlIOtdlK#&^Fg7SMEId5ox4;Q~IYRmG*c-qU z%fj-=dZ^sm$A%*}lDnbb2R$S&KNb{8*nq4*vgOdx;ghyCPZ5abZ$xeaQB$~doz%Dh z;F({Z?{u2kc%%u(R8wQaVLYzE()l0JXNMPH-LH6d2f6JED1H!P@tLV!4{b?PXpmC* zuu1955qGH~c>bN%`Ff=TGvT-P`ryi?W~vVR#!bemcAReQ0DQaGyw^;i8)mfMLXCeU zdghx8?i+XK8Bl?v`47lf7y%Tn&ms{N^I)X|lVunqZ`s=J{|}08Ri84-maQ`Z%N~s$ z9>_)2HMP;PjYIEx1D_a{X}VgDy0J23na-Yg+9KH403$LIIdFGyh8hRaE*t_+$?%xP zDkw|?ZvS9oaiNj-bg^JHj$}v~AF$MEzg0S)gws8V>!(cqveHIsE((x#eKk|)qkAtJ zJ$C&h9}d${86y+^)+=f_@%#`tD2Kzuj>@Ux!kTkIO!{n=sTbE{QaZAgC3H5j50p{@51r-f)ligrHc(pb-&it`#wauP{w0>#`Sy<=yrLTaBMkBEkF8$ zXMfej_R>pWjU%184?3*RLC!{opKkrM>V0O^O7pi$iB%Mk3Jf>`#%nW$3Pdcgvz~vy z+J^sn>_mKi_t*@3u`YrsWy?J+$DL~O+vhC7uxHUZTgI*E2%#(bk$tz9c}nHCPki9- z)covWZ<8$6sFFSFa=oJQcRCQr$r=46W}ri!Ir3og{r#cZv+%{6TwR|X+{KyfM6j3veq_r% zoSq=T>i)dqqWvb}192n6o|AjW$1}as8ej?I#Z#Qb4?EX>x^yo=ekSO-bGthVT8ME^ z*-{i(WW_6q+_~^Qxw>`y9PIy1(N;mxk{wr21~>@bR}xT?Lc4PT3Zum-NJg*x;@lOj z463E^yGpTmxumvu(CIRrzjCJ}82x}~6t|Tmq?(!RQvl9z$MrFnm{=QDe!L|hhTW>7MPKZ#^*@Nq zF~HewG!Oz9~z-7{VZLc4*ey#H_rpof~`lZp8NaA0(QKS&|jugjmFnY z9;OqShl}s_t=}yS*Hw>w)?+OS#p%3?o7crhoYZ_1uCvF20btoORSAEtcN&w31^XT z!e_}hcki7_m5|xr8NSY&e|8*08E@BiW;fgEz19l2?`BL0rBN$gi>%AK&gDIB*s|Xq z=HPaDcik3nRh1Y*WKpc^7n$X^HwO&pfJTn1DBOE&lzqfHIX>W6S?S2m_v~or?7GeA zPNcOnUDZR z9$-5MfO&D+H(%;M&aB8-cmByTU+;y*Sb>EM)uag9KfqacWzW-KSPCV8cb^mvV+s25 zD(h7HNthHF9F)^^0jGDB@uM%>fCm=XnUd7L(2--vLx2t3!oBr`mvSve(%P>%W^)|_ zI;%~{`h+WGl8Piu`=r@3*`k70O1c(ca%`@~x4T7jr|p1V8O`)ojsdzX6Ush;{Xh;} zOPuas*uzIeI0TkA4HNa%!yRZn;J`lS25MY)qwArYWm9LcWZH3hl_qPRju#>o;rIFK zK2Y%@mrwqWBSi}r4%0H98!pu;cMuyfz@S|p0nj;B;RwUtV&lhfvDPRX1jVj1&tjlKvsxk=%c_HQ>zV-FOelepRW*c!? z2=5&Zddg{rV7wr(paS+}DpyjuRM^Y-F0)r;FBcNPLr{|2Kf5f#K`b>YCq!NScWPf&86lFJhIb}E3N{Cd zRw(;81pZwP-CRnp1e^=qq;74|sQCvb+h!py9Qt{)`CXQ#awTM}dBsg%9e#=372nwr zZ@da^IAgMUjCwC9adD|WuKe(NJKFSM_c|W<{VY!iX2u>09%E2xUO;PIvq2n2UDwdi z)$X$hySZqaa{uRL3MdM{sBDL)%QRV2#{_5+6_IZi2>Y{J6Af~oge5Ywq;Iq=HgaxD zdJZ%#`VaLND#R#QlDu`C^H0V!En2|L17B%95pPxQdc9}a)pJ4k&cgt!<5rM!w3N<2 zWO88@!yPbG7<(MgPWiuFfDy};!cqpP)>GwFC9uGgT|}XsYDGtqO9#&?m%N#(MYa+^ z`SyfI7?k;*4P{=d>FE3{X0#m2wWj*|MU-;}Y+i$zVLb391f^5J2@nTFdTxAU1?m84 zz$1v`0+s~^N70Bcw!E{fc*Kh92Ipe|o${a`my5*@?Jm-248eF4DD<$%S}0mR<4=1Q zv$C<{Hvd_;#v+0x75C!4k80hk7CqU?U*&F7{;`1OEvY)D*f&tmjUOs-9A6MI{;oIR z!1U(L<@VRAv#U3zq;TTa$`vnv12V7uEFg==@mw$UzO+W=>uLuo{yeyVD!rf2Ak4Uc zRlHuZPnsb%lnBU`d)lko$tp5#)h!q8niF9myM4{cfM?;CFXrp#`OVm$bt}|1h30Jc z&=&a#6s5<0;@sqpZpNiXO8D$>*f4lTjPuyC^H(oc56*reQLt)zKU|Y?WrFR1D@yPo6yh5JL!z;+|JP6HTQV8aZAUqr$SMueP zQgMBJ=#N?sdw2%dGw6gs3nXobAt_|(T?oHO2iQb2TVq-H74N|3+(MoCrKIMpw@Ob( z2e9mG8A(GKQrqdAzD&hs&Dg*4j@M`XT(kHAVRo%QPG?wv8m>D>1B5;%pSm z5i3M+T{9ZJf5Fm)+YIsg5pA zZYlFyGzm$AFJ2`=WRr@jL~{zqsEY2h7$JHNE}!Arh7;yoeO_v|I=*U;yXL}?>F#=W zo{^97yzjo7dR-9H}y^#$ECwO-Z4UFF23FK}s zLLI9wbe~}d(1j7VUz@cKcP)b92*wlpd~2_%BdA8c+Tvj}NljZ9BQTG?{^NPq z6arS3{r^ZZbU#w)+2BK7SJN0}$dH@fll)BOPKc;@hQSL;Soj<-AncVP90hhW4==9Z z6x7>$LlUuFA9j0pCYD9I@)0Yn*7mVa46)ZndHv?-2mpwUJ>!4cFiB6a;qG(z$hT24 z)t?BUV%X@oS1Qq#wAoJPloOJCkK<|SM&yG}7X72ajzfWMd7f1l$7j28p)+JoLS8D4 zSQU4tDI>Z*U%0W91&%`gz7U{_vRT+Bx6VEBpq=$U#dA%3?l0m_rb)&S4gFqjTb8Lt ziB)gd_AV)Cp{(Y*2c;O;m*XKRi*(`O;Cwdt^l0Y4MZXiy4x5#>7lv6`w!`{t+LgL& zDPRYWeBavN0L{pm${bOUApGdbBkbn5XhjQy3S)P}Hqf+D!@(w6u|+aQ$fU5bS^8>5 z)z{`@!-e3~%c$5UuMa1N$L@x#nIRm-m^W{k387au6z<2DcD&-T_5kGwi0{rF>_ z4asV-nM@r-Qdyt zNtb|xNFyOF&C-o@*CJgKf&zl1G)OH-sfd(-uyikt)OY-U^E%^<l#WtscTlO;746^r&8^^=l0yh7c3BS1u>86umMg;^9L zD|Pm{W@n{sA7!-K<6F3)g>9wU;1Y^HE~;w;7@Rr`G6i?kU+<9fDKV(RpBDbJkPeAU zGl+7Eb^Ne>#Fv-Pl>{{sgbP1eSkeg4sKlVgB9u|kFlOw?2n=M}9Nh)-@AZD|@t+N3 zl0F%hS6~ckmdkmP6T9XLcMk5ABc2R|D2U2Ope;4NyY&^Uk=d>re-3{1W>7)#t&UXs zL#kzE{zL?My|9E7%=xhPmyre_nIg7mWOzIR3u9z?R9m{QLs$sZ%CcTg?&KA=Vxrc-sSp@g&|B`IB zUcSUAFlSB$h3NGvWp}>i+xK*FWRaGCA&---gO=DyCJk3XY66#D;Vyn&<9__hMSTi8 zmyi$aCZW03n9k4Y&gW^TQVw+{w3D#I;Rt0y)NmaC%|=_Tzw3WC z;#7q>#F{^U|9NtlYLnx>(%tm%wG5yA?AfHu(%mlX#&7PR==!?RE#*X;VRG*r2=JhZ z>#@Ml=-T~GCV4{>`A3ae2j1H~Fd3Y!)nOUX@vZ{@Ba%AeaYz!f=}Ernz4hKK2|}*D z19)&#_pZY{PDH%=xPO6no1YAR=^#X2K&$})k6m+_X zup{dCbQO}?8LQcl{@3b&28y+X_G@p=sp7S%WX z>GrR!6TTFn8zyB!yKKipA4xYAJaP8quixXENN+fwhlh;f#-Sp=xJSa=*hh{Uvlx(; z&PhdArXF+?oZNQ3N4Dr{lvkI_DBdij9q7K0FtFT@x5tykn^{;;5!8S*gzw;-1S+nG z%Oh+=q@Q@A=>q9x>)U-&dT3EE%&1&{FOSpLerGlrs#It)x1Krpu{duxbC3oVEL^dg zrK-#|C^Ud%+=TxJf*m4KJT8t48}sWF9UtH4p&WgW0p5}6nuc$tJ&fM?KHUS0*BBCw zIxCS@r&J0$sg(B?hU%vGJyB;5GU=6fZ<$`??TFtej5X#WUB37mq(+_Y*>V`2ov)W@gT+cA+rdg~DN3 zVETC6wg66!cQ)_Q8r))0ZKRW-Si}Y2mJVd%Yo`uN?f()wx+bx!Kj^~%nhKz+TF4Zm zfY-~?Q)E;;Mliu=)K#f)JVF*Qfvl2XqD{#;RpkI1Ar_kKu(zkfoAKeRP^IcOJzEO# za;CC@=h%| z0$PsN9!I1cyZn*LZnG%pNeZ4a7I;6`7NVXG6!P?AirlBxMBu6X5-ju|o)qrVrU+cu zXJ`pZK3gB5jI&s!Di!FB|HvgnJ?zkWfJ`5&C0x%p9&o=7O5@jo$;{8w9*c#p*Nt6` zI?Y=cQT-*XFNsBE7yKfL0)Dvq3d(Ok5HN*v6Qn|cJ#nJbtNy|iGV;AWVkzID2P5zN zk2MMG(m?r;ZtfdrP^Db=qn{}yw)b#2TNl$<|CuLv;|_Boiz0*^j!oELiUHB=Yxuic z1_0raNwhx1$Z zBNN^}J?y=5K%*3xY$D!8@rt>^D7eDEfX5Q+_b&LEE{+d~s!)Fh&+Rt(I=undDj-pa2LTKGgiV;OGbQcwmM+%mTA>0D zAflwB5j5U8;b{Nu@KFlfh9u$dexf-s)&eN~KQCGG;ThQ*4GGXlGQOs5+s`NkV8Dx` zL-T#h>jxXLXJn+3Op&#%f7*abI(J$uq`;&zj^RqY)9g)8?@x+eXm65+jVA_&1=|Uu zsas;TWA;0t0mlNq-O%#ZX z(TZh7(o`l^4!t3eoX*^aiR=uuvOTtrJ(VobB3({7+#}z4@woc)T%t>Yc;QAL6y+~L z!&D@4pPw_bD?aREjqx$Aa*DrfcuEniA-t$ zf0sK#r*rK1|4C-8wSa%vY0#I1g>Zdd^^3eXVtz@Pm{H?_d}dD)`|tO!pBu>yXW#`9 zPSBUWlC-N+s53!w>%~%F-^FF?g7G-Q2q+B{9w_F&<;AyrX-yh{=N1{3!jDNLp<+lW zqF-)0T34@EFecNdExs{*g7M*=J^P=cUB{u3ervEB}1v3U4Ytl9slX zKW5@>R_Iu6^Ttu02S*Em;n0(tkW3vZCp=&lQa9;{lH>;!xJOem-5hi=yM*^!5@tDo z?7zgNFo2Og*PxNSK#{-^f0Ei~Iz=TK5_5&q^cnkxiXC|4tF_{>@JgVpM~nz@P9N!@ z304AArA@Yk|8%tk9u0jUD7SpM$y;vu&6FGNX`7Q@oUWQm_J>oZ;AOu#-|5A|-U;nv zS569hhLtsmEjuaFm7yhCT5b2RoPi0127Fs$m2KWD(tp&e04d6BV?HlSY5-BdGo7$* zZvqN{KwPS+Es-Hv?YAI@nZgLQfH%HzbQ@9TLu)QfR%NWP2-$rKF8uxXF;hkT`GX;& z4gg$OS&e|EXX`>EyA(y5@yuelz0}yKIqd^jm&G7}{!mo+aA`zibtP_NF2<@`-@ghpfG1@DO zf&GMPgERu%8W?bj`^tiB$!aTjaWwiEkQ`_l+M1S&>Q#sWKN(hTG^6jNcPyB*(ad60 z&6}ANFj>!kA^oAiK}&1jI@?Vn|I*wxYSQoWM}W^yyZoF<%n|#kdXm@y?Z*h=x{F=k+Q5wKzM#(}qj#di|NDOhoU3uR-) zV>3m{)1IVFsf~UphgVc{$3o>*St!CbuCPL4Rjm->N^J7FEM!6oLs)5v)DcVsyJTz& zeFOldL`7ahE$Aca*D_nauw(t0B!tYC4b72?KS&*siWQ5*CKeVFGRL+<*gaMOvb|2muo9Q6FD>L>_dV*y4C>gEN~FRir$T z3$ODtw;$_h4lDxCKuDRetbm1rssUWM0M@yjM~|_;v2{9SQLbkF?n(lC!@HrOmH@DQ zgJNl&e^jG+&1Z#*{gTP>RL#fYa^a_b5?1Kj%o@yx2C(sQKH7(G>?vJR02;!j-QLDr zdBor4T;}-qEp>o$+uPk0Iu)+sBvh3_AkL=IRD~6}Hhx+qN6>0zb$cIU#VrdU$sxG8<-ELluBsZ(( zquVE&Jt?U3q=P`04*#ww4c$B3zml_Djl8YiU+Or&seYl8-;ST&z2WUF#9yc(5F)Rt zZFgf&3j$`tz-WB`@YeK};j;4$?=I>Xl&s*3Kg_r!PnbBGHSTJEzbCttTj-rq!tVuf<*~Dk0$fF9a8zi5t;ZH&h9|rpj>4QuZ~UkM>v~2a^Y-!6d3*nVA7;PIYfs&G zouWOM07TeABy6J4LPGp0Iis9~#pNBZ>Z?YM;jQ1T!DsOePiin~y%0s&U>S!Nq*PDa zMMtJb%p}AcnLkUFazieu`G{}{j$CnN@102}QO~);FMXqqr9^2TQiXYKg3nh}UHMj& z2!2%!b|51flrnYl)6(EP`BUL5Ew;5n6>*!$;4Cw|hw+9EnPV8ZO%E=I?j~N^p*zO& z?Z;ptnQQzB#%@b$QVEPh4Yg7u{NvfA^%|%wB-{#~=?yl#;HZr#S~nZnurJl@Z$1>c z0)ABMAAR&=Dc;*I4g1x^#GKXl6=grj#4BmIJAiN6kIWAroF+_(5u7oZ0jAE(WlfLo{e zLng%IOI~gQ>R|B)q00P4N-j)x$v_AVw7YPo*2*%n>Ci_GUmRKJF?^O(#zva-a< z_oqTte?Xh{ATXr0^DL+ih(G{i8{rljicf^`{Ks9dL8f9P4_e(VKubaSVaXGr^~li? zKO6~p*!Xp_u#mZQeZ!Hn=iG&j0y?pTn?QnHyVdla5ik2weE6RdGIkC@ID!mG0mR`r zDDZ{UFhd|O=_JPjlGK~zi%s7>`(Q=G8D9cS+*B!4S)MvP;~4pXmk-5fh*J5Hocc4pdt7haqr&a76IJe+e5695W4Yt&?<)=FNTe7^ z9f;j&v)>;R7Aowejh`>L>_Ef~$Z=9l2vK*E##dXddc3LfCWxYljfJ|WsVOf&)#T(D zD&SV!Hj&?Au`ev;w5AlCRFHH`@sz6UJ zNfZEJy}q6jprtbAK{!d_BJKxM9+Lp z<$qhX1Izrmho{f40@86wEpQ7(fB{Ttol<9Aq%jNtV}Kdlnnu~s!2ajB($~;r{E}>z zFe`rytJE9KdKTv@PE*w9*80ou92BOoO}g=q}sj*DTv z)XN>A8Q^sy-JhJDS5#s}l(Q&PJV(SRk=?hrh$Jvnk_#tHSzGTVYa{AUf(M zu&zH|!E`=|@w{=uQ|tA7P?jHu=e#SQ2TgunEUsofE|A8pPnE_&$wBSuvWAn8@O0IK z)1)ggxAt|Vw)HqvEtOIgq&_8%EOH$3YNkZP6+cZCg!!ta{p7mmSvA;VDV3m#`HGWl zR#qn}j14C2UhIzGGe(HjrzAh_5ucrzfPrAXGy<+aq=dSZQ^ib<1ybOy<3q0v`+V3Q zSB+C|(AI)PM-WKO0OZL2u{vpf6b7cxl!_w>_0b)-TMV^Mfw8nM_Nr^}2- zYn($ANl~Ef9VKvxUrIB9kFSu`qdl<3O@I-`qN3|fw2*D2{+=dUODl_qnh&C$0E1Aa z9(xnyrO-6`Ztl9224JY;Mf2Y0VD%W2(&20Ew1>m#|lx-z?BVhYF1*-&|U&L-skVfG4sVyrzxL1JIMVGU#F0f!Bn!A#X5oRw-MPRj zjyS-L6=x%0Po==ZG@BGSVW_N38=+nG%2EHI($&%L5dvyIdpD{uK_ndm<=f0nc3cIdQuS8V&Q`g1ro|mc zq66#1Dhn>F?G{$P&9ufOVbtVm6uI|NChgT9rF-fJgKlF&6L#%E zUkv_69N{$W_RZ9^%&mR5*=Kgf?{)e15F-2o&rC=x-aWE#mc$Qc?Qv-jJ$91LhqD~9 zbg&c#>&@EsL^B^j%=^WF5i7dgl=}@GehpC*h5gJ`kl@d-piV$W>^@+;;Ew zyz#R^90j(MREd3iB2p0;%%wqtrjck&f_J=4CX5rF4ThM)g>eZm*K;YW9{9ZU#|IlX z{2=p$6uxs`Ag{Y9L*2z4e-(B{@!};;eEo78kh?G;bvmX`Gh)AhtA_Y6wGA23k4BMq zb|qQ+1Hm8eC*V?84Ob&0M2J7IF*P$?Mp@kQyxV4T?26yuGHOnG#PH4K^UVB0f1L^* zs3r0n`N{Ergk|eyQbLS5$?-D+*ptMZ2;WrcJ2nwC(Yv81=hzlL;!6ERW$C?Q7u zL|(dy_ty_%g{7490?DYo%j`AY40XySPyD{!2hEAVB$#JCyZV{A%HXVo;);?FPKSx< zs-K+#nyA=;Zfu>+zhUU_8A*G)nd!7*nwQ=K;>z{~X^4JBy?cSh^>)#xmiol%S44sL zaBi69U(gfbN=HZVTmC88;f>EVm-6{|Wbl7jfJqiJ1sxlZXW#(}mqRfHmI5>u3rg$U zKCvGCw4zh_W1Xt|4T*fI)(2C&%RTM)Fw>0@pfm6FSXydHB@+TVY=rE;ei_Wp%=C|L z0H}J7if5X6?NgdahEV^BAwP7!=&{xa=6FkhDYoTi1`4wD$J*bZ?!x%j%^PoX+wteC zu1r^}`o|9p^fRKOI2ujEm<ZAwJQUi5{I_8w6n zs=pna(USaicBv80?a$ZQw4|8}!|mWg<|`=0V%g9RmXdVYPrAB%Gip(Bzj;*IM7QRV zv~zHq?_nMFu>0aEPA5iL}qSu^2}{L-v*z%(opkm%}O`quXSI^*wjrDnydto-qQ`h%Ebqe(lg zx?}K4_D0cl$-jW*kmf}4`l9cAx2ZV}Gs~kjmYaUYC$iGFDZb25SRTHz=!tB;0emYilLARbDfswsj zkn*G)JceDpb)>U|&A2uJ@VBiL9VAm7tXVSZKbHp1 z8vlOk*&;uDQ$X;G2^LvXtI5*Aj* zubUZVKV22@Fc2c>w}d)V4ES`(^yyHtI2t#R9@_K6UBUeo3iOIl()y#tTZgnKPNYUJ z%-$^;DP%rka7tvuw}D3YvF7Hp3_Gac_h(Iiq4gY*|03SrV{3|KC{Gr^(fX>+ubzLa z=X~O8m3UUo(XDwDI>^MQuBKAFCc&*eQvNj=etb!NOXFx$1`xJdZ`vks>paKsH4kOf z*T-kfOWGseU%bggw`hMf;Y=1WMXKmSGc&V>*N(Ub^XsAP!#_kOr%4q=q_q+6XSA?} zBGqDTPu;6!3Z=_iTl#x@DVo+b7q7~C=lTMxYijCS{1}zD{>)OvW8%q&3dm)#L5x*$ z`x|L#H*76L#<5U*qxhm!DcyzVeDZ_((8uhZxu%0H_WvRdFF`JEsUtWYd3bqamR;RgXqH;!57Lu#f{XFaY$z z8SWzmujW31dZiLfgEn(<5b>!H-YLnXcMQp><>V**T~X3ZD{Uljipl-P%Zo{E0E_D2 z?+$y!G|7BoY!Vd1jrVDgone|iPO${)$Iv3n{v+(JFj`IuuIuqP;N8}N(0Yt>qmVg? zp*(L}ujw13pi#;3D*@g-K_0_*%r)VZ*!faSm6at`KzkC{j0+HQ@UBT_sADS|ycetd z?T>n}-{EuLECatUbt&kSRO%Wn(-gp{pW)6(sGRox=QaTPh^%8FR<{Hzis@DB2@ndw zmSJtxE0RoSuT)o)>Z-{LHz)=%7R>WfWNzLBG$g~i2i!&a>zx-v@9qU5ZKar&{h8np zil@L!vx5FHF4w?^32W=R?(v3dlyT;mpM;I^U@d=z9eX_?FSuEHAB~HcUR08>pM&2PsqVdDnP^o ziO-IbYOAC`3U(a;GXRhZVPU`-wD0+<-g~a5)ez62##RK>RXO6<7^WO^6zalVj4#TQ zb985a<87Wlc6P01FT+}5KCZI3VFyDYnTyT)`yp44|L#eT1_Awbb&4i*5TQOL){(!G z75>6EV3;{nfpw$906;^Pm0GUPFcGV(ilEsaAH`M=6K<|{2(_`X0hbu1&Q8XvME^=f z(j_43*c;#JZ}2i28d_Q;=VjHirAIP_yt3Y=BmC^9fLZz__V3E0#lSo1#gJnzs7{Z? z-RT|DPinpWp!;6hGs;K30==+IZmJ_Nt(-g#0xnN@Ke|!kobl`kap^-QWIjQ;aX;)0 zH$CU63FX+3wXwB&xz^&dncs)BBv&&v2~b5E$?NaTH?&}^#~l1uxa;yr%;-jRz>4^z z!MjNy6f9GT^IhV4kd<=6GdirYG9}86H8ve<2_OevJ)cDFg1WZ&*?(%i_0_teZkrAr z^aFx5JB4l4P(Yp&OxhtO7|EHrUIEoaVuX~Y>LSC#4&$km+1?VbK)brM!kMp0O>BWtkQyZtv z_?^T){AqEF4E<{_LQQ?~;V*vZ74seQFXoUWC|jAhJQ!6x@~wIORpj{k`$p06#NDuL z`E&lFmfv?b2Y2_Jc6P3}v$~b0MQ(3GuTz3f0T`+)boU6mp(pgJ_Lefb7%h937dmn_ zuJy0pv#}W^+<=6q%1gfQ%x4poO&QbVq_OxY(?Vb+DAmebkM~{q^@ho=)aL#E@Qiu4 z&)qB!Bel^<(?v~v{2!f5qWWX^(h`!8kNZJ$_AGe@M7QIV@ zv$Wm3)AXZoaDcxAefG8TiAZ_7Bwx4J>1z7Hd54)2XN&)hWPJ6}r0icHY`UTI&h2=l z`{t>u`OSYh5Cp_b=b3(lyZEo#_9ft4a

XI(puHF0Ii=b0;Hssb5 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 Date: Sat, 31 Oct 2026 04:02:34 +0000 Subject: [PATCH 15/20] Rename 2026_10_31_03_59_17_screenshot.png to homepage.png --- ...6_10_31_03_59_17_screenshot.png => homepage.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/assets/{2026_10_31_03_59_17_screenshot.png => homepage.png} (100%) diff --git a/docs/assets/2026_10_31_03_59_17_screenshot.png b/docs/assets/homepage.png similarity index 100% rename from docs/assets/2026_10_31_03_59_17_screenshot.png rename to docs/assets/homepage.png From 6483df168a0d5c94a39c8855d55c193441f9be0c Mon Sep 17 00:00:00 2001 From: Aether Date: Sat, 31 Oct 2026 04:02:47 +0000 Subject: [PATCH 16/20] Delete docs/assets/ryotunes-hero.svg --- docs/assets/ryotunes-hero.svg | 65 ----------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 docs/assets/ryotunes-hero.svg diff --git a/docs/assets/ryotunes-hero.svg b/docs/assets/ryotunes-hero.svg deleted file mode 100644 index cd41f06e..00000000 --- a/docs/assets/ryotunes-hero.svg +++ /dev/null @@ -1,65 +0,0 @@ - - Ryotunes — Music, shaped for Ryoku - A restrained Ryoku-inspired banner for the Ryotunes desktop music application. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - RYOKU // MUSIC - RYOTUNES - Music, shaped for the Ryoku desktop. - - - - NATIVE AUDIO - - - LIVE THEMING - - - WEBKIT HIBERNATION - - - - - - - - - - PLAYBACK LIVES ON - UI MAY DISAPPEAR - - - - - - From bc722e2be9366372bd1b0af504b15f3abdded371 Mon Sep 17 00:00:00 2001 From: Aether Date: Sat, 31 Oct 2026 04:02:55 +0000 Subject: [PATCH 17/20] Delete docs/assets/ryotunes-surface.svg --- docs/assets/ryotunes-surface.svg | 135 ------------------------------- 1 file changed, 135 deletions(-) delete mode 100644 docs/assets/ryotunes-surface.svg diff --git a/docs/assets/ryotunes-surface.svg b/docs/assets/ryotunes-surface.svg deleted file mode 100644 index 1e0a79f5..00000000 --- a/docs/assets/ryotunes-surface.svg +++ /dev/null @@ -1,135 +0,0 @@ - -Illustrated Ryotunes v2.3 Home surface -A vector artwork based on Ryotunes Home, showing navigation, a listening console, artist cards and the bottom player. - - - - - - - - - - - - - - - - - - - - - - - - - -RYOTUNES -RYOKU // MUSIC - - - - -Ryotunes -LISTEN - - - - - Home - - - Search - - - Library - - -PLAYLISTS - - Liked music - Night drive - On repeat - - - - -Follow System -Ryoku palette live - - -HOME / 08:44 -Good morning -Your listening console, shaped by what is already playing. - - - - -NOT -LIKE -US - -NOW PLAYING -Not Like Us -Kendrick Lamar - - -PAUSE - -QUEUE - - -SESSION -native libmpv -MPRIS connected -Ryoku palette live - - -Familiar artists -SEE ALL - - - - - Juice WRLD - familiar artist - - OPEN - - - - Kendrick Lamar - in rotation - - - - Your mix - continue listening - - - RYOKU THEME - Follow System - - - - - - - - - -Not Like Us -Kendrick Lamar - - - - - - - - - -02:14 / 04:34 - \ No newline at end of file From 7c9c1dfc944c9d455c8ea7dafe71aabe04c26318 Mon Sep 17 00:00:00 2001 From: Aether Date: Sat, 31 Oct 2026 04:03:20 +0000 Subject: [PATCH 18/20] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08f677ee..b1229b62 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@

NixOS · x86_64-linux · Quickshell · Rust · GPL-3.0-or-later

- Illustrated Ryotunes interface with library navigation, listening view and playback controls + Illustrated interface preview. The appearance follows your chosen Ryoku palette and Ryotunes skin.
From d2cdfba2c1546b5f2636ff1c00333aa3876fb683 Mon Sep 17 00:00:00 2001 From: Aether Date: Sat, 31 Oct 2026 04:03:40 +0000 Subject: [PATCH 19/20] Replace homepage.svg with homepage.png in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1229b62..00486e59 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@

NixOS · x86_64-linux · Quickshell · Rust · GPL-3.0-or-later

- + Illustrated interface preview. The appearance follows your chosen Ryoku palette and Ryotunes skin. From 503afddcdf43c1b6750b0fc8003f012bc4c28be7 Mon Sep 17 00:00:00 2001 From: Aether Date: Sat, 31 Oct 2026 04:05:58 +0000 Subject: [PATCH 20/20] Update README to reflect app's purpose and features --- README.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 00486e59..04b7cb10 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,7 @@

RyotunesNix

-

Music, shaped for the Ryoku desktop.

- -

- A native Quickshell music player for NixOS.
- Built with Rust, QML and the Ryoku design language. -

+

The native music app for Ryoku on NixOS.

Get started · @@ -18,11 +13,9 @@ Contribute

-

NixOS · x86_64-linux · Quickshell · Rust · GPL-3.0-or-later

- - Illustrated interface preview. The appearance follows your chosen Ryoku palette and Ryotunes skin. +