def test_generate_wave(form: str, frequency: float, amplitudes: np.ndarray, frame_rate: int, location: float, max_channel_delay: float, expected: np.ndarray) -> None: """Test `generate_wave` function.""" result = generate_wave(form, frequency, amplitudes, frame_rate, location, max_channel_delay) np.testing.assert_almost_equal(result, expected)
def oscillate_between_sounds(sounds: np.ndarray, frame_rate: int, frequency: float, waveform: str = 'sine') -> np.ndarray: """ Combine multiple sounds into one sound by oscillating between them. :param sounds: array of shape (n_sounds, n_channels, n_frames) :param frame_rate: number of frames per second :param frequency: frequency of oscillations between sound sources :param waveform: form of oscillations wave :return: sound composed from input sounds """ step = 2 / (sounds.shape[0] - 1) thresholds = np.arange(-1, 1 + 1e-7, step) weights = np.tile(thresholds.reshape((-1, 1)), (1, sounds.shape[2])) wave = generate_wave(waveform, frequency, np.ones(sounds.shape[2]), frame_rate) wave = wave[0, :] weights = ((1 - np.abs(weights - wave) / step) * (np.abs(weights - wave) < step)) weights = weights.reshape((weights.shape[0], 1, weights.shape[1])) sound = np.sum(sounds * weights, axis=0) return sound
def tremolo(sound: np.ndarray, frame_rate: int, frequency: float = 6, amplitude: float = 0.5, waveform: str = 'sine') -> np.ndarray: """ Make sound volume vibrating. :param sound: sound to be modified :param frame_rate: number of frames per second :param frequency: frequency of volume oscillations (in Hz) :param amplitude: relative amplitude of volume oscillations, must be between 0 and 1 :param waveform: form of volume oscillations wave :return: sound with vibrating volume """ if not (0 < amplitude <= 1): raise ValueError("Amplitude for tremolo must be between 0 and 1.") amplitudes = amplitude * np.ones(sound.shape[1]) volume_wave = generate_wave(waveform, frequency, amplitudes, frame_rate) volume_wave += 1 sound *= volume_wave return sound
def test_frequency_filter_with_sine_waves(frequency: float, frame_rate: int, min_frequency: float, max_frequency: float, invert: bool, order: int) -> None: """Test that `frequency_filter` function removes requested frequencies.""" sound = generate_wave('sine', frequency, np.ones(frame_rate), frame_rate) result = frequency_filter(sound, frame_rate, min_frequency, max_frequency, invert, order) assert np.sum(np.abs(result)) < 0.01 * np.sum(np.abs(sound))
def synthesize(timbre_spec: TimbreSpec, frequency: float, volume: float, duration: float, location: float, max_channel_delay: float, frame_rate: int) -> np.ndarray: """ Synthesize sound fragment that corresponds to one note. :param timbre_spec: specification of a timbre :param frequency: frequency of fundamental in Hz :param volume: volume of the sound fragment :param duration: duration of fragment to be generated in seconds :param location: location of sound source; -1 stands for extremely left and 1 stands for extremely right :param max_channel_delay: maximum possible delay between channels in seconds; it is a measure of potential size of space occupied by sound sources :param frame_rate: number of frames per second :return: sound wave represented as timeline of pressure deviations """ envelope = timbre_spec.fundamental_volume_envelope_fn(duration, frame_rate) overtones_share = calculate_overtones_share(timbre_spec) fundamental_share = 1 - overtones_share sound = generate_wave(timbre_spec.fundamental_waveform, frequency, volume * fundamental_share * envelope, frame_rate, location, max_channel_delay) for effect_fn in timbre_spec.fundamental_effects: sound = effect_fn(sound, frame_rate) for overtone_spec in timbre_spec.overtones_specs: envelope = overtone_spec.volume_envelope_fn(duration, frame_rate) overtone_sound = generate_wave( overtone_spec.waveform, overtone_spec.frequency_ratio * frequency, volume * overtone_spec.volume_share * envelope, frame_rate, location, max_channel_delay) for effect_fn in overtone_spec.effects: overtone_sound = effect_fn(overtone_sound, frame_rate) sound += overtone_sound return sound
def test_filter_sweep_on_one_band(frequency: float, frame_rate: int, bands: List[Tuple[Optional[float], Optional[float]]], osc_frequency: float, waveform: str) -> None: """Test that `filter_sweep` function runs if there is one band only.""" sound = generate_wave('sine', frequency, np.ones(frame_rate), frame_rate) _ = filter_sweep(sound, frame_rate, bands, frequency=osc_frequency, waveform=waveform)
def test_frequency_filter_with_arbitrary_input(frequencies: List[float], frame_rate: int, min_frequency: float, max_frequency: float, invert: bool, order: int) -> None: """Test that `frequency_filter` function returns something finite.""" waves = [ generate_wave('sine', frequency, np.ones(frame_rate), frame_rate) for frequency in frequencies ] sound = sum(waves) result = frequency_filter(sound, frame_rate, min_frequency, max_frequency, invert, order) assert np.all(np.isfinite(result)) assert np.sum(np.abs(result)) < np.sum(np.abs(sound))
def vibrato(sound: np.ndarray, frame_rate: int, frequency: float = 4, width: float = 0.2, waveform: str = 'sine') -> np.ndarray: """ Make sound frequency vibrating. :param sound: sound to be modified :param frame_rate: number of frames per second :param frequency: frequency of sound's frequency oscillations (in Hz) :param width: difference between the highest frequency of oscillating sound and the lowest frequency of oscillating sound (in semitones) :param waveform: form of frequency oscillations wave :return: sound with vibrating frequency """ semitone = 2**(1 / 12) highest_to_lowest_ratio = semitone**width # If x = 0, d(x + m * sin(2 * \pi * f * x))/dx = 1 + 2 * \pi * f * m. # If x = \pi, d(x + m * sin(2 * \pi * f * x))/dx = 1 - 2 * \pi * f * m. # Ratio of above right sides is `highest_to_lowest_ratio`. # Let us solve it for `m` (`max_delay`). max_delay = ((highest_to_lowest_ratio - 1) / ((highest_to_lowest_ratio + 1) * 2 * np.pi * frequency)) amplitudes = max_delay * frame_rate * np.ones(sound.shape[1]) frequency_wave = generate_wave(waveform, frequency, amplitudes, frame_rate) time_indices = np.ones(sound.shape[1]).cumsum() - 1 + frequency_wave[0, :] upper_indices = np.ceil(time_indices).astype(int) upper_indices = np.clip(upper_indices, 0, sound.shape[1] - 1) upper_sound = sound[:, upper_indices] lower_indices = np.floor(time_indices).astype(int) lower_indices = np.clip(lower_indices, 0, sound.shape[1] - 1) lower_sound = sound[:, lower_indices] weights = time_indices - lower_indices sound = weights * upper_sound + (1 - weights) * lower_sound return sound
def test_muting_of_filter_sweep(frequencies: List[float], frame_rate: int, bands: List[Tuple[Optional[float], Optional[float]]], osc_frequency: float, waveform: str) -> None: """Test that `filter_sweep` function mutes what it must mute.""" waves = [ generate_wave('sine', frequency, np.ones(frame_rate), frame_rate) for frequency in frequencies ] sound = sum(waves) result = filter_sweep(sound, frame_rate, bands, frequency=osc_frequency, waveform=waveform) assert (np.sum(np.abs(result[:, :100])) < 0.01 * np.sum(np.abs(sound[:, :100]))) assert (np.sum(np.abs(result[:, -100:])) > 0.5 * np.sum(np.abs(sound[:, -100:])))
def test_phaser(frequency: float, frame_rate: int) -> None: """Test that `phaser` function runs without failures.""" sound = generate_wave('sine', frequency, np.ones(frame_rate), frame_rate) result = phaser(sound, frame_rate) assert np.all(np.isfinite(result))