# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. # # Use of this source code is governed by a BSD-style license # that can be found in the LICENSE file in the root of the source # tree. An additional intellectual property rights grant can be found # in the file PATENTS. All contributing project authors may # be found in the AUTHORS file in the root of the source tree. """Echo path simulation module. """ import hashlib import os from . import signal_processing class EchoPathSimulator(object): """Abstract class for the echo path simulators. In general, an echo path simulator is a function of the render signal and simulates the propagation of the latter into the microphone (e.g., due to mechanical or electrical paths). """ NAME = None REGISTERED_CLASSES = {} def __init__(self): pass def Simulate(self, output_path): """Creates the echo signal and stores it in an audio file (abstract method). Args: output_path: Path in which any output can be saved. Returns: Path to the generated audio track file or None if no echo is present. """ raise NotImplementedError() @classmethod def RegisterClass(cls, class_to_register): """Registers an EchoPathSimulator implementation. Decorator to automatically register the classes that extend EchoPathSimulator. Example usage: @EchoPathSimulator.RegisterClass class NoEchoPathSimulator(EchoPathSimulator): pass """ cls.REGISTERED_CLASSES[class_to_register.NAME] = class_to_register return class_to_register @EchoPathSimulator.RegisterClass class NoEchoPathSimulator(EchoPathSimulator): """Simulates absence of echo.""" NAME = 'noecho' def __init__(self): EchoPathSimulator.__init__(self) def Simulate(self, output_path): return None @EchoPathSimulator.RegisterClass class LinearEchoPathSimulator(EchoPathSimulator): """Simulates linear echo path. This class applies a given impulse response to the render input and then it sums the signal to the capture input signal. """ NAME = 'linear' def __init__(self, render_input_filepath, impulse_response): """ Args: render_input_filepath: Render audio track file. impulse_response: list or numpy vector of float values. """ EchoPathSimulator.__init__(self) self._render_input_filepath = render_input_filepath self._impulse_response = impulse_response def Simulate(self, output_path): """Simulates linear echo path.""" # Form the file name with a hash of the impulse response. impulse_response_hash = hashlib.sha256( str(self._impulse_response).encode('utf-8', 'ignore')).hexdigest() echo_filepath = os.path.join( output_path, 'linear_echo_{}.wav'.format(impulse_response_hash)) # If the simulated echo audio track file does not exists, create it. if not os.path.exists(echo_filepath): render = signal_processing.SignalProcessingUtils.LoadWav( self._render_input_filepath) echo = signal_processing.SignalProcessingUtils.ApplyImpulseResponse( render, self._impulse_response) signal_processing.SignalProcessingUtils.SaveWav( echo_filepath, echo) return echo_filepath @EchoPathSimulator.RegisterClass class RecordedEchoPathSimulator(EchoPathSimulator): """Uses recorded echo. This class uses the clean capture input file name to build the file name of the corresponding recording containing echo (a predefined suffix is used). Such a file is expected to be already existing. """ NAME = 'recorded' _FILE_NAME_SUFFIX = '_echo' def __init__(self, render_input_filepath): EchoPathSimulator.__init__(self) self._render_input_filepath = render_input_filepath def Simulate(self, output_path): """Uses recorded echo path.""" path, file_name_ext = os.path.split(self._render_input_filepath) file_name, file_ext = os.path.splitext(file_name_ext) echo_filepath = os.path.join( path, '{}{}{}'.format(file_name, self._FILE_NAME_SUFFIX, file_ext)) assert os.path.exists(echo_filepath), ( 'cannot find the echo audio track file {}'.format(echo_filepath)) return echo_filepath