v0.1.0 — Gapless playback, BPM detection, Artists view, speed control#7
v0.1.0 — Gapless playback, BPM detection, Artists view, speed control#7devohmycode merged 3 commits intomainfrom
Conversation
- Updated application version to 0.0.7 in installer.iss. - Added playback speed control with adjustable presets. - Implemented episode progress tracking for podcasts. - Introduced download functionality for podcast episodes. - Updated README with new features including playback speed, sleep timer, and episode downloads. - Added .gitignore for SpecStory project files.
- Added BPM detection functionality using a new BpmDetector service. - Introduced gapless playback support with a GaplessSampleProvider. - Updated TrackInfo model to include BPM property. - Enhanced audio player service to handle gapless transitions. - Modified MainWindow to support sorting by BPM and display BPM in track metadata. - Updated database schema to include BPM column in tracks table.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (17)
📝 WalkthroughWalkthroughThis PR enhances the audio player with BPM detection and metadata storage, gapless playback with pre-loaded next-track support, variable playback speed control, podcast episode downloading with progress resumption, artist browsing views, and sleep timer functionality. It also updates configuration, library schema with BPM column, and installer version to 0.1.0. Changes
Sequence DiagramsequenceDiagram
actor User
participant UI as MainWindow
participant Queue as QueueManager
participant Player as AudioPlayerService
participant Gapless as GaplessSampleProvider
participant Speed as SpeedControlSampleProvider
participant Output as NAudio Output
User->>UI: Request next track
UI->>Queue: PeekNext()
Queue-->>UI: Next TrackInfo
UI->>Player: PrepareNextTrack(nextTrack)
Player->>Player: Build audio chain for next
Player->>Gapless: QueueNext(nextChain)
Gapless-->>Player: Queued ✓
Player->>Gapless: PlayTrack(currentChain)
Gapless->>Speed: Read() [current track]
Speed->>Output: Write samples
Note over Gapless: Current track exhausted
Gapless->>Speed: Read() [switch to next]
Gapless->>Gapless: SourceTransitioned event
Speed->>Output: Write next track samples
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip CodeRabbit can enforce grammar and style rules using `languagetool`.Configure the |
Review Summary by Qodov0.1.0 — Gapless playback, BPM detection, Artists view, speed control, and podcast enhancements
WalkthroughsDescription• **Gapless playback** — seamless track transitions via GaplessSampleProvider with pre-loading and automatic fallback for incompatible formats • **BPM detection** — multi-band onset detection with comb filter analysis; reads from file tags, analyzes audio on demand, displayed and sortable in track list, persisted to database • **Playback speed control** — adjustable 0.25x–4x with 9 presets via SpeedControlSampleProvider using linear interpolation • **Artists and Albums views** — browse library grouped by artist/album with circular artwork cards and detail views with search/sort capabilities • **Sleep timer** — auto-stop after 15/30/45/60/90 minutes • **Podcast enhancements** — episode downloads (offline playback), playback resume with progress persistence, unread episode badges on subscriptions • **Folder drag & drop** — drop folders onto window to scan and add to library • **Custom Acrylic ColorPicker** — enhanced color picker with full WinUI flyout for tint and fallback colors • **Window drag fix** — resolved collapse animation timer preventing vertical window movement • **Database schema updates** — added bpm column to tracks table with migration support • **Audio chain integration** — SpeedControlSampleProvider and GaplessSampleProvider integrated into AudioPlayerService Diagramflowchart LR
A["Audio Source"] --> B["GaplessSampleProvider"]
B --> C["SpeedControlSampleProvider"]
C --> D["Output"]
E["BpmDetector"] -.-> F["Track Metadata"]
G["PodcastService"] --> H["Episode Downloads"]
G --> I["Progress Tracking"]
J["LibraryManager"] --> K["BPM Storage"]
L["UI Layer"] --> M["Artists View"]
L --> N["Speed Control"]
L --> O["Sleep Timer"]
File Changes1. Audiomatic/MainWindow.xaml.cs
|
Code Review by Qodo
1. Speed buffer out-of-range
|
| for (int i = 0; i < outputFrames; i++) | ||
| { | ||
| int neededSample = ((int)_sourcePosition + 1) * _channels; | ||
| while (neededSample >= _sourceBufferCount) | ||
| { | ||
| EnsureCapacity(_sourceBufferCount + 4096); | ||
| int read = _source.Read(_sourceBuffer, _sourceBufferCount, 4096); | ||
| if (read == 0) | ||
| return written; | ||
| _sourceBufferCount += read; | ||
| } | ||
|
|
||
| int srcFrame = (int)_sourcePosition; | ||
| float frac = (float)(_sourcePosition - srcFrame); | ||
|
|
||
| for (int ch = 0; ch < _channels; ch++) | ||
| { | ||
| float s0 = _sourceBuffer[srcFrame * _channels + ch]; | ||
| float s1 = _sourceBuffer[(srcFrame + 1) * _channels + ch]; | ||
| buffer[offset + written++] = s0 + (s1 - s0) * frac; | ||
| } |
There was a problem hiding this comment.
1. Speed buffer out-of-range 🐞 Bug ✓ Correctness
SpeedControlSampleProvider.Read can index past _sourceBuffer for multi-channel audio because it only guarantees samples up to the start of the next frame, but then reads all channels of that next frame. This can throw IndexOutOfRangeException during non-1.0 playback speeds.
Agent Prompt
### Issue description
`SpeedControlSampleProvider.Read` can read beyond `_sourceBuffer` when interpolating the `(srcFrame + 1)` frame for multi-channel audio.
### Issue Context
The current guard uses `neededSample = (srcFrame+1)*channels` which only ensures the first sample index of the next frame is available, but interpolation reads all channels of that next frame.
### Fix Focus Areas
- Audiomatic/Services/SpeedControlSampleProvider.cs[49-69]
### Suggested change
Update the guard to ensure the full next frame exists, e.g. compute:
- `int neededSamples = (srcFrame + 2) * _channels;`
- while (`neededSamples > _sourceBufferCount`) read more.
Also consider handling the end-of-source case by padding with zeros or duplicating last frame to avoid early-return artifacts.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| public float PlaybackSpeed | ||
| { | ||
| get => _playbackSpeed; | ||
| set | ||
| { | ||
| _playbackSpeed = Math.Clamp(value, 0.25f, 4.0f); | ||
| if (_speedProvider != null) | ||
| _speedProvider.Speed = _playbackSpeed; | ||
| if (!_useNAudio) | ||
| _mediaPlayer.PlaybackSession.PlaybackRate = _playbackSpeed; | ||
| } |
There was a problem hiding this comment.
2. Gapless next speed stale 🐞 Bug ✓ Correctness
AudioPlayerService.PlaybackSpeed updates only the current speed provider; if a next track was already prepared for gapless playback, its SpeedControlSampleProvider keeps the old speed. After the gapless transition, playback speed can unexpectedly jump to the stale value.
Agent Prompt
### Issue description
When `PrepareNextTrack` has already created `_nextSpeedProvider`, changing `PlaybackSpeed` only updates the current `_speedProvider` and not the queued `_nextSpeedProvider`, so speed can change unexpectedly after a gapless transition.
### Issue Context
`PrepareNextTrack` snapshots the current `_playbackSpeed` into `_nextSpeedProvider`.
### Fix Focus Areas
- Audiomatic/Services/AudioPlayerService.cs[562-572]
- Audiomatic/Services/AudioPlayerService.cs[208-238]
### Suggested change
In `PlaybackSpeed` setter, also update `_nextSpeedProvider.Speed` when present:
```csharp
if (_nextSpeedProvider != null)
_nextSpeedProvider.Speed = _playbackSpeed;
```
Optionally, if a next track is queued and speed changes, consider re-queuing to avoid internal state mismatch.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| public static string GetDownloadPath(string audioUrl) | ||
| { | ||
| // Use a hash of the URL to avoid filesystem issues with long/special-char URLs | ||
| var hash = Convert.ToHexString( | ||
| System.Security.Cryptography.SHA256.HashData( | ||
| System.Text.Encoding.UTF8.GetBytes(audioUrl)))[..24]; | ||
| // Keep original extension only if NAudio supports it, otherwise default to .mp3 | ||
| var ext = Path.GetExtension(new Uri(audioUrl).AbsolutePath); | ||
| if (string.IsNullOrEmpty(ext) || !SupportedDownloadExtensions.Contains(ext)) | ||
| ext = ".mp3"; | ||
| return Path.Combine(DownloadsDir, hash + ext); | ||
| } | ||
|
|
||
| public static bool IsDownloaded(string audioUrl) | ||
| { | ||
| var path = GetDownloadPath(audioUrl); | ||
| return File.Exists(path); | ||
| } |
There was a problem hiding this comment.
3. Invalid podcast url crash 🐞 Bug ⛯ Reliability
PodcastService.GetDownloadPath constructs a Uri from the episode enclosure URL without validation, and the UI calls IsDownloaded/GetDownloadPath for every episode. A malformed RSS enclosure URL can throw UriFormatException and crash episode rendering/download checks.
Agent Prompt
### Issue description
`PodcastService.GetDownloadPath` calls `new Uri(audioUrl)` without validation. If a feed provides a malformed enclosure URL, this throws and can crash the UI when calling `IsDownloaded`, `DeleteDownload`, etc.
### Issue Context
`FetchEpisodesAsync` stores enclosure URLs without validating they are absolute/valid.
### Fix Focus Areas
- Audiomatic/Services/PodcastService.cs[51-75]
- Audiomatic/Services/PodcastService.cs[155-172]
### Suggested change
Option A (preferred): make `GetDownloadPath` defensive:
- `if (!Uri.TryCreate(audioUrl, UriKind.Absolute, out var uri))` then fall back to parsing extension from the raw string (or default `.mp3`).
- Wrap extension extraction in try/catch.
Option B: validate in `FetchEpisodesAsync` before adding episodes (only add if `Uri.TryCreate(..., Absolute, out _)`).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Summary
GaplessSampleProvider; automatic fallback for incompatible formatsSpeedControlSampleProvider(linear interpolation)ColorPickerflyoutNew files
Services/GaplessSampleProvider.cs— ISampleProvider chain for gapless transitionsServices/SpeedControlSampleProvider.cs— playback speed via sample interpolationServices/BpmDetector.cs— multi-band onset + comb filter BPM analysisTest plan
Summary by CodeRabbit
Release Notes
New Features
Chores