Skip to content

Commit baeb15a

Browse files
Fix broken loop in streaming mode, code cleanup
1 parent db90db3 commit baeb15a

5 files changed

Lines changed: 67 additions & 104 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rusty-pipes"
3-
version = "0.1.1"
3+
version = "0.1.2"
44
edition = "2024"
55

66
[dependencies]

src/audio.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::mem;
1717

1818
use crate::app::{ActiveNote, AppMessage};
1919
use crate::organ::Organ;
20-
use crate::wav::{parse_wav_metadata, WavSampleReader};
20+
use crate::wav::{parse_wav_metadata, WavSampleReader, parse_smpl_chunk};
2121
use crate::wav_converter::SampleMetadata;
2222

2323
const AUDIO_SAMPLE_RATE: u32 = 48000;
@@ -132,17 +132,24 @@ impl Voice {
132132
let mut reader = BufReader::new(file);
133133

134134
// Assuming parse_wav_metadata is in a shared wav_reader mod
135-
let (fmt, loop_info_from_file, data_start, data_size) =
136-
parse_wav_metadata(&mut reader)
135+
let (fmt, other_chunks, data_start, data_size) =
136+
parse_wav_metadata(&mut reader, &path_buf)
137137
.map_err(|e| anyhow!("[LoaderThread] Failed to parse WAV metadata for {:?}: {}", path_buf.clone(), e))?;
138-
138+
139139
if fmt.sample_rate != sample_rate {
140140
return Err(anyhow!(
141141
"[LoaderThread] File {:?} has wrong sample rate: {} (expected {}). Please re-process samples.",
142142
path_buf, fmt.sample_rate, sample_rate
143143
));
144144
}
145145

146+
let mut loop_info_from_file = None;
147+
for chunk in other_chunks {
148+
if &chunk.id == b"smpl" {
149+
loop_info_from_file = parse_smpl_chunk(&chunk.data);
150+
break;
151+
}
152+
}
146153
// Set metadata from file
147154
loop_info = if is_attack_sample_clone { loop_info_from_file } else { None };
148155
input_channels = fmt.num_channels as usize;
@@ -156,7 +163,7 @@ impl Voice {
156163
log::debug!("[LoaderThread] Reading {:?} into memory for looping (streaming mode).", path_str);
157164
samples_in_memory = decoder.collect();
158165
use_memory_reader = true;
159-
source_is_finished = true;
166+
source_is_finished = false;
160167
} else {
161168
source = Some(Box::new(decoder));
162169
source_is_finished = false;

src/wav.rs

Lines changed: 36 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use anyhow::{anyhow, Result};
22
use std::io::{Seek, SeekFrom, Read, Cursor};
3+
use std::path::Path;
34
use byteorder::{ReadBytesExt as OtherReadBytesExt, LittleEndian};
45

56
const I16_MAX_F: f32 = 32768.0; // 2^15
@@ -14,99 +15,70 @@ pub struct WavFmt {
1415
pub sample_rate: u32,
1516
pub bits_per_sample: u16,
1617
}
17-
18-
impl WavFmt {
19-
/// Parses the 'fmt ' chunk data.
20-
fn parse(data: &[u8]) -> Result<Self> {
21-
if data.len() < 16 {
22-
return Err(anyhow!("'fmt ' chunk is too small: {} bytes", data.len()));
23-
}
24-
let mut cursor = Cursor::new(data);
25-
let audio_format = cursor.read_u16::<LittleEndian>()?;
26-
let num_channels = cursor.read_u16::<LittleEndian>()?;
27-
let sample_rate = cursor.read_u32::<LittleEndian>()?;
28-
let _byte_rate = cursor.read_u32::<LittleEndian>()?; // Not needed
29-
let _block_align = cursor.read_u16::<LittleEndian>()?; // Not needed
30-
let bits_per_sample = cursor.read_u16::<LittleEndian>()?;
31-
32-
if audio_format != 1 && audio_format != 3 {
33-
return Err(anyhow!("Unsupported audio format: {} (only 1=PCM or 3=Float)", audio_format));
34-
}
35-
36-
log::trace!("[WavFmt] Parsed: {}ch, {}Hz, {}b, format={}", num_channels, sample_rate, bits_per_sample, audio_format);
37-
38-
Ok(Self {
39-
audio_format,
40-
num_channels,
41-
sample_rate,
42-
bits_per_sample,
43-
})
44-
}
18+
/// A struct to hold metadata chunks (like 'smpl') that we want to preserve.
19+
#[derive(Debug, Clone)]
20+
pub struct OtherChunk {
21+
pub id: [u8; 4],
22+
pub data: Vec<u8>,
4523
}
4624

4725
/// Parses all necessary metadata from a WAV file in one pass.
4826
/// Returns (format, loop_info, data_chunk_start_pos, data_chunk_size)
4927
pub fn parse_wav_metadata<R: Read + Seek>(
50-
reader: &mut R
51-
) -> Result<(WavFmt, Option<(u32, u32)>, u64, u32)> {
28+
reader: &mut R,
29+
full_path_for_logs: &Path,
30+
) -> Result<(WavFmt, Vec<OtherChunk>, u64, u32)> {
5231
let mut riff_header = [0; 4];
5332
reader.read_exact(&mut riff_header)?;
54-
if &riff_header != b"RIFF" {
55-
return Err(anyhow!("Not a RIFF file"));
56-
}
33+
if &riff_header != b"RIFF" { return Err(anyhow!("Not a RIFF file: {:?}", full_path_for_logs)); }
5734
let _file_size = reader.read_u32::<LittleEndian>()?;
5835
let mut wave_header = [0; 4];
5936
reader.read_exact(&mut wave_header)?;
60-
if &wave_header != b"WAVE" {
61-
return Err(anyhow!("Not a WAVE file"));
62-
}
37+
if &wave_header != b"WAVE" { return Err(anyhow!("Not a WAVE file: {:?}", full_path_for_logs)); }
6338

64-
let mut fmt: Option<WavFmt> = None;
65-
let mut loop_info: Option<(u32, u32)> = None;
66-
let mut data_info: Option<(u64, u32)> = None; // (start_pos, size_in_bytes)
39+
let mut format_chunk: Option<WavFmt> = None;
40+
let mut data_chunk_info: Option<(u64, u32)> = None; // (offset, size)
41+
let mut other_chunks: Vec<OtherChunk> = Vec::new();
6742

68-
'chunk_loop: while let Ok(chunk_id) = reader.read_u32::<LittleEndian>().map(|id| id.to_le_bytes()) {
43+
while let Ok(chunk_id) = reader.read_u32::<LittleEndian>().map(|id| id.to_le_bytes()) {
6944
let chunk_size = reader.read_u32::<LittleEndian>()?;
7045
let chunk_data_start_pos = reader.stream_position()?;
7146
let next_chunk_aligned_pos =
7247
chunk_data_start_pos + (chunk_size as u64 + (chunk_size % 2) as u64);
7348

7449
match &chunk_id {
7550
b"fmt " => {
76-
let mut chunk_data = vec![0; chunk_size as usize];
77-
reader.read_exact(&mut chunk_data)?;
78-
fmt = Some(WavFmt::parse(&chunk_data)?);
79-
}
80-
b"smpl" => {
81-
let mut chunk_data = vec![0; chunk_size as usize];
82-
reader.read_exact(&mut chunk_data)?;
83-
loop_info = parse_smpl_chunk(&chunk_data);
51+
let mut fmt_data = vec![0; chunk_size as usize];
52+
reader.read_exact(&mut fmt_data)?;
53+
let mut cursor = Cursor::new(fmt_data);
54+
format_chunk = Some(WavFmt {
55+
audio_format: cursor.read_u16::<LittleEndian>()?,
56+
num_channels: cursor.read_u16::<LittleEndian>()?,
57+
sample_rate: cursor.read_u32::<LittleEndian>()?,
58+
bits_per_sample: {
59+
cursor.seek(SeekFrom::Start(14))?;
60+
cursor.read_u16::<LittleEndian>()?
61+
},
62+
});
8463
}
8564
b"data" => {
86-
// Found data, store its position and size.
87-
data_info = Some((chunk_data_start_pos, chunk_size));
88-
89-
// We MUST break here. Any 'smpl' chunk *must* come before 'data'.
90-
// If it comes after, it's a non-standard file and we won't find it.
91-
// This matches the behavior of the original parser.
92-
break 'chunk_loop;
65+
data_chunk_info = Some((chunk_data_start_pos, chunk_size));
9366
}
9467
_ => {
95-
// Other chunk, skip it
68+
let mut chunk_data = vec![0; chunk_size as usize];
69+
reader.read_exact(&mut chunk_data)?;
70+
other_chunks.push(OtherChunk { id: chunk_id, data: chunk_data });
9671
}
9772
}
98-
99-
// Seek to the *start* of the next chunk
10073
if reader.seek(SeekFrom::Start(next_chunk_aligned_pos)).is_err() {
101-
// End of file, break loop
102-
break 'chunk_loop;
74+
break; // Reached end of file
10375
}
10476
}
77+
78+
let format = format_chunk.ok_or_else(|| anyhow!("File has no 'fmt ' chunk: {:?}", full_path_for_logs))?;
79+
let (data_offset, data_size) = data_chunk_info.ok_or_else(|| anyhow!("File has no 'data' chunk: {:?}", full_path_for_logs))?;
10580

106-
let fmt = fmt.ok_or_else(|| anyhow!("'fmt ' chunk not found"))?;
107-
let (data_start, data_size) = data_info.ok_or_else(|| anyhow!("'data' chunk not found"))?;
108-
109-
Ok((fmt, loop_info, data_start, data_size))
81+
Ok((format, other_chunks, data_offset, data_size))
11082
}
11183

11284

src/wav_converter.rs

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,13 @@ use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write, Cursor};
55
use std::path::{Path, PathBuf};
66
use rubato::{Resampler, SincFixedIn, SincInterpolationType, SincInterpolationParameters, WindowFunction};
77

8-
use crate::wav::parse_smpl_chunk;
8+
use crate::wav::{parse_smpl_chunk, WavFmt, OtherChunk};
99

1010
const TARGET_SAMPLE_RATE: u32 = 48000;
1111
const I16_MAX_F: f32 = 32768.0; // 2^15
1212
const I24_MAX_F: f32 = 8388608.0; // 2^23
1313
const I32_MAX_F: f32 = 2147483648.0; // 2^31
1414

15-
/// A simple struct to hold the format info we care about.
16-
#[derive(Debug, Clone, Copy)]
17-
pub struct WavFormat {
18-
pub audio_format: u16,
19-
pub channel_count: u16,
20-
pub sampling_rate: u32,
21-
pub bits_per_sample: u16,
22-
}
23-
24-
/// A struct to hold metadata chunks (like 'smpl') that we want to preserve.
25-
#[derive(Debug, Clone)]
26-
pub struct OtherChunk {
27-
pub id: [u8; 4],
28-
pub data: Vec<u8>,
29-
}
30-
3115
#[derive(Debug)]
3216
pub struct SampleMetadata {
3317
pub loop_info: Option<(u32, u32)>,
@@ -47,12 +31,12 @@ fn read_i24<R: Read>(reader: &mut R) -> std::io::Result<i32> {
4731
/// Helper to read all audio data from a reader into f32 waves
4832
fn read_f32_waves<R: Read>(
4933
mut reader: R,
50-
format: WavFormat,
34+
format: WavFmt,
5135
data_size: u32
5236
) -> Result<Vec<Vec<f32>>> {
5337
let bytes_per_sample = (format.bits_per_sample / 8) as u32;
54-
let num_frames = data_size / (bytes_per_sample * format.channel_count as u32);
55-
let num_channels = format.channel_count as usize;
38+
let num_frames = data_size / (bytes_per_sample * format.num_channels as u32);
39+
let num_channels = format.num_channels as usize;
5640
let mut output_waves = vec![Vec::with_capacity(num_frames as usize); num_channels];
5741

5842
for _ in 0..num_frames {
@@ -117,7 +101,7 @@ fn write_f32_waves_to_bytes(
117101
pub fn parse_wav_metadata<R: Read + Seek>(
118102
reader: &mut R,
119103
full_path_for_logs: &Path,
120-
) -> Result<(WavFormat, Vec<OtherChunk>, u64, u32)> {
104+
) -> Result<(WavFmt, Vec<OtherChunk>, u64, u32)> {
121105
let mut riff_header = [0; 4];
122106
reader.read_exact(&mut riff_header)?;
123107
if &riff_header != b"RIFF" { return Err(anyhow!("Not a RIFF file: {:?}", full_path_for_logs)); }
@@ -126,7 +110,7 @@ pub fn parse_wav_metadata<R: Read + Seek>(
126110
reader.read_exact(&mut wave_header)?;
127111
if &wave_header != b"WAVE" { return Err(anyhow!("Not a WAVE file: {:?}", full_path_for_logs)); }
128112

129-
let mut format_chunk: Option<WavFormat> = None;
113+
let mut format_chunk: Option<WavFmt> = None;
130114
let mut data_chunk_info: Option<(u64, u32)> = None; // (offset, size)
131115
let mut other_chunks: Vec<OtherChunk> = Vec::new();
132116

@@ -141,10 +125,10 @@ pub fn parse_wav_metadata<R: Read + Seek>(
141125
let mut fmt_data = vec![0; chunk_size as usize];
142126
reader.read_exact(&mut fmt_data)?;
143127
let mut cursor = Cursor::new(fmt_data);
144-
format_chunk = Some(WavFormat {
128+
format_chunk = Some(WavFmt {
145129
audio_format: cursor.read_u16::<LittleEndian>()?,
146-
channel_count: cursor.read_u16::<LittleEndian>()?,
147-
sampling_rate: cursor.read_u32::<LittleEndian>()?,
130+
num_channels: cursor.read_u16::<LittleEndian>()?,
131+
sample_rate: cursor.read_u32::<LittleEndian>()?,
148132
bits_per_sample: {
149133
cursor.seek(SeekFrom::Start(14))?;
150134
cursor.read_u16::<LittleEndian>()?
@@ -181,10 +165,10 @@ pub fn load_sample_as_f32(path: &Path) -> Result<(Vec<f32>, SampleMetadata)> {
181165
parse_wav_metadata(&mut reader, path)?;
182166

183167
// Sanity check
184-
if format.sampling_rate != TARGET_SAMPLE_RATE {
168+
if format.sample_rate != TARGET_SAMPLE_RATE {
185169
return Err(anyhow!(
186170
"Attempted to cache non-processed file: {:?} (Rate: {}Hz)",
187-
path, format.sampling_rate
171+
path, format.sample_rate
188172
));
189173
}
190174

@@ -199,7 +183,7 @@ pub fn load_sample_as_f32(path: &Path) -> Result<(Vec<f32>, SampleMetadata)> {
199183

200184
let metadata = SampleMetadata {
201185
loop_info,
202-
channel_count: format.channel_count,
186+
channel_count: format.num_channels,
203187
};
204188

205189
reader.seek(SeekFrom::Start(data_offset))?;
@@ -248,7 +232,7 @@ pub fn process_sample_file(
248232
let target_sample_rate = TARGET_SAMPLE_RATE;
249233
let target_is_float = format.audio_format == 3 && !convert_to_16_bit;
250234

251-
let needs_resample = format.sampling_rate != target_sample_rate || pitch_tuning_cents != 0.0;
235+
let needs_resample = format.sample_rate != target_sample_rate || pitch_tuning_cents != 0.0;
252236
let needs_bit_change = target_bits != format.bits_per_sample || (format.audio_format == 3 && !target_is_float);
253237

254238
// Early exit
@@ -267,7 +251,7 @@ pub fn process_sample_file(
267251
if pitch_tuning_cents != 0.0 {
268252
suffixes.push(format!("p{:+.1}", pitch_tuning_cents));
269253
}
270-
if format.sampling_rate != target_sample_rate {
254+
if format.sample_rate != target_sample_rate {
271255
suffixes.push(format!("{}k", target_sample_rate / 1000));
272256
}
273257

@@ -294,7 +278,7 @@ pub fn process_sample_file(
294278
// Resample if needed
295279
let output_waves = if needs_resample {
296280
let pitch_factor = 2.0f64.powf(pitch_tuning_cents as f64 / 1200.0);
297-
let effective_input_rate = format.sampling_rate as f64 / pitch_factor;
281+
let effective_input_rate = format.sample_rate as f64 / pitch_factor;
298282
let resample_ratio = target_sample_rate as f64 / effective_input_rate;
299283

300284
// Use high-quality Sinc resampler for offline processing
@@ -328,7 +312,7 @@ pub fn process_sample_file(
328312
let new_data_size = final_data_chunk.len() as u32;
329313
let new_bits_per_sample: u16 = target_bits;
330314
let new_audio_format = if target_is_float { 3 } else { 1 };
331-
let new_block_align = format.channel_count * (new_bits_per_sample / 8);
315+
let new_block_align = format.num_channels * (new_bits_per_sample / 8);
332316
let new_byte_rate = target_sample_rate * new_block_align as u32; // Use target rate
333317

334318
let mut other_chunks_total_size: u32 = 0;
@@ -348,7 +332,7 @@ pub fn process_sample_file(
348332
writer.write_all(b"fmt ")?;
349333
writer.write_u32::<LittleEndian>(16)?; // chunk size (minimal PCM)
350334
writer.write_u16::<LittleEndian>(new_audio_format)?;
351-
writer.write_u16::<LittleEndian>(format.channel_count)?;
335+
writer.write_u16::<LittleEndian>(format.num_channels)?;
352336
writer.write_u32::<LittleEndian>(target_sample_rate)?; // <-- Write new rate
353337
writer.write_u32::<LittleEndian>(new_byte_rate)?;
354338
writer.write_u16::<LittleEndian>(new_block_align)?;

0 commit comments

Comments
 (0)