11extern crate rubato;
22use audioadapter_buffers:: direct:: InterleavedSlice ;
3+ use clap:: { Parser , ValueEnum } ;
34use rubato:: {
45 Async , FixedAsync , PolynomialDegree , Resampler , SincInterpolationParameters ,
56 SincInterpolationType , Slip , WindowFunction ,
67} ;
78use std:: convert:: TryInto ;
8- use std:: env;
99use std:: fs:: File ;
1010use std:: io:: prelude:: { Read , Seek , Write } ;
1111use std:: io:: { BufReader , BufWriter } ;
@@ -18,23 +18,71 @@ use log::LevelFilter;
1818const BYTE_PER_SAMPLE : usize = 8 ;
1919
2020// A resampler app that reads a raw file of little-endian 64 bit floats, and writes the output in the same format.
21- // Unlike the `process_all_f64 ` example, which converts between two fixed rates, this one uses one of the
21+ // Unlike the `process_f64 ` example, which converts between two fixed rates, this one uses one of the
2222// *adjustable* resamplers to apply a small, constant rate offset. This is the clock-drift / rate-matching case:
2323// the nominal input and output rates are equal (ratio 1:1), and the resampler is nudged by a user-selected
2424// offset given in parts per million (ppm). A positive offset produces slightly more output frames than input,
2525// a negative offset slightly fewer.
2626//
2727// The offset is applied through the `Resampler::as_adjustable` capability accessor, so the same code drives every
2828// adjustable resampler type without knowing the concrete type. The synchronous FFT resamplers cannot change
29- // ratio and are therefore not offered here; use the `process_all_f64` example for fixed-ratio conversion.
29+ // ratio and are therefore not offered here; use the `process_f64` example for fixed-ratio conversion.
30+ // For a ratio that changes while processing, see the `ramp_ratio_f64` example.
3031//
31- // The command line arguments are resampler type, input filename, output filename, number of channels,
32- // and the rate offset in ppm. The adjustable resamplers support offsets up to roughly +/- 10%.
32+ // The adjustable resamplers support offsets up to roughly +/- 10%.
3333// To apply a +50 ppm offset to the two-channel file `sine_f64_2ch.raw` using the Slip resampler:
3434// ```
35- // cargo run --release --example adjust_ratio_f64 SlipFixedOutput sine_f64_2ch.raw test.raw 2 50
35+ // cargo run --release --example adjust_ratio_f64 sine_f64_2ch.raw test.raw -r SlipFixedOutput -o 50
3636// ```
3737
38+ /// Apply a small constant rate offset to a raw file of 64 bit floats.
39+ #[ derive( Parser ) ]
40+ #[ command( version) ]
41+ struct Options {
42+ /// Raw file of little-endian 64 bit floats to read.
43+ input : String ,
44+
45+ /// Raw file to write, in the same format.
46+ output : String ,
47+
48+ /// Resampler to use. Only the adjustable types are offered, since the
49+ /// synchronous FFT resamplers cannot change ratio.
50+ #[ arg( short, long, value_enum, ignore_case = true , default_value_t = ResamplerType :: SlipFixedOutput ) ]
51+ resampler : ResamplerType ,
52+
53+ /// Number of channels in the file.
54+ #[ arg( short, long, default_value_t = 2 ) ]
55+ channels : usize ,
56+
57+ /// Rate offset in parts per million. Positive gives slightly more output
58+ /// frames than input, negative slightly fewer.
59+ #[ arg( short, long, default_value_t = 50.0 , allow_negative_numbers = true ) ]
60+ offset : f64 ,
61+ }
62+
63+ /// The adjustable resampler types this example can build.
64+ #[ derive( Copy , Clone , PartialEq , Eq , ValueEnum ) ]
65+ enum ResamplerType {
66+ /// Sinc interpolation, fixed input size.
67+ #[ value( name = "SincFixedInput" ) ]
68+ SincFixedInput ,
69+ /// Sinc interpolation, fixed output size.
70+ #[ value( name = "SincFixedOutput" ) ]
71+ SincFixedOutput ,
72+ /// Polynomial interpolation, fixed input size.
73+ #[ value( name = "PolyFixedInput" ) ]
74+ PolyFixedInput ,
75+ /// Polynomial interpolation, fixed output size.
76+ #[ value( name = "PolyFixedOutput" ) ]
77+ PolyFixedOutput ,
78+ /// Slip resampler, fixed input size.
79+ #[ value( name = "SlipFixedInput" ) ]
80+ SlipFixedInput ,
81+ /// Slip resampler, fixed output size.
82+ #[ value( name = "SlipFixedOutput" ) ]
83+ SlipFixedOutput ,
84+ }
85+
3886/// Helper to read an entire file to memory as f64 values
3987fn read_file < R : Read + Seek > ( inbuffer : & mut R ) -> Vec < f64 > {
4088 let mut buffer = vec ! [ 0u8 ; BYTE_PER_SAMPLE ] ;
@@ -66,31 +114,19 @@ fn main() {
66114 let mut builder = Builder :: from_default_env ( ) ;
67115 builder. filter ( None , LevelFilter :: Debug ) . init ( ) ;
68116
69- let resampler_type = env:: args ( ) . nth ( 1 ) . expect (
70- "Please specify a resampler type, one of:\n SincFixedInput\n SincFixedOutput\n PolyFixedInput\n PolyFixedOutput\n SlipFixedInput\n SlipFixedOutput" ,
71- ) ;
72-
73- let file_in = env:: args ( ) . nth ( 2 ) . expect ( "Please specify an input file." ) ;
74- let file_out = env:: args ( ) . nth ( 3 ) . expect ( "Please specify an output file." ) ;
75- println ! ( "Opening files: {}, {}" , file_in, file_out) ;
117+ let opts = Options :: parse ( ) ;
118+ let channels = opts. channels ;
119+ let offset_ppm = opts. offset ;
120+ println ! ( "Opening files: {}, {}" , opts. input, opts. output) ;
76121
77- let channels_str = env:: args ( )
78- . nth ( 4 )
79- . expect ( "Please specify number of channels" ) ;
80- let channels = channels_str. parse :: < usize > ( ) . unwrap ( ) ;
81-
82- let offset_str = env:: args ( )
83- . nth ( 5 )
84- . expect ( "Please specify the rate offset in ppm" ) ;
85- let offset_ppm = offset_str. parse :: < f64 > ( ) . unwrap ( ) ;
86122 let rel_ratio = 1.0 + offset_ppm / 1_000_000.0 ;
87123 println ! (
88124 "Applying a rate offset of {} ppm (relative ratio {})" ,
89125 offset_ppm, rel_ratio
90126 ) ;
91127
92128 println ! ( "Copy input file to buffer" ) ;
93- let file_in_disk = File :: open ( file_in ) . expect ( "Can't open file" ) ;
129+ let file_in_disk = File :: open ( & opts . input ) . expect ( "Can't open file" ) ;
94130 let mut file_in_reader = BufReader :: new ( file_in_disk) ;
95131 let indata = read_file ( & mut file_in_reader) ;
96132 let nbr_input_frames = indata. len ( ) / channels;
@@ -103,8 +139,8 @@ fn main() {
103139 // Every branch is built at the nominal ratio of 1.0. The asynchronous resamplers get a maximum
104140 // relative ratio of 1.1, matching the Slip resampler's built-in +/- 10% range.
105141 let chunk_size = 1024 ;
106- let mut resampler: Box < dyn Resampler < f64 > > = match resampler_type . as_str ( ) {
107- " SincFixedInput" => {
142+ let mut resampler: Box < dyn Resampler < f64 > > = match opts . resampler {
143+ ResamplerType :: SincFixedInput => {
108144 let params = SincInterpolationParameters :: new ( 128 , WindowFunction :: Blackman2 )
109145 . oversampling_factor ( 256 )
110146 . interpolation ( SincInterpolationType :: Quadratic ) ;
@@ -113,7 +149,7 @@ fn main() {
113149 . unwrap ( ) ,
114150 )
115151 }
116- " SincFixedOutput" => {
152+ ResamplerType :: SincFixedOutput => {
117153 let params = SincInterpolationParameters :: new ( 128 , WindowFunction :: Blackman2 )
118154 . oversampling_factor ( 256 )
119155 . interpolation ( SincInterpolationType :: Quadratic ) ;
@@ -122,7 +158,7 @@ fn main() {
122158 . unwrap ( ) ,
123159 )
124160 }
125- " PolyFixedInput" => Box :: new (
161+ ResamplerType :: PolyFixedInput => Box :: new (
126162 Async :: < f64 > :: new_poly (
127163 1.0 ,
128164 1.1 ,
@@ -133,7 +169,7 @@ fn main() {
133169 )
134170 . unwrap ( ) ,
135171 ) ,
136- " PolyFixedOutput" => Box :: new (
172+ ResamplerType :: PolyFixedOutput => Box :: new (
137173 Async :: < f64 > :: new_poly (
138174 1.0 ,
139175 1.1 ,
@@ -144,16 +180,12 @@ fn main() {
144180 )
145181 . unwrap ( ) ,
146182 ) ,
147- " SlipFixedInput" => {
183+ ResamplerType :: SlipFixedInput => {
148184 Box :: new ( Slip :: < f64 > :: new ( chunk_size, channels, FixedAsync :: Input ) . unwrap ( ) )
149185 }
150- " SlipFixedOutput" => {
186+ ResamplerType :: SlipFixedOutput => {
151187 Box :: new ( Slip :: < f64 > :: new ( chunk_size, channels, FixedAsync :: Output ) . unwrap ( ) )
152188 }
153- _ => panic ! (
154- "Unknown or non-adjustable resampler type {}\n Must be one of SincFixedInput, SincFixedOutput, PolyFixedInput, PolyFixedOutput, SlipFixedInput, SlipFixedOutput" ,
155- resampler_type
156- ) ,
157189 } ;
158190
159191 // Recover the adjust-ratio capability from the trait object and apply the offset once. Since the
@@ -193,6 +225,6 @@ fn main() {
193225 ) ;
194226
195227 println ! ( "Write output to file, trimming off the silent frames from both ends." ) ;
196- let mut file_out_disk = BufWriter :: new ( File :: create ( file_out ) . unwrap ( ) ) ;
228+ let mut file_out_disk = BufWriter :: new ( File :: create ( & opts . output ) . unwrap ( ) ) ;
197229 write_file ( & outdata, & mut file_out_disk, nbr_out * channels) ;
198230}
0 commit comments