Skip to content

Instantly share code, notes, and snippets.

@bczhc
Last active February 19, 2026 15:58
Show Gist options
  • Select an option

  • Save bczhc/f570e2cd41d2098ecf67cea19ed9b454 to your computer and use it in GitHub Desktop.

Select an option

Save bczhc/f570e2cd41d2098ecf67cea19ed9b454 to your computer and use it in GitHub Desktop.
阴乐产生器。

力竭了

freq-compress in.wav out.wav -c 512
ffmpeg -i out.wav -f s16le - | ffmpeg -f s16le -ac 2 -ar 44100 -i pipe:0 -filter:a 'atempo=0.5' out2.wav -y
mpv out2.wav

全损

freq-compress in.wav out.wav -c 1024 -s 8
ffmpeg -i out.wav -f s16le - | ffmpeg -f s16le -ac 2 -ar 44100 -i pipe:0 -filter:a 'atempo=0.5,atempo=0.5,atempo=0.5' out2.wav -y && mpv out2.wav -y
mpv out2.wav
use clap::Parser;
use hound;
use num_complex::Complex;
use realfft::RealFftPlanner;
use std::f32::consts::PI;
#[derive(Parser, Debug)]
#[command(author, version, about = "频谱线性缩放工具")]
struct Args {
input: String,
output: String,
/// 缩放倍率 (例如 2.0 将频谱压缩一半)
#[arg(short, long, default_value_t = 2.0)]
scale: f32,
/// 处理块的大小
#[arg(short, long, default_value_t = 4096)]
chunk_size: usize,
/// 跳跃步长比例 (0.1 ~ 1.0, 越小越平滑)
#[arg(long, default_value_t = 0.25)]
hop_rate: f32,
/// 是否禁用窗函数 (禁用后会产生大量 Clicking)
#[arg(long, default_value_t = false)]
no_window: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let mut reader = hound::WavReader::open(&args.input)?;
let spec = reader.spec();
let channels = spec.channels as usize;
let full_samples: Vec<f32> = reader
.samples::<i16>()
.map(|s| s.unwrap() as f32 / i16::MAX as f32)
.collect();
let mut processed_channels = Vec::new();
for c in 0..channels {
let channel_data: Vec<f32> = full_samples.iter().enumerate()
.filter(|&(i, _)| i % channels == c)
.map(|(_, &s)| s)
.collect();
processed_channels.push(process_channel(&channel_data, &args));
}
// 采样率随倍率缩放以保持时长
let mut out_spec = spec;
out_spec.sample_rate = (spec.sample_rate as f32 / args.scale) as u32;
let mut writer = hound::WavWriter::create(&args.output, out_spec)?;
let output_len = processed_channels[0].len();
for i in 0..output_len {
for c in 0..channels {
let s = processed_channels[c][i];
writer.write_sample((s.clamp(-1.0, 1.0) * i16::MAX as f32) as i16)?;
}
}
println!("处理完成: {} -> {}", args.input, args.output);
Ok(())
}
fn process_channel(input: &[f32], args: &Args) -> Vec<f32> {
let n = args.chunk_size;
let out_n = (n as f32 / args.scale) as usize;
let hop_in = (n as f32 * args.hop_rate) as usize;
let hop_out = (out_n as f32 * args.hop_rate) as usize;
let mut planner = RealFftPlanner::<f32>::new();
let fft = planner.plan_fft_forward(n);
let ifft = planner.plan_fft_inverse(out_n);
// 准备窗函数 (Hanning)
let window: Vec<f32> = if args.no_window {
vec![1.0; n]
} else {
(0..n).map(|i| 0.5 * (1.0 - (2.0 * PI * i as f32 / (n - 1) as f32).cos())).collect()
};
// 预估输出长度
let expected_len = (input.len() as f32 / args.scale) as usize + out_n;
let mut output = vec![0.0; expected_len];
let mut weight_sum = vec![0.0; expected_len];
let mut pos_in = 0;
let mut pos_out = 0;
while pos_in + n <= input.len() {
// 1. 加窗切片
let mut indata = fft.make_input_vec();
for j in 0..n {
indata[j] = input[pos_in + j] * window[j];
}
// 2. FFT
let mut spectrum = fft.make_output_vec();
fft.process(&mut indata, &mut spectrum).unwrap();
// 3. 频谱重采样 (线性搬移)
let new_spec_len = out_n / 2 + 1;
let mut new_spectrum = vec![Complex::new(0.0, 0.0); new_spec_len];
for j in 0..new_spec_len {
let src_idx = (j as f32 * args.scale).round() as usize;
if src_idx < spectrum.len() {
new_spectrum[j] = spectrum[src_idx];
}
}
new_spectrum[0].im = 0.0;
if let Some(last) = new_spectrum.last_mut() { last.im = 0.0; }
// 4. IFFT
let mut outdata = ifft.make_output_vec();
ifft.process(&mut new_spectrum, &mut outdata).unwrap();
// 5. 重叠相加 (OLA)
let norm = 1.0 / out_n as f32;
for j in 0..out_n {
if pos_out + j < output.len() {
// 如果加了窗,输出时也要再乘一遍窗以保证平滑过渡
let w = if args.no_window { 1.0 } else {
// 对应的输出端窗函数索引
let out_w_idx = (j as f32 * args.scale).round() as usize;
if out_w_idx < n { window[out_w_idx] } else { 0.0 }
};
output[pos_out + j] += outdata[j] * norm * w;
weight_sum[pos_out + j] += w * w;
}
}
pos_in += hop_in;
pos_out += hop_out;
}
// 6. 归一化能量 (防止重叠部分声音变大)
for i in 0..output.len() {
if weight_sum[i] > 1e-6 {
output[i] /= weight_sum[i];
}
}
output
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment