Skip to content

refactor(#299): スペクトラムのタップ実サンプルレートを解析器まで伝搬#303

Merged
GeneralD merged 8 commits into
mainfrom
feat/#299-spectrum-samplerate
Jul 5, 2026
Merged

refactor(#299): スペクトラムのタップ実サンプルレートを解析器まで伝搬#303
GeneralD merged 8 commits into
mainfrom
feat/#299-spectrum-samplerate

Conversation

@GeneralD

@GeneralD GeneralD commented Jul 4, 2026

Copy link
Copy Markdown
Owner

agent type breaking scope tests depends on

概要

#299 のうち 課題1(サンプルレートが 48kHz 固定) を解消する。CoreAudio process tap のミックスダウンは出力デバイスの実レートに追従するため、44.1kHz 環境では Hz→FFT bin のマッピングと帯域カットオフ(min_freq / max_freq)がズレていた。タップの実サンプルレートを capture 経路から解析器まで伝搬させる。

変更内容

  • ProcessTapEngine: 構築時に kAudioTapPropertyFormat からタップの実レートを読み取り公開(読めなければ 48000 フォールバック)。
  • StereoSamples(Entity): sampleRate フィールドを追加(既定 48000。capture 無しのプレースホルダ用で、解析器は空窓では走らない)。
  • AudioTapDataSourceImpl.latestSamples: エンジンからレートを読み、各窓に付与。AudioCaptureRepositoryStereoSamples をそのまま転送するので無改修
  • SpectrumUseCaseImpl: window.sampleRate を読み、メモ化 FrequencyAnalyzer のキーを (bars, sampleRate) に拡張 → デバイスのレート変更で再構築。
  • FrequencyAnalyzer は既に sampleRate 引数を受け取る設計だったので、変更は伝搬のみ。

背景・動機

#298 のレビュー(CodeRabbit / Copilot)で挙がったスペクトラム DSP のハードウェア依存 2 件のうち 1 件目。これにより物理周波数が実レートに依らず同じバーに載り、帯域カットオフも正確になる。

課題2(平滑化定数が 60fps 前提) は既存の調整済みの見た目を変え、実機 120Hz での再チューニングが必須なため、本 PR には含めず別対応とする(#299 に残す)。

テスト計画

  • FrequencyAnalyzerTests: 物理周波数のサンプルレート不変性(44.1kHz と 48kHz で 2kHz が同じバーに載る)
  • SpectrumUseCaseImplTests: 伝搬検証(同一 PCM・異なるレート → 帯域マッピングが変わる)+ Fake repository がレートを保持
  • 全テスト 1083 件パス、make lint クリーン

備考

  • 純粋リファクタ(視覚的なデフォルト挙動は 48kHz 環境では不変、44.1kHz 環境で正しくなる)。実機での視覚差分キャプチャはスペクトラム有効化 + TCC + 44.1kHz 出力が要るため省略。
  • バージョンを 2.20.1 に更新。

Refs #299, #298

Summary by CodeRabbit

  • New Features
    • Spectrum analysis now uses the capture tap’s actual sample rate to keep frequency results physically accurate across hardware.
    • Captured sample data now includes sample-rate metadata for downstream processing.
  • Bug Fixes
    • Fixed frequency-to-bar mapping to remain consistent when the device sample rate changes.
    • Improved audio capture start/stop and switching behavior to correctly follow the active capture’s sample rate.
  • Tests
    • Added/extended coverage for sample-rate resolution, analyzer reuse vs rebuild, and tap lifecycle scenarios.

GeneralD added 2 commits July 4, 2026 22:22
process tap のミックスダウンは出力デバイスのレートに追従するため、44.1kHz
環境では 48kHz 固定前提だと Hz→FFT bin マッピングと帯域カットオフ
(min_freq/max_freq) がズレる。#298 レビュー (CodeRabbit/Copilot) 指摘の
うち HW 依存 1 件目 (課題1) を解消する。

- ProcessTapEngine が構築時に kAudioTapPropertyFormat からタップの実レート
  を読み取り公開 (読めなければ 48000 フォールバック)。
- AudioTapDataSourceImpl.latestSamples が各 StereoSamples 窓に実レートを付与
  (StereoSamples に sampleRate フィールド追加、capture 無しのプレースホルダは
  48000 だが解析器は空窓で走らない)。Repository は StereoSamples をそのまま
  転送するので無改修。
- SpectrumUseCaseImpl が window.sampleRate を読み、メモ化 FrequencyAnalyzer の
  キーを (bars, sampleRate) に拡張 → デバイスのレート変更で再構築。

物理周波数が実レートに依らず同じバーに載り、帯域カットオフも正確になる。
FrequencyAnalyzer は既にレート引数を受け取る設計だったので、伝搬のみ。
サンプルレート不変性 (FrequencyAnalyzer) と伝搬 (SpectrumUseCase) を
ユニットテストで検証。

課題2 (リフレッシュレート非依存化) は既存の見た目を変え実機再チューニングが
要るため別対応。

Refs #299
Copilot AI review requested due to automatic review settings July 4, 2026 13:23
@GeneralD GeneralD self-assigned this Jul 4, 2026
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR propagates the audio process tap’s actual sample rate through the spectrum pipeline. The tap engine now resolves and exposes a real rate, samples carry it, and spectrum analyzer caching now keys on both bar count and sample rate.

Changes

Sample-rate propagation

Layer / File(s) Summary
ProcessTapEngine sample rate capture
Sources/AudioTapDataSource/ProcessTapEngine.swift, Tests/AudioTapDataSourceTests/ProcessTapEngineHelpersTests.swift, Tests/FrequencyAnalyzerTests/FrequencyAnalyzerTests.swift
ProcessTapEngine stores the tap’s resolved sample rate, reads the live tap format through CoreAudio, falls back to 48 kHz when needed, and is covered by helper and frequency-invariance tests.
StereoSamples sample rate field
Sources/Entity/StereoSamples.swift
StereoSamples adds a sampleRate property and initializer argument with a default of 48000.
AudioTapDataSourceImpl protocol abstraction and wiring
Sources/AudioTapDataSource/AudioTapDataSourceImpl.swift, Tests/AudioTapDataSourceTests/AudioTapDataSourceImplTests.swift
AudioTapDataSourceImpl now uses an AudioTapEngine protocol, a factory-based engine constructor, and tags returned samples with the engine sample rate; tests cover lifecycle and propagation behavior with a fake engine.
SpectrumUseCaseImpl analyzer memoization
Sources/SpectrumUseCase/SpectrumUseCaseImpl.swift, Tests/SpectrumUseCaseTests/SpectrumUseCaseImplTests.swift
Analyzer caching now depends on (bars, sampleRate), and tests cover reuse, rebuild, and differing output across rates.
Documentation and version bump
.claude/CLAUDE.md, Sources/VersionHandler/Resources/version.txt
Design notes were updated to describe sample-rate propagation, and the version was bumped to 2.20.1.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • GeneralD/lyra#296: Shares the same CoreAudio process-tap and spectrum pipeline path, and this PR extends it by propagating the tap’s real sample rate.
  • GeneralD/lyra#298: Touches the same SpectrumUseCaseImpl and FrequencyAnalyzer integration points that this PR updates for sample-rate-aware caching.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: propagating the tap’s real sample rate through the spectrum analyzer path.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#299-spectrum-samplerate

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

CoreAudio の process tap が出力デバイスの実サンプルレートに追従する前提に合わせて、キャプチャ経路からスペクトラム解析器までサンプルレートを伝搬し、Hz→FFT bin マッピングと min_freq / max_freq の解釈ズレを解消するリファクタです。

Changes:

  • ProcessTapEngine が tap のストリームフォーマットから実サンプルレートを読み取り、StereoSamplessampleRate を追加して窓ごとにタグ付け
  • SpectrumUseCaseImplwindow.sampleRate を用いて FrequencyAnalyzer(bars, sampleRate) でメモ化
  • サンプルレート伝搬に関するユニットテストを追加し、バージョンを 2.20.1 に更新

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Tests/SpectrumUseCaseTests/SpectrumUseCaseImplTests.swift サンプルレート伝搬により同一PCMでもバーが変わることを検証するテストを追加
Tests/FrequencyAnalyzerTests/FrequencyAnalyzerTests.swift 物理周波数がサンプルレート差に依らず同じバーにマップされることを検証
Sources/VersionHandler/Resources/version.txt バージョンを 2.20.1 に更新
Sources/SpectrumUseCase/SpectrumUseCaseImpl.swift window.sampleRate を解析器へ渡し、メモ化キーを (bars, sampleRate) に拡張
Sources/Entity/StereoSamples.swift StereoSamplessampleRate フィールドを追加(既定 48000)
Sources/AudioTapDataSource/ProcessTapEngine.swift tap のフォーマットから実サンプルレートを取得・公開
Sources/AudioTapDataSource/AudioTapDataSourceImpl.swift latestSamples が窓に tap の実サンプルレートを付与
.claude/CLAUDE.md #299 のサンプルレート伝搬方針を設計ドキュメントへ追記

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +20 to +24
/// Memoized analyzer plus the per-channel bar count and tap sample rate it
/// was built for. The displayed bar count is derived from the overlay width
/// (cava style), so it changes on resize, and the sample rate follows the
/// output device (#299) — rebuild when either changes.
private var analyzer: (bars: Int, sampleRate: Double, engine: FrequencyAnalyzer)?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agent action

ご指摘の通りです。ProcessTapEngine.sampleRate は tap 構築時に確定する let で、稼働中は更新されません。コメントを、tap 再生成(再生/一時停止・ソース変更)時に新デバイスレートを拾って再構築される、という実挙動に合わせて修正しました。6881450

Comment thread .claude/CLAUDE.md Outdated
**Spectrum cava-faithful smoothing + configurability (#297)**: `SpectrumPresenter.tick()` runs on the DisplayLink and applies a faithful port of cavacore.c (MIT): per-bar sensitivity scaling → gravity release → **non-normalized leaky integrator** (sustained energy compounds toward `1/(1-noise_reduction)` ≈ 4× at 0.77, so beats tower over one-frame transients — the reason cava's kick band reads prominent) → clamp at full height, with the gain auto-tuned from overshoot (cava's **autosens**, moved here from the UseCase). Three knob families are curated-default-but-configurable, following the philosophy of showing the good part without forcing setup: (1) **bar count is derived from the overlay size cava-style** (fixed `bar_width` + `bar_spacing`, count fills the track) — the View reports the track length via `SpectrumPresenter.updateBarTrackLength` (overlay width for vertical placements, height for horizontal), `targetBarCount` derives the count, and `SpectrumUseCase.magnitudes(style:barCount:)` rebuilds the `FrequencyAnalyzer` when the count changes; (2) **band cutoffs** `min_freq`/`max_freq` (default 40 Hz–14 kHz) — `FrequencyAnalyzer` maps Hz→FFT bin (at the 48 kHz tap rate) and log-divides that range; (3) **gradient direction** `frequency`/`amplitude`/`level` and **placement** `bottom`/`top`/`left`/`right`/`underlay` — `left`/`right` rotate the bars into horizontal columns growing inward, `spectrumBarRects` computing an edge-anchored growth axis vs. an edge-parallel track axis so one geometry covers all four edges (`SpectrumGradientDirection`, `SpectrumPlacement` in Entity). The growth-axis size is `height_ratio` (fraction of the axis) plus an optional absolute clamp `min_height`/`max_height` in points (CSS `min-height`/`max-height` semantics, min wins on conflict) — the pure testable `spectrumBarStripDepth` free function resolves it, so a ratio-based length stays sane across very different displays (e.g. capping a horizontal placement on an ultrawide).
**Spectrum cava-faithful smoothing + configurability (#297)**: `SpectrumPresenter.tick()` runs on the DisplayLink and applies a faithful port of cavacore.c (MIT): per-bar sensitivity scaling → gravity release → **non-normalized leaky integrator** (sustained energy compounds toward `1/(1-noise_reduction)` ≈ 4× at 0.77, so beats tower over one-frame transients — the reason cava's kick band reads prominent) → clamp at full height, with the gain auto-tuned from overshoot (cava's **autosens**, moved here from the UseCase). Three knob families are curated-default-but-configurable, following the philosophy of showing the good part without forcing setup: (1) **bar count is derived from the overlay size cava-style** (fixed `bar_width` + `bar_spacing`, count fills the track) — the View reports the track length via `SpectrumPresenter.updateBarTrackLength` (overlay width for vertical placements, height for horizontal), `targetBarCount` derives the count, and `SpectrumUseCase.magnitudes(style:barCount:)` rebuilds the `FrequencyAnalyzer` when the count changes; (2) **band cutoffs** `min_freq`/`max_freq` (default 40 Hz–14 kHz) — `FrequencyAnalyzer` maps Hz→FFT bin (at the tap's actual sample rate, propagated from the capture path — #299) and log-divides that range; (3) **gradient direction** `frequency`/`amplitude`/`level` and **placement** `bottom`/`top`/`left`/`right`/`underlay` — `left`/`right` rotate the bars into horizontal columns growing inward, `spectrumBarRects` computing an edge-anchored growth axis vs. an edge-parallel track axis so one geometry covers all four edges (`SpectrumGradientDirection`, `SpectrumPlacement` in Entity). The growth-axis size is `height_ratio` (fraction of the axis) plus an optional absolute clamp `min_height`/`max_height` in points (CSS `min-height`/`max-height` semantics, min wins on conflict) — the pure testable `spectrumBarStripDepth` free function resolves it, so a ratio-based length stays sane across very different displays (e.g. capping a horizontal placement on an ultrawide).

**Spectrum sample-rate propagation (#299)**: the process-tap mixdown follows the current output device, so its rate is 44.1 kHz, 48 kHz, or whatever the hardware runs at — not a fixed 48 kHz. `ProcessTapEngine` reads the tap's real rate from `kAudioTapPropertyFormat` at construction and exposes it; `AudioTapDataSourceImpl.latestSamples` tags each `StereoSamples` window with it (an Entity field, default 48000 for the no-capture placeholder that the analyzer never runs on); `SpectrumUseCaseImpl` reads `window.sampleRate` and keys the memoized `FrequencyAnalyzer` on `(bars, sampleRate)`, so a device rate change rebuilds it. This keeps a physical frequency on the same visual bar and the `min_freq`/`max_freq` cutoffs accurate regardless of the hardware rate. (#299 also tracks a second, decoupled item — refresh-rate-independent smoothing — which changes the tuned look and needs real-device re-tuning, so it ships separately.)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agent action

CLAUDE.md の #299 記述も同様に、ProcessTapEngine.sampleRate が構築時確定の let であり、tap 再生成時のみ新デバイスレートが反映される(稼働中のライブな切替には追従しない)点を明記しました。6881450

@codecov

codecov Bot commented Jul 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.36170% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
Sources/AudioTapDataSource/ProcessTapEngine.swift 73.68% 5 Missing ⚠️

📢 Thoughts on this report? Let us know!

GeneralD added 5 commits July 5, 2026 01:42
ProcessTapEngine.sampleRate は tap 構築時に一度だけ読み取る let であり、
稼働中のデバイス切替では更新されない。よって「device rate change rebuilds
the analyzer」という表現は過大で、正確には「tap 再生成時に新レートを拾い
再構築される」が正しい。

SpectrumUseCaseImpl.swift のメモ化コメントと CLAUDE.md #299 ノートを
いずれも実挙動(tap lifetime 中は固定、tap 再生成で更新)に合わせて修正。

Addresses Copilot review comments: #3523264777, #3523264792
…mSampleRate

Pull the `format.mSampleRate > 0` judgment out of the CoreAudio call
site in `streamSampleRate(of:)` into a standalone, dependency-free free
function `tapSampleRate(from: AudioStreamBasicDescription) -> Double?`.

`streamSampleRate` now delegates to it after a successful
`AudioObjectGetPropertyData` call, keeping the OS-boundary crossing
isolated while making the pure decision unit-testable.

Tests added to ProcessTapEngineHelpersTests:
  - 44.1 kHz → 44100
  - 48 kHz → 48000
  - 0 Hz → nil (no audio stream)
  - -1 Hz → nil (malformed descriptor)
…e paths

The existing tests exercised two distinct SpectrumUseCaseImpl instances,
so the cache-hit branch of `resolvedAnalyzer` (return analyzer.engine
when bars and sampleRate both match) was never reached.

Two new tests on the *same* use-case instance:

- analyzerCachedOnMatchingKey: identical bar count + sampleRate → first
  == second, proving the analyzer is reused (covers the cache-hit line).
- analyzerRebuildsOnSampleRateChange: mutates FakeAudioCaptureRepository
  to yield a 44.1 kHz window after the initial 48 kHz call; first !=
  second, proving the FrequencyAnalyzer is rebuilt when the tap rate
  changes on the same instance.
…mplerate

# Conflicts:
#	Sources/VersionHandler/Resources/version.txt
latestSamples の sampleRate タグ付け(#299 の肝)と startTap/stopTap の
tap 存在パスは、engine が ProcessTapEngine(ライブ CoreAudio タップ必須)に
直結していたためテスト不能で未カバーだった。ProcessTapEngine を
AudioTapEngine プロトコルの背後にシーム化し、生成を factory 注入にすることで:

- fake tap を注入してライブ音声デバイス無しで tap 存在パスを検証可能に
- 散在していた #available / as? ProcessTapEngine のチェックが factory に集約
  され production コードも簡潔化

FakeTapEngine で latestSamples のレートタグ付け・再起動時の旧タップ破棄・
factory nil 時の失敗・stopTap の破棄+クリアをカバー(4 テスト追加)。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Sources/SpectrumUseCase/SpectrumUseCaseImpl.swift (1)

20-26: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cache key omits other analyzer-affecting SpectrumStyle fields.

resolvedAnalyzer rebuilds only on bars/sampleRate changes, but the constructed FrequencyAnalyzer also depends on style.fftSize, minDb, maxDb, scale, minFrequency, and maxFrequency. If any of those change without a bar-count or sample-rate change, the stale cached engine is returned, silently mismatching the newly fetched style.fftSize-sized window.

This predates this PR (previously keyed by bars alone), but since the cache key is being revisited here, consider broadening it.

♻️ Possible direction: widen the cache key
-    private var analyzer: (bars: Int, sampleRate: Double, engine: FrequencyAnalyzer)?
+    private var analyzer: (bars: Int, sampleRate: Double, style: SpectrumStyle, engine: FrequencyAnalyzer)?
...
-        if let analyzer, analyzer.bars == bars, analyzer.sampleRate == sampleRate {
+        if let analyzer, analyzer.bars == bars, analyzer.sampleRate == sampleRate, analyzer.style == style {
             return analyzer.engine
         }

(Requires SpectrumStyle: Equatable.)

Can you confirm whether SpectrumStyle's other fields (scale, frequency bounds, fftSize) are ever mutated at runtime on a live instance independent of a resize/rate change?


Also applies to: 65-86

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/SpectrumUseCase/SpectrumUseCaseImpl.swift` around lines 20 - 26, The
analyzer cache key in SpectrumUseCaseImpl only tracks bars and sampleRate, but
resolvedAnalyzer also depends on SpectrumStyle fields like fftSize, minDb,
maxDb, scale, minFrequency, and maxFrequency. Update the memoization in
SpectrumUseCaseImpl (especially the analyzer storage and resolvedAnalyzer logic)
so any change to these style properties forces a rebuild, likely by widening the
cached key and making SpectrumStyle Equatable if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Sources/SpectrumUseCase/SpectrumUseCaseImpl.swift`:
- Around line 20-26: The analyzer cache key in SpectrumUseCaseImpl only tracks
bars and sampleRate, but resolvedAnalyzer also depends on SpectrumStyle fields
like fftSize, minDb, maxDb, scale, minFrequency, and maxFrequency. Update the
memoization in SpectrumUseCaseImpl (especially the analyzer storage and
resolvedAnalyzer logic) so any change to these style properties forces a
rebuild, likely by widening the cached key and making SpectrumStyle Equatable if
needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 72794a20-a212-443b-9526-ba5c3ec0051c

📥 Commits

Reviewing files that changed from the base of the PR and between 4e08d67 and be24142.

📒 Files selected for processing (10)
  • .claude/CLAUDE.md
  • Sources/AudioTapDataSource/AudioTapDataSourceImpl.swift
  • Sources/AudioTapDataSource/ProcessTapEngine.swift
  • Sources/Entity/StereoSamples.swift
  • Sources/SpectrumUseCase/SpectrumUseCaseImpl.swift
  • Sources/VersionHandler/Resources/version.txt
  • Tests/AudioTapDataSourceTests/AudioTapDataSourceImplTests.swift
  • Tests/AudioTapDataSourceTests/ProcessTapEngineHelpersTests.swift
  • Tests/FrequencyAnalyzerTests/FrequencyAnalyzerTests.swift
  • Tests/SpectrumUseCaseTests/SpectrumUseCaseImplTests.swift

streamSampleRate は「address 構築 + AudioObjectGetPropertyData + フォーマット
判定 + ?? 48000 フォールバック」を1つの private メソッドに束ねており、
ライブタップ成功後にしか呼ばれず未カバーだった。責務を分離:

- resolvedTapSampleRate(from:) — フォールバック判定を純粋 free 関数に抽出。
  nil/正/ゼロ/負の全パスをテスト(既存の tapSampleRate と同じパターン)。
- liveTapFormat(of:) — 唯一 irreducible な CoreAudio syscall を薄く隔離し
  internal 化。bogus object id で失敗パス(address 構築+syscall+guard)を
  テスト(processPid が既に AudioObjectGetPropertyData を bogus id で安全に
  呼んでいる前例に倣う)。

結果、sampleRate 読み取り経路の未カバーは 2 行のみ(init 行 48 と
liveTapFormat の成功 return)——どちらも実オーディオデバイス必須で証明可能に
irreducible。残りは全て回収。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Tests/AudioTapDataSourceTests/ProcessTapEngineHelpersTests.swift (1)

9-13: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate format(rate:) helper across two test suites.

Same private helper is redefined in TapSampleRateTests and ResolvedTapSampleRateTests. Consider hoisting it to a shared file-private top-level function to avoid drift if the construction logic ever changes.

♻️ Proposed dedup
+private func format(rate: Double) -> AudioStreamBasicDescription {
+    var f = AudioStreamBasicDescription()
+    f.mSampleRate = rate
+    return f
+}
+
 `@Suite`("tapSampleRate")
 struct TapSampleRateTests {
-    private func format(rate: Double) -> AudioStreamBasicDescription {
-        var f = AudioStreamBasicDescription()
-        f.mSampleRate = rate
-        return f
-    }
     ...
 }

 `@Suite`("resolvedTapSampleRate")
 struct ResolvedTapSampleRateTests {
-    private func format(rate: Double) -> AudioStreamBasicDescription {
-        var f = AudioStreamBasicDescription()
-        f.mSampleRate = rate
-        return f
-    }
     ...
 }

Also applies to: 38-42

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/AudioTapDataSourceTests/ProcessTapEngineHelpersTests.swift` around
lines 9 - 13, The private format(rate:) helper is duplicated in both
TapSampleRateTests and ResolvedTapSampleRateTests, so move the shared
AudioStreamBasicDescription निर्माण logic into a single file-private top-level
helper in ProcessTapEngineHelpersTests.swift and update both suites to call it.
Keep the helper name and behavior consistent so future changes only need to be
made in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Tests/AudioTapDataSourceTests/ProcessTapEngineHelpersTests.swift`:
- Around line 9-13: The private format(rate:) helper is duplicated in both
TapSampleRateTests and ResolvedTapSampleRateTests, so move the shared
AudioStreamBasicDescription निर्माण logic into a single file-private top-level
helper in ProcessTapEngineHelpersTests.swift and update both suites to call it.
Keep the helper name and behavior consistent so future changes only need to be
made in one place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: faaee009-f173-4e6b-9346-5465c420f0ca

📥 Commits

Reviewing files that changed from the base of the PR and between be24142 and 361b154.

📒 Files selected for processing (2)
  • Sources/AudioTapDataSource/ProcessTapEngine.swift
  • Tests/AudioTapDataSourceTests/ProcessTapEngineHelpersTests.swift

@GeneralD GeneralD merged commit 294da5a into main Jul 5, 2026
6 of 8 checks passed
@GeneralD GeneralD deleted the feat/#299-spectrum-samplerate branch July 5, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants