вступление

Волновая форма — это форма, которую периодическая функция принимает при отображении на графике в зависимости от времени. Различные волновые формы используются для создания различных типов звуков для обработки звука.

Прежде чем мы погрузимся в изучение различных волновых форм, нам нужно поговорить о том, что входит в создание звука и какую роль играют волновые формы.

Для наших целей звук — это просто периодическая функция (то есть он повторяется через заданный период), которая определяется: функцией, частотой и амплитудой.

Амплитуда

Амплитуда в основном просто контролирует громкость звука, чем выше абсолютное значение амплитуды, тем громче звук.

Частота

частота определяет высоту звука, чем выше частота, тем выше нота (например, C7), чем ниже частота, тем ниже нота (например, D4).

вот пример синусоиды с частотой=220

а вот и та синусоида с частотой=440

Как вы можете видеть, удвоение частоты удваивает количество полных периодов, которые мы можем видеть в наших 200 образцах, чем больше колебаний в данный момент времени (т.е. выше частота), тем выше ноту, которую мы слышим.

Сигналы

Теперь мы рассмотрим тему этой статьи… форму волны! Волновая форма является функцией звука, она принимает частоту, амплитуду и время и преобразует их в периодическую волну со своей собственной уникальной тональностью.

Четыре основных — это синус, квадрат, треугольник и пилообразный.

Синусоиды: y(t) = A * sin(2πft): часто описываются как «чистые» или «гладкие» и обычно ассоциируются с музыкальными тонами (например, для фортепиано или скрипки). также отлично подходит для тюнинга.

Квадратные волны: y(t) = A * sign(sin(2πft)): создают «бипящий» или «жужжащий» звук, который можно использовать для электронных или синтетических тонов.

Треугольные сигналы: y(t) = A * 4 * |t*f - floor(t*f + 0,5)| - A: имеют «полый» или «флейтоподобный» звук и отлично подходят для имитации различных музыкальных инструментов и синтезаторов.

Пилообразные формы волны: y(t) = A * 2 * (t*f - floor(0,5 + t*f)): очень богаты и производят жужжащие полные тона, они часто используются для лидов и базовые линии.

В реальной жизни формы сигналов музыкальных инструментов не так просты, как математически сгенерированные (см. изображение ниже). Но их можно создать, объединив вместе несколько разных математических звуков, чтобы получить что-то близкое к истинному звучанию.

Код

Теперь мы рассмотрим код, который нам нужен, чтобы поэкспериментировать с этими формами сигналов и частотами.

Волновой класс

import pygame
import numpy as np
import time
import matplotlib.pyplot as plt


class Wave:
    def __init__(self, bits, sample_rate, freq, function):
        self.bits = bits
        self.sample_rate = sample_rate
        self.freq = freq
        self.function = function

    def set_freq(self, freq):
        self.freq = freq

    def set_function(self, function):
        self.function = function

    def play(self, duration=1):
        num_samples = int(round(duration * self.sample_rate))
        sound = pygame.sndarray.make_sound(self._get_sound_buffer(num_samples))
        sound.play(loops=1, maxtime=int(duration * 1000))
        time.sleep(duration)

    def plot_wave(self, num_samples=100):
        plt.plot(self._get_sound_buffer(num_samples)[:, 0])

    def _get_sound_buffer(self, num_samples):
        sound_buffer = np.zeros((num_samples, 2), dtype=np.int16)
        amplitude = 2 ** (self.bits - 1) - 1

        for sample_num in range(num_samples):
            t = float(sample_num) / self.sample_rate
            y = self.function(amplitude, self.freq, t)
            sound_buffer[sample_num][1] = y
            sound_buffer[sample_num][0] = y

        return sound_buffer

__инициализация__

Наш класс Wave принимает начальную частоту и функцию (которые можно настроить с помощью сеттеров), а также принимает Sample_rate, который является константой, которую я установил на 441000, и битами, установленными на 16.

Мы выбрали частоту дискретизации 441000, потому что это общепринятая частота для максимального качества при минимальных размерах. Чем выше частота дискретизации, тем лучше качество, но тем больше размер получаемых звуковых файлов.

