chore: rebrand from Kon/Corbie to Magnotia
Replace all instances of the legacy product names "Kon" and "Corbie" with "Magnotia" across user-facing copy, code identifiers, package names, bundle ids, file paths, and documentation. Preserves the unrelated "konsole" (KDE terminal) reference and the parent CORBEL company name. - Renames 10 Rust crates (kon-* → magnotia-*) and the tauri binary - Updates package.json, tauri.conf.json (productName + identifier) - Renames CSS classes (kon-rh-* → magnotia-rh-*) and animations - Renames brand and roadmap docs - Regenerates Cargo.lock and package-lock.json Verified: svelte-check passes; pure-rust crates compile under new names.
This commit is contained in:
@@ -6,7 +6,7 @@ use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use cpal::{FromSample, Sample, SampleFormat, SizedSample};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use kon_core::error::{KonError, Result};
|
||||
use magnotia_core::error::{MagnotiaError, Result};
|
||||
|
||||
const AUDIO_CHANNEL_CAPACITY: usize = 32;
|
||||
|
||||
@@ -100,7 +100,7 @@ impl MicrophoneCapture {
|
||||
|
||||
let devices = host
|
||||
.input_devices()
|
||||
.map_err(|e| KonError::AudioCaptureFailed(format!("input_devices: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioCaptureFailed(format!("input_devices: {e}")))?;
|
||||
|
||||
// Load ALSA card descriptions once per enumeration. These are the
|
||||
// "real" product names (e.g. "Blue Microphones") that cpal's
|
||||
@@ -138,17 +138,17 @@ impl MicrophoneCapture {
|
||||
let host = cpal::default_host();
|
||||
let devices = host
|
||||
.input_devices()
|
||||
.map_err(|e| KonError::AudioCaptureFailed(format!("input_devices: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioCaptureFailed(format!("input_devices: {e}")))?;
|
||||
|
||||
for device in devices {
|
||||
let name = device_display_name(&device).unwrap_or_default();
|
||||
if name == device_name {
|
||||
eprintln!("[kon-audio] start_with_device: opening explicit device '{name}'");
|
||||
eprintln!("[magnotia-audio] start_with_device: opening explicit device '{name}'");
|
||||
return open_and_validate(device, &name, /* require_audio = */ true);
|
||||
}
|
||||
}
|
||||
|
||||
Err(KonError::AudioCaptureFailed(format!(
|
||||
Err(MagnotiaError::AudioCaptureFailed(format!(
|
||||
"Selected device '{device_name}' not found in current host enumeration. \
|
||||
It may have been disconnected. Open Settings → Audio to pick another."
|
||||
)))
|
||||
@@ -172,7 +172,7 @@ impl MicrophoneCapture {
|
||||
|
||||
let mut all_devices: Vec<cpal::Device> = host
|
||||
.input_devices()
|
||||
.map_err(|e| KonError::AudioCaptureFailed(format!("input_devices: {e}")))?
|
||||
.map_err(|e| MagnotiaError::AudioCaptureFailed(format!("input_devices: {e}")))?
|
||||
.collect();
|
||||
|
||||
// Sort: default first, then non-monitor, then monitor-as-last-resort.
|
||||
@@ -190,7 +190,7 @@ impl MicrophoneCapture {
|
||||
});
|
||||
|
||||
eprintln!(
|
||||
"[kon-audio] start: enumerated {} input device(s) (default='{}')",
|
||||
"[magnotia-audio] start: enumerated {} input device(s) (default='{}')",
|
||||
all_devices.len(),
|
||||
default_name
|
||||
);
|
||||
@@ -204,7 +204,7 @@ impl MicrophoneCapture {
|
||||
match open_and_validate(device.clone(), &name, true) {
|
||||
Ok(result) => return Ok(result),
|
||||
Err(e) => {
|
||||
eprintln!("[kon-audio] '{name}' rejected: {e}");
|
||||
eprintln!("[magnotia-audio] '{name}' rejected: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,14 +212,14 @@ impl MicrophoneCapture {
|
||||
// Second pass: accept anything that delivers bytes (monitor sources
|
||||
// included). Better to capture from a monitor than fail entirely.
|
||||
eprintln!(
|
||||
"[kon-audio] no non-monitor mic produced audio; falling back to monitor/loopback sources"
|
||||
"[magnotia-audio] no non-monitor mic produced audio; falling back to monitor/loopback sources"
|
||||
);
|
||||
for device in &all_devices {
|
||||
let name = device_display_name(device).unwrap_or_default();
|
||||
match open_and_validate(device.clone(), &name, false) {
|
||||
Ok(result) => {
|
||||
eprintln!(
|
||||
"[kon-audio] FALLBACK: capturing from '{name}' (likely monitor source). \
|
||||
"[magnotia-audio] FALLBACK: capturing from '{name}' (likely monitor source). \
|
||||
Recordings may be silent or contain system audio."
|
||||
);
|
||||
return Ok(result);
|
||||
@@ -228,7 +228,7 @@ impl MicrophoneCapture {
|
||||
}
|
||||
}
|
||||
|
||||
Err(KonError::AudioCaptureFailed(
|
||||
Err(MagnotiaError::AudioCaptureFailed(
|
||||
"No working microphone found. Check that an input device is connected, \
|
||||
that PulseAudio/PipeWire is running, and that the app has microphone permission. \
|
||||
Then open Settings → Audio to pick a device explicitly."
|
||||
@@ -355,13 +355,13 @@ fn open_and_validate(
|
||||
) -> Result<(MicrophoneCapture, mpsc::Receiver<AudioChunk>)> {
|
||||
let config = device
|
||||
.default_input_config()
|
||||
.map_err(|e| KonError::AudioCaptureFailed(format!("default_input_config: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioCaptureFailed(format!("default_input_config: {e}")))?;
|
||||
let sample_rate = config.sample_rate();
|
||||
let channels = config.channels();
|
||||
let format = config.sample_format();
|
||||
|
||||
eprintln!(
|
||||
"[kon-audio] trying '{name}' ({sr}Hz, {ch}ch, {fmt:?})",
|
||||
"[magnotia-audio] trying '{name}' ({sr}Hz, {ch}ch, {fmt:?})",
|
||||
sr = sample_rate,
|
||||
ch = channels,
|
||||
fmt = format
|
||||
@@ -415,16 +415,16 @@ fn open_and_validate(
|
||||
name.to_string(),
|
||||
),
|
||||
other => {
|
||||
return Err(KonError::AudioCaptureFailed(format!(
|
||||
return Err(MagnotiaError::AudioCaptureFailed(format!(
|
||||
"unsupported sample format {other:?}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
.map_err(|e| KonError::AudioCaptureFailed(format!("build_input_stream: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioCaptureFailed(format!("build_input_stream: {e}")))?;
|
||||
|
||||
stream
|
||||
.play()
|
||||
.map_err(|e| KonError::AudioCaptureFailed(format!("stream.play: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioCaptureFailed(format!("stream.play: {e}")))?;
|
||||
|
||||
// Validation window: collect chunks for DEVICE_VALIDATION_MS, compute RMS.
|
||||
let deadline =
|
||||
@@ -451,19 +451,19 @@ fn open_and_validate(
|
||||
}
|
||||
|
||||
if total_samples == 0 {
|
||||
return Err(KonError::AudioCaptureFailed(
|
||||
return Err(MagnotiaError::AudioCaptureFailed(
|
||||
"device delivered zero samples in validation window".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let rms = (sum_sq / total_samples as f64).sqrt() as f32;
|
||||
eprintln!(
|
||||
"[kon-audio] '{name}' validation: {samples} samples, rms={rms:.6}",
|
||||
"[magnotia-audio] '{name}' validation: {samples} samples, rms={rms:.6}",
|
||||
samples = total_samples
|
||||
);
|
||||
|
||||
if require_audio && rms < SILENCE_RMS_FLOOR {
|
||||
return Err(KonError::AudioCaptureFailed(format!(
|
||||
return Err(MagnotiaError::AudioCaptureFailed(format!(
|
||||
"device produced silence (rms={rms:.6} below floor {SILENCE_RMS_FLOOR:.6})"
|
||||
)));
|
||||
}
|
||||
@@ -474,7 +474,7 @@ fn open_and_validate(
|
||||
// failing fast. (Codex review 2026/04/17 D3)
|
||||
const DEAD_SILENCE_FLOOR: f32 = 1e-7;
|
||||
if rms < DEAD_SILENCE_FLOOR {
|
||||
return Err(KonError::AudioCaptureFailed(format!(
|
||||
return Err(MagnotiaError::AudioCaptureFailed(format!(
|
||||
"device produced dead silence (rms={rms:.6e} below absolute floor {DEAD_SILENCE_FLOOR:.6e})"
|
||||
)));
|
||||
}
|
||||
@@ -489,7 +489,7 @@ fn open_and_validate(
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("[kon-audio] selected microphone: '{name}'");
|
||||
eprintln!("[magnotia-audio] selected microphone: '{name}'");
|
||||
Ok((
|
||||
MicrophoneCapture {
|
||||
stream: Some(stream),
|
||||
@@ -539,7 +539,7 @@ where
|
||||
// Surface stream errors to the live session via err_tx so the
|
||||
// frontend can show a toast. Also keep the eprintln for ops
|
||||
// logs. (Codex review 2026/04/17 M2)
|
||||
eprintln!("[kon-audio] capture error: {err}");
|
||||
eprintln!("[magnotia-audio] capture error: {err}");
|
||||
if err_tx
|
||||
.try_send(CaptureRuntimeError {
|
||||
device_name: err_device_name.clone(),
|
||||
@@ -553,7 +553,7 @@ where
|
||||
// frontend never received the typed event.
|
||||
let prior = dropped_errors.fetch_add(1, Ordering::Relaxed);
|
||||
eprintln!(
|
||||
"[kon-audio] capture error channel full; dropped error #{} for device '{}'",
|
||||
"[magnotia-audio] capture error channel full; dropped error #{} for device '{}'",
|
||||
prior + 1,
|
||||
err_device_name,
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::path::Path;
|
||||
|
||||
use kon_core::error::Result;
|
||||
use kon_core::types::AudioSamples;
|
||||
use magnotia_core::error::Result;
|
||||
use magnotia_core::types::AudioSamples;
|
||||
|
||||
use crate::decode::decode_audio_file;
|
||||
use crate::resample::resample_to_16khz;
|
||||
@@ -15,5 +15,5 @@ pub async fn decode_and_resample(path: &Path) -> Result<AudioSamples> {
|
||||
resample_to_16khz(&audio)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| kon_core::error::KonError::AudioDecodeFailed(format!("Task join error: {e}")))?
|
||||
.map_err(|e| magnotia_core::error::MagnotiaError::AudioDecodeFailed(format!("Task join error: {e}")))?
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@ use symphonia::core::io::MediaSourceStream;
|
||||
use symphonia::core::meta::MetadataOptions;
|
||||
use symphonia::core::probe::Hint;
|
||||
|
||||
use kon_core::error::{KonError, Result};
|
||||
use kon_core::types::AudioSamples;
|
||||
use magnotia_core::error::{MagnotiaError, Result};
|
||||
use magnotia_core::types::AudioSamples;
|
||||
|
||||
/// Decode an audio file to mono f32 PCM samples.
|
||||
/// Supports all formats symphonia handles: mp3, aac, flac, wav, ogg, etc.
|
||||
///
|
||||
/// Any read- or decode-side error is propagated as `KonError::AudioDecodeFailed`.
|
||||
/// Any read- or decode-side error is propagated as `MagnotiaError::AudioDecodeFailed`.
|
||||
/// A previous implementation `break`ed out of the packet loop on any read
|
||||
/// error and skipped per-packet decode errors, so a truncated or corrupt
|
||||
/// input silently returned `Ok` with whatever had decoded before the
|
||||
@@ -29,7 +29,7 @@ pub fn decode_audio_file_limited(
|
||||
max_duration_secs: Option<f64>,
|
||||
) -> Result<AudioSamples> {
|
||||
let file = File::open(path)
|
||||
.map_err(|e| KonError::AudioDecodeFailed(format!("Cannot open file: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Cannot open file: {e}")))?;
|
||||
let mss = MediaSourceStream::new(Box::new(file), Default::default());
|
||||
|
||||
let mut hint = Hint::new();
|
||||
@@ -42,7 +42,7 @@ pub fn decode_audio_file_limited(
|
||||
|
||||
pub fn probe_audio_duration_secs(path: &Path) -> Result<Option<f64>> {
|
||||
let file = File::open(path)
|
||||
.map_err(|e| KonError::AudioDecodeFailed(format!("Cannot open file: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Cannot open file: {e}")))?;
|
||||
let mss = MediaSourceStream::new(Box::new(file), Default::default());
|
||||
let mut hint = Hint::new();
|
||||
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
|
||||
@@ -56,15 +56,15 @@ pub fn probe_audio_duration_secs(path: &Path) -> Result<Option<f64>> {
|
||||
&FormatOptions::default(),
|
||||
&MetadataOptions::default(),
|
||||
)
|
||||
.map_err(|e| KonError::AudioDecodeFailed(format!("Unsupported format: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Unsupported format: {e}")))?;
|
||||
let track = probed
|
||||
.format
|
||||
.default_track()
|
||||
.ok_or_else(|| KonError::AudioDecodeFailed("No audio track found".into()))?;
|
||||
.ok_or_else(|| MagnotiaError::AudioDecodeFailed("No audio track found".into()))?;
|
||||
let sample_rate = track
|
||||
.codec_params
|
||||
.sample_rate
|
||||
.ok_or_else(|| KonError::AudioDecodeFailed("Unknown sample rate".into()))?;
|
||||
.ok_or_else(|| MagnotiaError::AudioDecodeFailed("Unknown sample rate".into()))?;
|
||||
Ok(track
|
||||
.codec_params
|
||||
.n_frames
|
||||
@@ -86,20 +86,20 @@ fn decode_media_stream(
|
||||
&FormatOptions::default(),
|
||||
&MetadataOptions::default(),
|
||||
)
|
||||
.map_err(|e| KonError::AudioDecodeFailed(format!("Unsupported format: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Unsupported format: {e}")))?;
|
||||
|
||||
let mut format = probed.format;
|
||||
|
||||
let track = format
|
||||
.default_track()
|
||||
.ok_or_else(|| KonError::AudioDecodeFailed("No audio track found".into()))?;
|
||||
.ok_or_else(|| MagnotiaError::AudioDecodeFailed("No audio track found".into()))?;
|
||||
let sample_rate = track
|
||||
.codec_params
|
||||
.sample_rate
|
||||
.ok_or_else(|| KonError::AudioDecodeFailed("Unknown sample rate".into()))?;
|
||||
.ok_or_else(|| MagnotiaError::AudioDecodeFailed("Unknown sample rate".into()))?;
|
||||
|
||||
if sample_rate == 0 {
|
||||
return Err(KonError::AudioDecodeFailed("Invalid sample rate: 0".into()));
|
||||
return Err(MagnotiaError::AudioDecodeFailed("Invalid sample rate: 0".into()));
|
||||
}
|
||||
|
||||
let track_id = track.id;
|
||||
@@ -107,7 +107,7 @@ fn decode_media_stream(
|
||||
|
||||
let mut decoder = symphonia::default::get_codecs()
|
||||
.make(&track.codec_params, &DecoderOptions::default())
|
||||
.map_err(|e| KonError::AudioDecodeFailed(format!("Codec error: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Codec error: {e}")))?;
|
||||
|
||||
let mut samples: Vec<f32> = Vec::new();
|
||||
|
||||
@@ -121,12 +121,12 @@ fn decode_media_stream(
|
||||
break;
|
||||
}
|
||||
Err(SymphoniaError::ResetRequired) => {
|
||||
return Err(KonError::AudioDecodeFailed(
|
||||
return Err(MagnotiaError::AudioDecodeFailed(
|
||||
"decoder reset required mid-stream — input contains a discontinuity".into(),
|
||||
));
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(KonError::AudioDecodeFailed(format!(
|
||||
return Err(MagnotiaError::AudioDecodeFailed(format!(
|
||||
"packet read failed: {e}"
|
||||
)));
|
||||
}
|
||||
@@ -138,7 +138,7 @@ fn decode_media_stream(
|
||||
|
||||
let decoded = decoder
|
||||
.decode(&packet)
|
||||
.map_err(|e| KonError::AudioDecodeFailed(format!("packet decode failed: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("packet decode failed: {e}")))?;
|
||||
|
||||
let spec = *decoded.spec();
|
||||
let channels = spec.channels.count();
|
||||
@@ -158,7 +158,7 @@ fn decode_media_stream(
|
||||
.map(|limit| samples.len() > limit)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Err(KonError::AudioDecodeFailed(format!(
|
||||
return Err(MagnotiaError::AudioDecodeFailed(format!(
|
||||
"Audio is longer than the {:.0} minute import limit",
|
||||
max_duration_secs.unwrap_or(0.0) / 60.0
|
||||
)));
|
||||
@@ -166,7 +166,7 @@ fn decode_media_stream(
|
||||
}
|
||||
|
||||
if samples.is_empty() {
|
||||
return Err(KonError::AudioDecodeFailed("No audio data decoded".into()));
|
||||
return Err(MagnotiaError::AudioDecodeFailed("No audio data decoded".into()));
|
||||
}
|
||||
|
||||
Ok(AudioSamples::new(samples, sample_rate, 1))
|
||||
@@ -187,7 +187,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn valid_wav_bytes(sample_count: usize) -> Vec<u8> {
|
||||
let path = temp_path("kon_decode_tmp_for_bytes.wav");
|
||||
let path = temp_path("magnotia_decode_tmp_for_bytes.wav");
|
||||
let samples: Vec<f32> = (0..sample_count).map(|i| (i as f32) / 1000.0).collect();
|
||||
let audio = AudioSamples::mono_16khz(samples);
|
||||
write_wav(&path, &audio).unwrap();
|
||||
@@ -234,7 +234,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn decodes_valid_wav_successfully() {
|
||||
let path = temp_path("kon_decode_valid.wav");
|
||||
let path = temp_path("magnotia_decode_valid.wav");
|
||||
let samples: Vec<f32> = (0..4_000).map(|i| (i as f32) / 1000.0).collect();
|
||||
write_wav(&path, &AudioSamples::mono_16khz(samples)).unwrap();
|
||||
|
||||
@@ -247,7 +247,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn missing_file_surfaces_error() {
|
||||
let path = temp_path("kon_decode_missing.wav");
|
||||
let path = temp_path("magnotia_decode_missing.wav");
|
||||
let result = decode_audio_file(&path);
|
||||
assert!(result.is_err(), "missing file must error, got: {result:?}");
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ use rubato::{
|
||||
Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction,
|
||||
};
|
||||
|
||||
use kon_core::constants::WHISPER_SAMPLE_RATE;
|
||||
use kon_core::error::{KonError, Result};
|
||||
use kon_core::types::AudioSamples;
|
||||
use magnotia_core::constants::WHISPER_SAMPLE_RATE;
|
||||
use magnotia_core::error::{MagnotiaError, Result};
|
||||
use magnotia_core::types::AudioSamples;
|
||||
|
||||
/// Resample audio to 16kHz mono using sinc interpolation (rubato).
|
||||
/// Returns a new AudioSamples at the target sample rate.
|
||||
@@ -17,7 +17,7 @@ pub fn resample_to_16khz(audio: &AudioSamples) -> Result<AudioSamples> {
|
||||
}
|
||||
|
||||
if from_rate == 0 {
|
||||
return Err(KonError::AudioDecodeFailed(
|
||||
return Err(MagnotiaError::AudioDecodeFailed(
|
||||
"Cannot resample: source rate is 0".into(),
|
||||
));
|
||||
}
|
||||
@@ -36,7 +36,7 @@ pub fn resample_to_16khz(audio: &AudioSamples) -> Result<AudioSamples> {
|
||||
let mut resampler = SincFixedIn::<f32>::new(
|
||||
ratio, 1.1, params, chunk_size, 1, // mono
|
||||
)
|
||||
.map_err(|e| KonError::AudioDecodeFailed(format!("Resampler init failed: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Resampler init failed: {e}")))?;
|
||||
|
||||
let samples = audio.samples();
|
||||
let mut output_samples: Vec<f32> = Vec::new();
|
||||
@@ -53,7 +53,7 @@ pub fn resample_to_16khz(audio: &AudioSamples) -> Result<AudioSamples> {
|
||||
let input = vec![chunk];
|
||||
let result = resampler
|
||||
.process(&input, None)
|
||||
.map_err(|e| KonError::AudioDecodeFailed(format!("Resample failed: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("Resample failed: {e}")))?;
|
||||
|
||||
if !result.is_empty() && !result[0].is_empty() {
|
||||
output_samples.extend_from_slice(&result[0]);
|
||||
|
||||
@@ -27,8 +27,8 @@ use rubato::{
|
||||
Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType, WindowFunction,
|
||||
};
|
||||
|
||||
use kon_core::constants::WHISPER_SAMPLE_RATE;
|
||||
use kon_core::error::{KonError, Result};
|
||||
use magnotia_core::constants::WHISPER_SAMPLE_RATE;
|
||||
use magnotia_core::error::{MagnotiaError, Result};
|
||||
|
||||
/// Number of input samples the rubato resampler consumes per `process()`
|
||||
/// call. Matches the chunk size used in `resample::resample_to_16khz`.
|
||||
@@ -51,7 +51,7 @@ impl StreamingResampler {
|
||||
/// rubato rejects the requested ratio.
|
||||
pub fn new(from_rate: u32) -> Result<Self> {
|
||||
if from_rate == 0 {
|
||||
return Err(KonError::AudioDecodeFailed(
|
||||
return Err(MagnotiaError::AudioDecodeFailed(
|
||||
"StreamingResampler: input sample rate is 0".into(),
|
||||
));
|
||||
}
|
||||
@@ -77,7 +77,7 @@ impl StreamingResampler {
|
||||
INPUT_CHUNK,
|
||||
1, // mono
|
||||
)
|
||||
.map_err(|e| KonError::AudioDecodeFailed(format!("StreamingResampler init failed: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("StreamingResampler init failed: {e}")))?;
|
||||
|
||||
Ok(Self::Sinc {
|
||||
resampler,
|
||||
@@ -108,7 +108,7 @@ impl StreamingResampler {
|
||||
let chunk: Vec<f32> = residual.drain(..INPUT_CHUNK).collect();
|
||||
let input = vec![chunk];
|
||||
let result = resampler.process(&input, None).map_err(|e| {
|
||||
KonError::AudioDecodeFailed(format!(
|
||||
MagnotiaError::AudioDecodeFailed(format!(
|
||||
"StreamingResampler process failed: {e}"
|
||||
))
|
||||
})?;
|
||||
@@ -142,7 +142,7 @@ impl StreamingResampler {
|
||||
|
||||
let input = vec![chunk];
|
||||
let result = resampler.process(&input, None).map_err(|e| {
|
||||
KonError::AudioDecodeFailed(format!("StreamingResampler flush failed: {e}"))
|
||||
MagnotiaError::AudioDecodeFailed(format!("StreamingResampler flush failed: {e}"))
|
||||
})?;
|
||||
|
||||
let Some(mut out) = result.into_iter().next() else {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// For now, all audio is treated as speech. This matches v0.2 behaviour
|
||||
// (no VAD) and doesn't affect core functionality.
|
||||
|
||||
use kon_core::constants::VAD_SPEECH_THRESHOLD;
|
||||
use magnotia_core::constants::VAD_SPEECH_THRESHOLD;
|
||||
|
||||
/// Stub speech detector. Treats all audio as speech.
|
||||
#[derive(Default)]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::io::BufWriter;
|
||||
use std::path::Path;
|
||||
|
||||
use kon_core::error::{KonError, Result};
|
||||
use kon_core::types::AudioSamples;
|
||||
use magnotia_core::error::{MagnotiaError, Result};
|
||||
use magnotia_core::types::AudioSamples;
|
||||
|
||||
/// Append-friendly WAV writer for long-running captures.
|
||||
///
|
||||
@@ -40,10 +40,10 @@ impl WavWriter {
|
||||
bits_per_sample: 16,
|
||||
sample_format: hound::SampleFormat::Int,
|
||||
};
|
||||
let file = std::fs::File::create(path).map_err(KonError::Io)?;
|
||||
let file = std::fs::File::create(path).map_err(MagnotiaError::Io)?;
|
||||
let buffered = BufWriter::new(file);
|
||||
let inner = hound::WavWriter::new(buffered, spec)
|
||||
.map_err(|e| KonError::Io(std::io::Error::other(format!("WAV create failed: {e}"))))?;
|
||||
.map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV create failed: {e}"))))?;
|
||||
Ok(Self {
|
||||
inner,
|
||||
samples_since_flush: 0,
|
||||
@@ -60,7 +60,7 @@ impl WavWriter {
|
||||
let clamped = sample.clamp(-1.0, 1.0);
|
||||
let int_sample = (clamped * i16::MAX as f32) as i16;
|
||||
self.inner.write_sample(int_sample).map_err(|e| {
|
||||
KonError::Io(std::io::Error::other(format!("WAV write failed: {e}")))
|
||||
MagnotiaError::Io(std::io::Error::other(format!("WAV write failed: {e}")))
|
||||
})?;
|
||||
}
|
||||
self.samples_since_flush += samples.len();
|
||||
@@ -78,7 +78,7 @@ impl WavWriter {
|
||||
pub fn flush(&mut self) -> Result<()> {
|
||||
self.inner
|
||||
.flush()
|
||||
.map_err(|e| KonError::Io(std::io::Error::other(format!("WAV flush failed: {e}"))))?;
|
||||
.map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV flush failed: {e}"))))?;
|
||||
self.samples_since_flush = 0;
|
||||
Ok(())
|
||||
}
|
||||
@@ -89,7 +89,7 @@ impl WavWriter {
|
||||
/// that care about the unflushed tail should always finalise.
|
||||
pub fn finalize(self) -> Result<()> {
|
||||
self.inner.finalize().map_err(|e| {
|
||||
KonError::Io(std::io::Error::other(format!("WAV finalize failed: {e}")))
|
||||
MagnotiaError::Io(std::io::Error::other(format!("WAV finalize failed: {e}")))
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -105,33 +105,33 @@ pub fn write_wav(path: &Path, audio: &AudioSamples) -> Result<()> {
|
||||
};
|
||||
|
||||
let mut writer = hound::WavWriter::create(path, spec)
|
||||
.map_err(|e| KonError::Io(std::io::Error::other(format!("WAV create failed: {e}"))))?;
|
||||
.map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV create failed: {e}"))))?;
|
||||
|
||||
for &sample in audio.samples() {
|
||||
let clamped = sample.clamp(-1.0, 1.0);
|
||||
let int_sample = (clamped * i16::MAX as f32) as i16;
|
||||
writer
|
||||
.write_sample(int_sample)
|
||||
.map_err(|e| KonError::Io(std::io::Error::other(format!("WAV write failed: {e}"))))?;
|
||||
.map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV write failed: {e}"))))?;
|
||||
}
|
||||
|
||||
writer
|
||||
.finalize()
|
||||
.map_err(|e| KonError::Io(std::io::Error::other(format!("WAV finalize failed: {e}"))))?;
|
||||
.map_err(|e| MagnotiaError::Io(std::io::Error::other(format!("WAV finalize failed: {e}"))))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read a WAV file to f32 PCM `AudioSamples`.
|
||||
///
|
||||
/// Any per-sample decode error is surfaced as `KonError::AudioDecodeFailed`
|
||||
/// Any per-sample decode error is surfaced as `MagnotiaError::AudioDecodeFailed`
|
||||
/// rather than silently dropped. A previous implementation used
|
||||
/// `filter_map(|s| s.ok())`, so a truncated or corrupt payload returned
|
||||
/// a short, silently-partial `AudioSamples` — callers got `Ok` while
|
||||
/// losing audio (flagged by the 2026-04-22 review).
|
||||
pub fn read_wav(path: &Path) -> Result<AudioSamples> {
|
||||
let reader = hound::WavReader::open(path)
|
||||
.map_err(|e| KonError::AudioDecodeFailed(format!("WAV open failed: {e}")))?;
|
||||
.map_err(|e| MagnotiaError::AudioDecodeFailed(format!("WAV open failed: {e}")))?;
|
||||
|
||||
let spec = reader.spec();
|
||||
let sample_rate = spec.sample_rate;
|
||||
@@ -145,7 +145,7 @@ pub fn read_wav(path: &Path) -> Result<AudioSamples> {
|
||||
sample
|
||||
.map(|s| s as f32 / (1 << (bits_per_sample - 1)) as f32)
|
||||
.map_err(|e| {
|
||||
KonError::AudioDecodeFailed(format!("WAV sample decode failed: {e}"))
|
||||
MagnotiaError::AudioDecodeFailed(format!("WAV sample decode failed: {e}"))
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<f32>>>()?,
|
||||
@@ -153,7 +153,7 @@ pub fn read_wav(path: &Path) -> Result<AudioSamples> {
|
||||
.into_samples::<f32>()
|
||||
.map(|sample| {
|
||||
sample.map_err(|e| {
|
||||
KonError::AudioDecodeFailed(format!("WAV sample decode failed: {e}"))
|
||||
MagnotiaError::AudioDecodeFailed(format!("WAV sample decode failed: {e}"))
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<f32>>>()?,
|
||||
@@ -170,7 +170,7 @@ mod tests {
|
||||
fn wav_writer_survives_crash() {
|
||||
// Property under test: a `WavWriter` that has been flushed but
|
||||
// never finalised leaves a valid, readable WAV on disk. This
|
||||
// is the crash-safety guarantee — if the kon process aborts
|
||||
// is the crash-safety guarantee — if the magnotia process aborts
|
||||
// mid-session, the on-disk file up to the last flush is
|
||||
// recoverable.
|
||||
//
|
||||
@@ -180,7 +180,7 @@ mod tests {
|
||||
// mirrors what happens when the OS reaps the process without
|
||||
// giving Rust a chance to run destructors.
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let path = temp_dir.join("kon_test_wav_writer_survives_crash.wav");
|
||||
let path = temp_dir.join("magnotia_test_wav_writer_survives_crash.wav");
|
||||
let _ = std::fs::remove_file(&path);
|
||||
|
||||
let mut writer = WavWriter::create(&path, 16_000, 1).unwrap();
|
||||
@@ -217,7 +217,7 @@ mod tests {
|
||||
#[test]
|
||||
fn wav_writer_append_then_finalize_roundtrips() {
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let path = temp_dir.join("kon_test_wav_writer_finalize.wav");
|
||||
let path = temp_dir.join("magnotia_test_wav_writer_finalize.wav");
|
||||
let _ = std::fs::remove_file(&path);
|
||||
|
||||
let mut writer = WavWriter::create(&path, 16_000, 1).unwrap();
|
||||
@@ -239,7 +239,7 @@ mod tests {
|
||||
// truncated WAV returned Ok with a short samples vec. The
|
||||
// new code must propagate the error.
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let path = temp_dir.join("kon_test_truncated_wav.wav");
|
||||
let path = temp_dir.join("magnotia_test_truncated_wav.wav");
|
||||
let _ = std::fs::remove_file(&path);
|
||||
|
||||
// Write 100 samples (200 bytes at 16-bit).
|
||||
@@ -265,7 +265,7 @@ mod tests {
|
||||
#[test]
|
||||
fn wav_roundtrip() {
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let path = temp_dir.join("kon_test_roundtrip.wav");
|
||||
let path = temp_dir.join("magnotia_test_roundtrip.wav");
|
||||
|
||||
let original = AudioSamples::mono_16khz(vec![0.0, 0.5, -0.5, 0.25, -0.25]);
|
||||
write_wav(&path, &original).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user