Skip to content

harmonics voice domain — play + say, text→notes core, offline audio (issue #1)#3

Merged
OriNachum merged 40 commits into
mainfrom
build/harmonics-voice-inc1
Jul 8, 2026
Merged

harmonics voice domain — play + say, text→notes core, offline audio (issue #1)#3
OriNachum merged 40 commits into
mainfrom
build/harmonics-voice-inc1

Conversation

@OriNachum

Copy link
Copy Markdown
Contributor

harmonics voice domain — the non-TTS audio surface (issue #1)

Builds the domain from issue #1: an agent/robot's own non-speech voice. It renders live meaning — not words — into short, pleasant sonic gestures, the first-person inverse of a spectator soundtrack.

How it was built

Full devague pipeline, human-gated at each seam:

  1. /think → a converged spec (docs/specs/) — 16 claims, 15 honesty conditions.
  2. /spec-to-plan → a buildable plan (docs/plans/) — 12 tasks, 6 dependency waves, 30/30 coverage.
  3. /assign-to-workforce → each wave fanned out to parallel subagents in isolated git worktrees, every merge TDD-gated (tests pass before and after).

The design spine — the voice ≠ soundtrack distinction — came from a design conversation: a soundtrack narrates events in the third person off a log; a voice is first-person, driven by the agent's own axes, and (for say) its melody tracks the sentence so a listener can approximately follow it.

What's in it

Pure, offline, dependency-free text→notes core; audio isolated on top.

  • axes.py — the five-axis vocabulary (intent, confidence, urgency, state, identity).
  • notes.py — the NoteEvent core + JSON / MIDI-like serialization (the test surface and robot representation).
  • mapping.py — the design spine: axes → sonic parameters over a consonant pentatonic scale with a velocity ceiling, so urgent is attention-grabbing but never an alarm (proven by test: urgent vs calm differ only in timing, never pitch/loudness).
  • identity.py — per-agent voice-prints (key + instrument) derived from the identity string, so you can tell who is speaking; palette overrides supported.
  • variation.py — deterministic micro-variation keyed by a --seq nonce (never wall-clock/entropy).
  • inference.py — offline sentence → axes (a documented cue table; no model/network).
  • contour.py — text → melodic contour (one note per word) so a human can follow sound ↔ text; still non-speech.
  • stress.py*word* / ALL-CAPS emphasis that raises pitch/velocity within the ceiling.
  • audio/ — pure-stdlib offline WAV synth; live playback via a lazy optional import so the core never needs an audio device.
  • Verbs: harmonics play --intent … [--as] [--seq] and harmonics say "<sentence>" — dry-run by default, --json / --out / --wav / --midi / --play.
  • docs/ear-test-protocol.md — blind-listening protocols with better-than-chance bars for the human-discrimination claims.

The scaffold's culture-agent-template self-descriptions are replaced with the real voice mission everywhere (parser, explain, learn, overview, README).

Verification

  • 2230 tests pass; black / isort / flake8 / bandit clean; markdownlint clean.
  • The agent-first rubric gate teken cli doctor . --strict passes 26/26.
  • End-to-end proof: harmonics play --intent success --wav out.wav and harmonics say "done, tests all green" --wav out.wav both write valid RIFF/WAVE PCM 16-bit mono 44100 Hz, while import harmonics.audio.synth needs no audio device.

Open items (non-blocking, tracked as plan risks)

  • Which real playback backend to standardize on (the core stays dependency-free regardless).
  • Exact text→contour granularity (per-word today; per-syllable is a future refinement).
  • Final "pleasantness" ultimately needs a human ear — the bounded palette/velocity/tempo proxies reduce but don't eliminate subjectivity; hence the ear-test protocol.

Closes #1.

  • harmonics-cli (Claude)

🤖 Generated with Claude Code

OriNachum and others added 29 commits July 8, 2026 11:00
…hink → /spec-to-plan)

Converged spec + 12-task/6-wave plan for the agent "voice" domain: axes→notes
core, play + say verbs, text-legible say tune with expressive pitch/volume
stress, derived identity, --seq micro-variation. Includes the .devague frame +
plan state (the confirm/reject evidence trail).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The five-axis contract (intent/confidence/urgency/state/identity) every domain
module speaks — vocabulary + a validated frozen Axes record, dependency-free.
Sound decisions deliberately excluded (that is the mapping module's job). Laid
down before the wave fan-out so inference/mapping/verbs agree without a
cross-wave import dependency.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…ing state

- Backtick the CLI-usage placeholders (<i>/<agent>/<sentence>) so they aren't
  parsed as inline HTML; drop trailing periods from the H1 titles (MD026).
- Add .devague/** to the markdownlint ignores, alongside .afi/.teken.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Harmonics voice domain: text→notes core, play/say verbs, offline WAV synth

✨ Enhancement 📝 Documentation 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Adds the harmonics “non-speech voice” domain: offline, dependency-free text→notes core with audio
 layered on top.
• Introduces harmonics play (explicit axes) and harmonics say (sentence→axes+contour+stress),
 dry-run by default.
• Adds a pure-stdlib WAV synth plus extensive docs/spec/plan artifacts and new tests.
Diagram

graph TD
    A["axes.py"] --> B["inference.py"] --> C["say verb"]
    A --> D["mapping.py"] --> E["play verb"]
    F["identity.py"] --> D
    F --> G["contour.py"] --> C
    D --> H["notes.py"]
    G --> H
    H --> I["stress.py"] --> C
    H --> J["variation.py"] --> C
    J --> E
    H --> K[("audio/synth.py")]
    K --> L{{"simpleaudio / sounddevice"}}
    subgraph Legend
        direction LR
        _mod(["Pure module"]) ~~~ _db[("Audio/WAV")] ~~~ _ext{{"Optional external"}}
    end
Loading
High-Level Assessment

The chosen strategy—keep the text→notes core pure/stdlib-only and layer audio behind an isolated module with lazy optional playback imports—is the right architecture for deterministic testing and for avoiding mandatory audio dependencies. No meaningfully better alternative stands out for this increment.

Files changed (40) +6141 / -31

Enhancement (13) +2238 / -1
__init__.pyExpose audio synth entry points +16/-0

Expose audio synth entry points

• Re-exports render_wav/write_wav/play and DEFAULT_SAMPLE_RATE for callers.

harmonics/audio/init.py

synth.pyPure-stdlib WAV synth + lazy playback +245/-0

Pure-stdlib WAV synth + lazy playback

• Implements deterministic additive-sine synthesis to 16-bit PCM WAV, file writing, and optional live playback via lazy import of simpleaudio/sounddevice with structured errors.

harmonics/audio/synth.py

axes.pyDefine the expressive axis vocabulary (Axes) +105/-0

Define the expressive axis vocabulary (Axes)

• Adds allowed values for intent/confidence/urgency/state and a frozen validated Axes container with copy and dict helpers.

harmonics/axes.py

__init__.pyRegister new play/say verbs and update CLI description +9/-1

Register new play/say verbs and update CLI description

• Imports/registers 'play' and 'say' subcommands and replaces the template CLI description with the harmonics voice mission.

harmonics/cli/init.py

play.pyAdd 'harmonics play' command (axes→notes) +197/-0

Add 'harmonics play' command (axes→notes)

• Implements play: validates axes flags, derives identity signature, renders gesture notes, applies optional deterministic variation, and supports dry-run/JSON/file/WAV/play outputs.

harmonics/cli/_commands/play.py

say.pyAdd 'harmonics say' command (text→notes) +297/-0

Add 'harmonics say' command (text→notes)

• Implements say pipeline: parse emphasis, infer axes, derive signature, render per-word contour, shade by axes, apply stress and variation, and support dry-run/JSON/out/midi/wav/play outputs.

harmonics/cli/_commands/say.py

contour.pyRender text to a deterministic melodic contour +229/-0

Render text to a deterministic melodic contour

• Implements one-note-per-word contour using SHA-256 hashing for stable degree/velocity selection and punctuation-based timing adjustments within palette bounds.

harmonics/contour.py

identity.pyDerive per-agent voice signatures from identity +151/-0

Derive per-agent voice signatures from identity

• Implements SHA-256-based stable signature derivation (root pitch + instrument + seed) with override support and validation.

harmonics/identity.py

inference.pyInfer axes from sentences offline via cues +169/-0

Infer axes from sentences offline via cues

• Adds a deterministic cue-table inference function for intent/confidence/urgency/state with defined priority order and token/substring matching rules.

harmonics/inference.py

mapping.pyMap axes to gestures over consonant palette +348/-0

Map axes to gestures over consonant palette

• Implements pure axes→NoteEvent rendering using intent motifs plus confidence cadence, urgency timing/repetition, and state sustain shaping while enforcing consonant scale and velocity ceiling.

harmonics/mapping.py

notes.pyIntroduce NoteEvent and serialization utilities +134/-0

Introduce NoteEvent and serialization utilities

• Defines NoteEvent with validation plus JSON round-trip helpers and a MIDI-like tick export format for downstream synth/robot consumption.

harmonics/notes.py

stress.pyAdd emphasis parsing and stress application +218/-0

Add emphasis parsing and stress application

• Adds parse_emphasis for '*word*' and ALL-CAPS markers and apply_stress to lift pitch/velocity within the velocity ceiling, without changing key.

harmonics/stress.py

variation.pyDeterministic micro-variation via nonce +120/-0

Deterministic micro-variation via nonce

• Adds apply_variation to jitter timing and velocity in bounded ways as a pure function of a '--seq' nonce.

harmonics/variation.py

Tests (13) +2609 / -0
ear_harness.pyAdd offline/no-audio import harness helpers +163/-0

Add offline/no-audio import harness helpers

• Adds shared helpers used to assert modules remain stdlib-only and don’t require audio backends at import time.

tests/ear_harness.py

test_audio.pyTest WAV rendering and playback isolation +206/-0

Test WAV rendering and playback isolation

• Validates deterministic WAV bytes, WAV correctness, file writing behavior, and lazy optional playback backend handling.

tests/test_audio.py

test_axes.pyTest axes vocabulary validation +68/-0

Test axes vocabulary validation

• Covers allowed/invalid values, identity validation, and frozen Axes copy/dict utilities.

tests/test_axes.py

test_contour.pyTest text contour determinism and constraints +264/-0

Test text contour determinism and constraints

• Ensures per-word contour is deterministic, in-key, bounded by duration/velocity constraints, and respects punctuation rhythm.

tests/test_contour.py

test_ear_harness.pyTest ear harness helpers +182/-0

Test ear harness helpers

• Verifies the offline/no-third-party import assertions used across audio/core tests.

tests/test_ear_harness.py

test_identity.pyTest identity signature derivation and overrides +156/-0

Test identity signature derivation and overrides

• Covers deterministic derivation, validation of root/instrument, and override application/unknown key handling.

tests/test_identity.py

test_inference.pyTest cue-table inference and import hygiene +163/-0

Test cue-table inference and import hygiene

• Validates inference rule priority and matching behavior and enforces stdlib-only import policy.

tests/test_inference.py

test_mapping.pyTest mapping invariants (pleasantness, determinism, cadence) +246/-0

Test mapping invariants (pleasantness, determinism, cadence)

• Asserts per-intent motifs, consonant scale membership, velocity ceiling and minimum duration, urgent-vs-calm differences limited to timing/repetition, and confidence cadence behavior.

tests/test_mapping.py

test_notes.pyTest NoteEvent validation and serialization +162/-0

Test NoteEvent validation and serialization

• Covers range validation, JSON round-trips, and MIDI-like tick conversion semantics.

tests/test_notes.py

test_play.pyTest 'harmonics play' command behavior +194/-0

Test 'harmonics play' command behavior

• Covers dry-run outputs, JSON mode, determinism, --seq behavior, file capture via --out/--wav, and error handling.

tests/test_play.py

test_say.pyTest 'harmonics say' pipeline end-to-end +304/-0

Test 'harmonics say' pipeline end-to-end

• Covers word-tracking note count, dry-run output formats, determinism, axis shading effects, emphasis handling, and capture outputs including MIDI-like and WAV.

tests/test_say.py

test_stress.pyTest emphasis markers and stress effects +307/-0

Test emphasis markers and stress effects

• Verifies parsing of asterisks and ALL-CAPS and ensures stressed notes change pitch/velocity within bounds.

tests/test_stress.py

test_variation.pyTest deterministic micro-variation bounds and no-op baseline +194/-0

Test deterministic micro-variation bounds and no-op baseline

• Validates DEFAULT_NONCE baseline, determinism for fixed nonces, and bounded timing/velocity deltas.

tests/test_variation.py

Documentation (8) +601 / -29
CHANGELOG.mdAdd 0.5.0 release notes for voice domain +46/-0

Add 0.5.0 release notes for voice domain

• Documents the new voice domain modules, CLI verbs, audio backend, and docs; notes mission text replacements.

CHANGELOG.md

README.mdRewrite README around the harmonics voice mission +54/-16

Rewrite README around the harmonics voice mission

• Replaces template text with the five-axis design spine, voice-vs-soundtrack framing, status, and updated contributing guidance.

README.md

ear-test-protocol.mdAdd blind-listening ear-test protocol +190/-0

Add blind-listening ear-test protocol

• Defines human evaluation protocols and pass bars for identity/intent discrimination, tune↔phrase matching, and stress identification.

docs/ear-test-protocol.md

2026-07-08-harmonics-gives-an-agent-or-robot-its-own-non-spee.mdAdd devague buildable plan document +105/-0

Add devague buildable plan document

• Adds the markdown plan produced from the spec with tasks, waves, and dependencies.

docs/plans/2026-07-08-harmonics-gives-an-agent-or-robot-its-own-non-spee.md

2026-07-08-harmonics-gives-an-agent-or-robot-its-own-non-spee.mdAdd devague converged spec document +67/-0

Add devague converged spec document

• Adds the markdown spec describing claims and honesty conditions for the increment.

docs/specs/2026-07-08-harmonics-gives-an-agent-or-robot-its-own-non-spee.md

learn.pyUpdate learn prompt text for the voice mission +12/-6

Update learn prompt text for the voice mission

• Adjusts self-teaching content to match the harmonics voice domain instead of the template scaffolding.

harmonics/cli/_commands/learn.py

overview.pyAlign overview text with harmonics voice mission +1/-1

Align overview text with harmonics voice mission

• Minor update to overview output text to match the new domain framing.

harmonics/cli/_commands/overview.py

catalog.pyAdd explain docs for play/say and rewrite root description +126/-6

Add explain docs for play/say and rewrite root description

• Replaces template root text and adds detailed help entries for new verbs, including usage and output modes.

harmonics/explain/catalog.py

Other (6) +693 / -1
currentTrack current devague frame pointer +1/-0

Track current devague frame pointer

• Adds a pointer file referencing the active devague frame for this increment.

.devague/current

current_planTrack current devague plan pointer +1/-0

Track current devague plan pointer

• Adds a pointer file referencing the active devague plan for this increment.

.devague/current_plan

harmonics-gives-an-agent-or-robot-its-own-non-spee.jsonCommit devague frame state (spec evidence trail) +293/-0

Commit devague frame state (spec evidence trail)

• Adds tool-generated JSON capturing the /think frame evidence trail for the increment.

.devague/frames/harmonics-gives-an-agent-or-robot-its-own-non-spee.json

harmonics-gives-an-agent-or-robot-its-own-non-spee.jsonCommit devague plan state +394/-0

Commit devague plan state

• Adds tool-generated JSON capturing the /spec-to-plan plan state for the increment.

.devague/plans/harmonics-gives-an-agent-or-robot-its-own-non-spee.json

.markdownlint-cli2.yamlIgnore .devague artifacts in markdownlint +3/-0

Ignore .devague artifacts in markdownlint

• Excludes .devague/** from markdownlint since artifacts are tool-generated and not authored prose.

.markdownlint-cli2.yaml

pyproject.tomlBump version to 0.5.0 +1/-1

Bump version to 0.5.0

• Updates project version to reflect the new voice domain increment.

pyproject.toml

@qodo-code-review

qodo-code-review Bot commented Jul 8, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 13 rules
✅ Skills: version-bump

Grey Divider


Action required

1. cmd_play return type wrong ✓ Resolved 📘 Rule violation ≡ Correctness
Description
The command handler cmd_play is annotated as -> int instead of the required int | None. This
breaks the standardized CLI handler typing contract expected across commands.
Code

harmonics/cli/_commands/play.py[87]

+def cmd_play(args: argparse.Namespace) -> int:
Evidence
PR Compliance ID 1794489 requires command handler return types to be exactly int | None. The new
handlers cmd_play and cmd_say are annotated as -> int, violating that requirement.

Rule 1794489: Command handler functions must return int | None
harmonics/cli/_commands/play.py[87-87]
harmonics/cli/_commands/say.py[188-188]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Command handler functions must have return type annotation `int | None`, but `cmd_play` and `cmd_say` are annotated as `-> int`.

## Issue Context
The CLI dispatcher (`harmonics.cli.main` / `_dispatch`) supports handlers that return either an `int` exit code or `None` for success. The compliance requirement standardizes all command handlers to explicitly annotate `int | None`.

## Fix Focus Areas
- harmonics/cli/_commands/play.py[87-133]
- harmonics/cli/_commands/say.py[188-247]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. File writes lack CliError ✓ Resolved 📘 Rule violation ≡ Correctness
Description
play/say perform filesystem writes via Path.write_text(...) and write_wav(...) without
catching I/O failures and converting them into a specific CliError with user-actionable
remediation. As a result, common environment/path issues (e.g., missing parent dir, permission
denied) can surface as a generic “unexpected: …” CLI error with “file a bug” remediation rather than
a structured environment/file error.
Code

harmonics/cli/_commands/play.py[R120-126]

+    if args.out:
+        Path(args.out).write_text(sequence_to_json(notes), encoding="utf-8")
+        if json_mode:
+            emit_result({"wrote": args.out, "notes": len(notes)}, json_mode=True)
+        else:
+            emit_result(f"wrote {len(notes)} note(s) to {args.out}", json_mode=False)
+        return 0
Evidence
PR Compliance ID 1794508 requires CLI failure paths to raise CliError with code, message, and
remediation, but the new cmd_say/cmd_play handlers perform direct filesystem writes
(Path.write_text(...) and WAV output via write_wav(...)) that can raise
OSError/PermissionError without being caught at the point of failure and translated into a
targeted CliError. When such exceptions escape, the top-level dispatcher in
harmonics/cli/__init__.py wraps any non-CliError into an “unexpected …” CliError (with
EXIT_USER_ERROR and “file a bug” remediation), even though the exit-code policy treats
file/operational I/O problems as environment/setup errors that should be reported explicitly and
helpfully.

Rule 1794508: CLI failures must raise CliError with code, message, and remediation
harmonics/cli/_commands/play.py[120-126]
harmonics/cli/_commands/say.py[220-233]
harmonics/cli/_commands/play.py[110-126]
harmonics/cli/init.py[106-126]
harmonics/cli/_errors.py[16-23]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
CLI commands should translate expected user/environment failures into structured `CliError(code, message, remediation)` rather than allowing raw exceptions to bubble up. The new `harmonics play`/`harmonics say` commands perform output file writes (`--out`, `--midi`, `--wav`) using `Path.write_text(...)` and `write_wav(...)` that can raise `OSError` (e.g., missing parent directory, permission denied, invalid path, disk full), and these currently escape the handlers and get wrapped by the global dispatcher into a generic “unexpected …” `EXIT_USER_ERROR` with “file a bug” remediation.

## Issue Context
The CLI contract (per PR Compliance ID 1794508) expects handlers to raise `CliError` with an appropriate `code`, `message`, and `remediation` for user-facing failures; otherwise, the dispatcher treats the exception as unexpected and emits a generic error. File I/O failures are expected operational/environment problems and should be caught where the write occurs and re-raised as a targeted `CliError` (likely `EXIT_ENV_ERROR` per the exit-code policy) with remediation that guides users to fix path/permissions/parent directory issues.

## Fix Focus Areas
- harmonics/cli/_commands/play.py[110-133]
- harmonics/cli/_commands/say.py[220-240]
- harmonics/cli/_errors.py[16-23]
- harmonics/cli/__init__.py[106-126]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Explain misstates --play ✓ Resolved 🐞 Bug ≡ Correctness
Description
harmonics explain entries for play/say (and root) claim --play is “not available yet”/sound
output is a later increment, but the PR implements --play and an audio backend that attempts
playback via simpleaudio/sounddevice. This makes harmonics explain play|say factually
incorrect.
Code

harmonics/explain/catalog.py[R165-166]

+- `--play` — not available yet (the audio backend is a later increment);
+  exits with a friendly hint to use `--out` or `--json` instead.
Evidence
The explain catalog explicitly states --play is unavailable, while the CLI registers --play and
calls harmonics.audio.play, which is implemented in the audio backend.

harmonics/explain/catalog.py[160-166]
harmonics/explain/catalog.py[223-228]
harmonics/explain/catalog.py[41-47]
harmonics/cli/_commands/play.py[97-108]
harmonics/cli/_commands/say.py[205-218]
harmonics/audio/synth.py[197-245]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The explain catalog text says `--play` is not available yet and that `play`/`say` only render notes, but the CLI now supports `--play` and the audio backend implements `harmonics.audio.play`. This mismatch can mislead users/agents.

## Issue Context
- `harmonics explain play` / `harmonics explain say` are intended to be authoritative.
- The new CLI commands do implement `--play`, and `harmonics.audio.synth.play()` attempts to use optional backends.

## Fix Focus Areas
- harmonics/explain/catalog.py[41-47]
- harmonics/explain/catalog.py[158-167]
- harmonics/explain/catalog.py[220-228]
- harmonics/cli/_commands/play.py[97-108]
- harmonics/cli/_commands/say.py[205-218]

## Suggested fix
1. Replace the `--play` bullets to reflect reality: `--play` is supported but requires `simpleaudio` or `sounddevice`; otherwise it fails with a friendly `CliError` and remediation.
2. Update the root “Coming: real --play …” paragraph accordingly (or remove it).
3. Optionally mention `--wav` as the offline/no-device alternative.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Big-endian playback byte swap ✓ Resolved 🐞 Bug ≡ Correctness
Description
On big-endian systems, _quantize() byteswaps to produce little-endian WAV PCM, but the
sounddevice playback path reads those little-endian frames into array('h') without byteswapping,
so sample values will be interpreted incorrectly during live playback.
Code

harmonics/audio/synth.py[R231-238]

+    if sounddevice is not None:
+        with wave.open(io.BytesIO(data), "rb") as wf:
+            frames = wf.readframes(wf.getnframes())
+            framerate = wf.getframerate()
+        samples = array("h")
+        samples.frombytes(frames)
+        sounddevice.play(samples, framerate)
+        sounddevice.wait()
Evidence
The code explicitly enforces little-endian WAV PCM output on big-endian hosts, but the sounddevice
path directly interprets WAV frame bytes as native-endian array('h') values without adjusting
endianness.

harmonics/audio/synth.py[136-152]
harmonics/audio/synth.py[231-238]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`render_wav()` produces little-endian PCM (byteswapping on big-endian hosts). In the `sounddevice` playback fallback, the code converts WAV frame bytes to `array('h')` via `frombytes()` but does not compensate for endianness on big-endian machines, which will corrupt playback.

## Issue Context
This only affects big-endian platforms, but the module already explicitly handles endianness when producing PCM bytes.

## Fix Focus Areas
- harmonics/audio/synth.py[136-152]
- harmonics/audio/synth.py[231-238]

## Suggested fix
After `samples.frombytes(frames)`, add:
```py
if sys.byteorder == "big":
   samples.byteswap()
```
so the integer samples passed to `sounddevice.play()` match the WAV’s little-endian encoding.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread harmonics/cli/_commands/play.py Outdated
Comment thread harmonics/cli/_commands/play.py Outdated
Comment thread harmonics/explain/catalog.py Outdated
Comment thread harmonics/audio/synth.py
… (S3776)

Both are byte-identical refactors — the rendered WAV bytes are unchanged for
every articulation (verified by MD5), so the approved glide sound is untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@sonarqubecloud

sonarqubecloud Bot commented Jul 8, 2026

Copy link
Copy Markdown

@OriNachum OriNachum merged commit 51a4895 into main Jul 8, 2026
8 checks passed
@OriNachum OriNachum deleted the build/harmonics-voice-inc1 branch July 8, 2026 10:43
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.

harmonics-cli — build brief (onboarding / source of truth)

1 participant