Установка битов на 16 позволяет нам выразить приличный диапазон громкости (поскольку амплитуда составляет 2 ^ (биты-1) - 1). Так как мы просто воспроизводим звуковые сигналы с одной громкостью, эта битовая глубина подходит.

Подробнее о выборе разрядности и частоты дискретизации вы можете прочитать в этой статье.

_get_sound_buffer

Это самый важный фрагмент кода во всем руководстве.

  1. Мы получаем num_samples, умножая продолжительность ноты на sample_rate (это просто определяет, как долго во временном направлении работает наша функция)
  2. Мы получаем значение y для заданной частоты, амплитуды и времени, передавая их в функцию сигнала.
  3. Мы добавляем это значение y в sound_buffer как для левого, так и для правого канала уха (0 и 1).

plot_wave

Это просто позволяет нам визуализировать волну, генерируя форму волны для заданного количества X.

играть

Здесь используется pygame, чтобы взять созданный нами sound_buffer и воспроизвести его для пользователя.

Настраивать

Теперь нам нужно выполнить некоторые настройки в нашем .ipynb (мы делаем ipynb, чтобы графики можно было легко визуализировать)

import pygame
from Wave import Wave
import math

BITS = 16
SAMPLE_RATE = 44100
pygame.init()
pygame.mixer.pre_init(SAMPLE_RATE, BITS)

Увеличение частоты

Теперь давайте посмотрим, как увеличение частоты влияет на звук на простой синусоиде.

wave = Wave(BITS, SAMPLE_RATE, 220, lambda A, f, t: int(round(A * math.sin(2 * math.pi * f * t))))
wave.play(1)
wave.plot_wave(200)

#increasing frequency
wave.set_freq(440)
wave.play(1)
wave.plot_wave(200)

Вы должны заметить, что вторая нота выше первой, но более того, они звучат очень похоже, потому что удвоение частоты поднимает ее на следующую октаву. Таким образом, частота (C5) * 4 == частота (C6) * 2 == частота (C7). В общем, изменение полутона (или полтона для пианистов) масштабирует частоту на 2 ^ (1/12), поскольку в октаве 12 полутонов.

Волновые функции

Теперь давайте послушаем (и увидим) наши различные формы волны.

# square wave
wave.set_function(lambda A, f, t: -A if math.sin(2 * math.pi * f * t) < 0 else A)
wave.play(1)
wave.plot_wave(200)

# triangle wave
wave.set_function(lambda A, f, t: A * 4 * math.fabs(t*f - math.floor(t*f + 0.5)) - A)
wave.play(1)
wave.plot_wave(200)

# saw tooth
wave.set_function(lambda A, f, t: A * 2 * (t*f - math.floor(0.5 + t*f)))
wave.play(1)
wave.plot_wave(200)

# reverse saw tooth
wave.set_function(lambda A, f, t: A * 2 * (1 - (t*f) % 1) - 1)
wave.play(1)
wave.plot_wave(200)

Шумоподавление

Еще одна вещь, которую я хотел затронуть, — это композиция волновых форм для создания различных звуков. Мы можем комбинировать множество звуков с разными частотами, волновыми формами и амплитудами, чтобы создавать новые классные звуки (например, звук фортепиано или флейты). Но мы также можем комбинировать волны разрушительным образом.

# noise canceling
wave.set_function(lambda A, f, t: A * 2 * (t*f - math.floor(0.5 + t*f)) + A * 2 * (1 - (t*f) % 1) - 1)
wave.play(1)
wave.plot_wave(200)

Здесь я объединил пилообразные и обратные пилообразные волны, в результате они компенсировали друг друга, и в результате не было излучаемого звука. Вот как работают наушники с шумоподавлением, они обнаруживают волну окружающего шума и излучают волну против шума, так что ваше ухо слышит тишину.

Заключение

Теперь, когда вы понимаете, как создавать сигналы из стандартных блоков и как их комбинировать, вы можете пойти и создать свой собственный уникальный виртуальный инструмент или проанализировать и понять формы сигналов некоторых инструментов в повседневной жизни.