1111"""
1212
1313# Importing libraries
14- import math
1514import random
1615import torch
1716import torch .nn .functional as F
17+ import torchaudio
1818from speechbrain .dataio .legacy import ExtendedCSVDataset
1919from speechbrain .dataio .dataloader import make_dataloader
2020from speechbrain .processing .signal_processing import (
@@ -508,21 +508,22 @@ def forward(self, waveform):
508508
509509
510510class Resample (torch .nn .Module ):
511- """This class resamples an audio signal using sinc-based interpolation.
512-
513- It is a modification of the `resample` function from torchaudio
514- (https://pytorch.org/audio/stable/tutorials/audio_resampling_tutorial.html)
511+ """This class resamples audio using the
512+ :class:`torchaudio resampler <torchaudio.transforms.Resample>` based on
513+ sinc interpolation.
515514
516515 Arguments
517516 ---------
518517 orig_freq : int
519518 the sampling frequency of the input signal.
520519 new_freq : int
521520 the new sampling frequency after this operation is performed.
522- lowpass_filter_width : int
523- Controls the sharpness of the filter, larger numbers result in a
524- sharper filter, but they are less efficient. Values from 4 to 10 are
525- allowed.
521+ *args
522+ additional arguments forwarded to the
523+ :class:`torchaudio.transforms.Resample` constructor
524+ **kwargs
525+ additional keyword arguments forwarded to the
526+ :class:`torchaudio.transforms.Resample` constructor
526527
527528 Example
528529 -------
@@ -537,49 +538,28 @@ class Resample(torch.nn.Module):
537538 torch.Size([1, 26087])
538539 """
539540
540- def __init__ (self , orig_freq = 16000 , new_freq = 16000 , lowpass_filter_width = 6 ):
541+ def __init__ (self , orig_freq = 16000 , new_freq = 16000 , * args , ** kwargs ):
541542 super ().__init__ ()
543+
542544 self .orig_freq = orig_freq
543545 self .new_freq = new_freq
544- self .lowpass_filter_width = lowpass_filter_width
545-
546- # Compute rate for striding
547- self ._compute_strides ()
548- assert self .orig_freq % self .conv_stride == 0
549- assert self .new_freq % self .conv_transpose_stride == 0
550546
551- def _compute_strides (self ):
552- """Compute the phases in polyphase filter.
553-
554- (almost directly from torchaudio.compliance.kaldi)
555- """
556-
557- # Compute new unit based on ratio of in/out frequencies
558- base_freq = math .gcd (self .orig_freq , self .new_freq )
559- input_samples_in_unit = self .orig_freq // base_freq
560- self .output_samples = self .new_freq // base_freq
561-
562- # Store the appropriate stride based on the new units
563- self .conv_stride = input_samples_in_unit
564- self .conv_transpose_stride = self .output_samples
547+ self .resampler = torchaudio .transforms .Resample (
548+ orig_freq = orig_freq , new_freq = new_freq , * args , ** kwargs ,
549+ )
565550
566551 def forward (self , waveforms ):
567552 """
568553 Arguments
569554 ---------
570555 waveforms : tensor
571556 Shape should be `[batch, time]` or `[batch, time, channels]`.
572- lengths : tensor
573- Shape should be a single dimension, `[batch]`.
574557
575558 Returns
576559 -------
577560 Tensor of shape `[batch, time]` or `[batch, time, channels]`.
578561 """
579562
580- if not hasattr (self , "first_indices" ):
581- self ._indices_and_weights (waveforms )
582-
583563 # Don't do anything if the frequencies are the same
584564 if self .orig_freq == self .new_freq :
585565 return waveforms
@@ -593,8 +573,15 @@ def forward(self, waveforms):
593573 else :
594574 raise ValueError ("Input must be 2 or 3 dimensions" )
595575
576+ # If necessary, migrate the resampler to the current device, for
577+ # backwards compat with scripts that do not call `resampler.to()`
578+ # themselves.
579+ # Please do not reuse the sample resampler for tensors that live on
580+ # different devices, though.
581+ self .resampler .to (waveforms .device ) # in-place
582+
596583 # Do resampling
597- resampled_waveform = self ._perform_resample (waveforms )
584+ resampled_waveform = self .resampler (waveforms )
598585
599586 if unsqueezed :
600587 resampled_waveform = resampled_waveform .squeeze (1 )
@@ -603,219 +590,6 @@ def forward(self, waveforms):
603590
604591 return resampled_waveform
605592
606- def _perform_resample (self , waveforms ):
607- """Resamples the waveform at the new frequency.
608-
609- This matches Kaldi's OfflineFeatureTpl ResampleWaveform which uses a
610- LinearResample (resample a signal at linearly spaced intervals to
611- up/downsample a signal). LinearResample (LR) means that the output
612- signal is at linearly spaced intervals (i.e the output signal has a
613- frequency of `new_freq`). It uses sinc/bandlimited interpolation to
614- upsample/downsample the signal.
615-
616- (almost directly from torchaudio.compliance.kaldi)
617-
618- https://ccrma.stanford.edu/~jos/resample/
619- Theory_Ideal_Bandlimited_Interpolation.html
620-
621- https://github.com/kaldi-asr/kaldi/blob/master/src/feat/resample.h#L56
622-
623- Arguments
624- ---------
625- waveforms : tensor
626- The batch of audio signals to resample.
627-
628- Returns
629- -------
630- The waveforms at the new frequency.
631- """
632-
633- # Compute output size and initialize
634- batch_size , num_channels , wave_len = waveforms .size ()
635- window_size = self .weights .size (1 )
636- tot_output_samp = self ._output_samples (wave_len )
637- resampled_waveform = torch .zeros (
638- (batch_size , num_channels , tot_output_samp ), device = waveforms .device
639- )
640- self .weights = self .weights .to (waveforms .device )
641-
642- # Check weights are on correct device
643- if waveforms .device != self .weights .device :
644- self .weights = self .weights .to (waveforms .device )
645-
646- # eye size: (num_channels, num_channels, 1)
647- eye = torch .eye (num_channels , device = waveforms .device ).unsqueeze (2 )
648-
649- # Iterate over the phases in the polyphase filter
650- for i in range (self .first_indices .size (0 )):
651- wave_to_conv = waveforms
652- first_index = int (self .first_indices [i ].item ())
653- if first_index >= 0 :
654- # trim the signal as the filter will not be applied
655- # before the first_index
656- wave_to_conv = wave_to_conv [..., first_index :]
657-
658- # pad the right of the signal to allow partial convolutions
659- # meaning compute values for partial windows (e.g. end of the
660- # window is outside the signal length)
661- max_index = (tot_output_samp - 1 ) // self .output_samples
662- end_index = max_index * self .conv_stride + window_size
663- current_wave_len = wave_len - first_index
664- right_padding = max (0 , end_index + 1 - current_wave_len )
665- left_padding = max (0 , - first_index )
666- wave_to_conv = torch .nn .functional .pad (
667- wave_to_conv , (left_padding , right_padding )
668- )
669- conv_wave = torch .nn .functional .conv1d (
670- input = wave_to_conv ,
671- weight = self .weights [i ].repeat (num_channels , 1 , 1 ),
672- stride = self .conv_stride ,
673- groups = num_channels ,
674- )
675-
676- # we want conv_wave[:, i] to be at
677- # output[:, i + n*conv_transpose_stride]
678- dilated_conv_wave = torch .nn .functional .conv_transpose1d (
679- conv_wave , eye , stride = self .conv_transpose_stride
680- )
681-
682- # pad dilated_conv_wave so it reaches the output length if needed.
683- left_padding = i
684- previous_padding = left_padding + dilated_conv_wave .size (- 1 )
685- right_padding = max (0 , tot_output_samp - previous_padding )
686- dilated_conv_wave = torch .nn .functional .pad (
687- dilated_conv_wave , (left_padding , right_padding )
688- )
689- dilated_conv_wave = dilated_conv_wave [..., :tot_output_samp ]
690-
691- resampled_waveform += dilated_conv_wave
692-
693- return resampled_waveform
694-
695- def _output_samples (self , input_num_samp ):
696- """Based on LinearResample::GetNumOutputSamples.
697-
698- LinearResample (LR) means that the output signal is at
699- linearly spaced intervals (i.e the output signal has a
700- frequency of ``new_freq``). It uses sinc/bandlimited
701- interpolation to upsample/downsample the signal.
702-
703- (almost directly from torchaudio.compliance.kaldi)
704-
705- Arguments
706- ---------
707- input_num_samp : int
708- The number of samples in each example in the batch.
709-
710- Returns
711- -------
712- Number of samples in the output waveform.
713- """
714-
715- # For exact computation, we measure time in "ticks" of 1.0 / tick_freq,
716- # where tick_freq is the least common multiple of samp_in and
717- # samp_out.
718- samp_in = int (self .orig_freq )
719- samp_out = int (self .new_freq )
720-
721- tick_freq = abs (samp_in * samp_out ) // math .gcd (samp_in , samp_out )
722- ticks_per_input_period = tick_freq // samp_in
723-
724- # work out the number of ticks in the time interval
725- # [ 0, input_num_samp/samp_in ).
726- interval_length = input_num_samp * ticks_per_input_period
727- if interval_length <= 0 :
728- return 0
729- ticks_per_output_period = tick_freq // samp_out
730-
731- # Get the last output-sample in the closed interval,
732- # i.e. replacing [ ) with [ ]. Note: integer division rounds down.
733- # See http://en.wikipedia.org/wiki/Interval_(mathematics) for an
734- # explanation of the notation.
735- last_output_samp = interval_length // ticks_per_output_period
736-
737- # We need the last output-sample in the open interval, so if it
738- # takes us to the end of the interval exactly, subtract one.
739- if last_output_samp * ticks_per_output_period == interval_length :
740- last_output_samp -= 1
741-
742- # First output-sample index is zero, so the number of output samples
743- # is the last output-sample plus one.
744- num_output_samp = last_output_samp + 1
745-
746- return num_output_samp
747-
748- def _indices_and_weights (self , waveforms ):
749- """Based on LinearResample::SetIndexesAndWeights
750-
751- Retrieves the weights for resampling as well as the indices in which
752- they are valid. LinearResample (LR) means that the output signal is at
753- linearly spaced intervals (i.e the output signal has a frequency
754- of ``new_freq``). It uses sinc/bandlimited interpolation to
755- upsample/downsample the signal.
756-
757- Returns
758- -------
759- - the place where each filter should start being applied
760- - the filters to be applied to the signal for resampling
761- """
762-
763- # Lowpass filter frequency depends on smaller of two frequencies
764- min_freq = min (self .orig_freq , self .new_freq )
765- lowpass_cutoff = 0.99 * 0.5 * min_freq
766-
767- assert lowpass_cutoff * 2 <= min_freq
768- window_width = self .lowpass_filter_width / (2.0 * lowpass_cutoff )
769-
770- assert lowpass_cutoff < min (self .orig_freq , self .new_freq ) / 2
771- output_t = torch .arange (
772- start = 0.0 , end = self .output_samples , device = waveforms .device
773- )
774- output_t /= self .new_freq
775- min_t = output_t - window_width
776- max_t = output_t + window_width
777-
778- min_input_index = torch .ceil (min_t * self .orig_freq )
779- max_input_index = torch .floor (max_t * self .orig_freq )
780- num_indices = max_input_index - min_input_index + 1
781-
782- max_weight_width = num_indices .max ()
783- j = torch .arange (max_weight_width , device = waveforms .device )
784- input_index = min_input_index .unsqueeze (1 ) + j .unsqueeze (0 )
785- delta_t = (input_index / self .orig_freq ) - output_t .unsqueeze (1 )
786-
787- weights = torch .zeros_like (delta_t )
788- inside_window_indices = delta_t .abs ().lt (window_width )
789-
790- # raised-cosine (Hanning) window with width `window_width`
791- weights [inside_window_indices ] = 0.5 * (
792- 1
793- + torch .cos (
794- 2
795- * math .pi
796- * lowpass_cutoff
797- / self .lowpass_filter_width
798- * delta_t [inside_window_indices ]
799- )
800- )
801-
802- t_eq_zero_indices = delta_t .eq (0.0 )
803- t_not_eq_zero_indices = ~ t_eq_zero_indices
804-
805- # sinc filter function
806- weights [t_not_eq_zero_indices ] *= torch .sin (
807- 2 * math .pi * lowpass_cutoff * delta_t [t_not_eq_zero_indices ]
808- ) / (math .pi * delta_t [t_not_eq_zero_indices ])
809-
810- # limit of the function at t = 0
811- weights [t_eq_zero_indices ] *= 2 * lowpass_cutoff
812-
813- # size (output_samples, max_weight_width)
814- weights /= self .orig_freq
815-
816- self .first_indices = min_input_index
817- self .weights = weights
818-
819593
820594class DropFreq (torch .nn .Module ):
821595 """This class drops a random frequency from the signal.
0 commit comments