Давайте создадим генератор непрерывной синусоиды с помощью Go. Это может стать первым шагом к созданию вашего революционного синтезатора, кто знает 🙂 Но сначала немного теории, прежде чем мы погрузимся в процесс.
Как работает цифровой звук
Мы знаем, что звуковые волны — это вибрации, проходящие через воздух. Эти колебания вызывают изменения давления воздуха от источника звука (в нашем случае генератора) к приемнику (нашим ушам) с течением времени. Важно отметить, что частицы воздуха не перемещаются вместе с вибрациями к приемнику. Таким образом, давление воздуха и время необходимы только для того, чтобы уловить звук с точки зрения приемника.
Количество колебаний давления в секунду называется частотой звука. Величина разницы давления называется амплитудой звука. Тишина — это когда звуковое давление остается на уровне атмосферного давления.
Частота дискретизации и теорема Найквиста
Для того чтобы представить звуки в цифровом мире, нам необходимо делать выборки уровней звукового давления через регулярные промежутки времени. Количество отсчетов в секунду называется частотой дискретизации. Более высокая частота дискретизации может точно представить звуковой сигнал ценой увеличения вычислительной мощности и памяти, в то время как более низкая частота дискретизации может пропустить высокие частоты. Таким образом, ясно, что частота дискретизации зависит от частоты сигнала. Поскольку человеческий слух ограничен частотным диапазоном от 20 Гц до 20 КГц, наша частота дискретизации должна поддерживать этот диапазон. Но как узнать оптимальную частоту дискретизации для воспроизведения аудиосигнала в слышимом нами частотном спектре? Теорема выборки Найквиста-Шеннона гласит, что можно воспроизвести любой звук, если частота дискретизации в два раза больше самой высокой частоты исходного сигнала. Поскольку большинство людей не могут воспринимать частоты выше 20 кГц, теоретически достаточно частоты дискретизации 40 кГц. Но стандарт качества CD определяется как 44,1кГц/44000Гц.
Что мы будем строить?
Мы собираемся построить генератор звуковых волн, который будет непрерывно излучать синусоидальную волну с заданной частотой в режиме Go.
Мы будем использовать пакет beep go для обработки аудиосигналов и воспроизведения звуков. Beep использует oto под капотом для воспроизведения аудио.
Настройка проекта
Создайте новый каталог и инициализируйте корневой модуль
go mod init github.com/nuwan89/sine-generator
Это создаст корневой модуль следующим образом:
module github.com/nuwan89/sine-generator
go 1.17
Установка зависимостей Go
go get github.com/faiface/beep
go get github.com/hajimehoshi/oto
Инициализация звукового сигнала
Вставьте приведенный ниже код в функцию main
. Не волнуйтесь, мы пройдемся по каждой строке.
func main() {
sr := beep.SampleRate(44100)
speaker.Init(sr, sr.N(time.Second/10)) // sr.N(time.Second/10) = buffer size for duration 1/10 second
sine, _ := SineTone(sr, 100)
speaker.Play(sine)
select {} // makes the program hang forever
}
- Сначала мы определяем частоту дискретизации как 44100 Гц.
- Затем мы инициализируем динамик с частотой дискретизации и размером буфера (одна десятая секунды).
- Вместо того чтобы считывать образцы в реальном времени, динамик считывает их из буфера с задержкой. Это позволяет воспроизводить звук непрерывно, без выпадений и глюков.
- SineTone — это функция-конструктор для стримера, который мы собираемся реализовать, и единственная задача которого — передавать бесконечную последовательность аудиообразцов. Ей нужна частота дискретизации и частота (100 Гц).
- Наконец-то мы можем транслировать звук через динамики с помощью данного стримера 🙂
type SineWave struct {
sampleFactor float64 // Just for ease of use so that we don't have to calculate every sample
phase float64
}
func (g *SineWave) Stream(samples [][2]float64) (n int, ok bool) {
for i := range samples { // increment = ((2 * PI) / SampleRate) * freq
v := math.Sin(g.phase * 2.0 * math.Pi) // period of the wave is thus defined as: 2 * PI.
samples[i][0] = v
samples[i][1] = v
_, g.phase = math.Modf(g.phase + g.sampleFactor)
}
return len(samples), true
}
func (*SineWave) Err() error {
return nil
}
-
SineWave — это просто структура, которая хранит свойства нашей синусоиды. Фаза звуковой волны — это, по сути, текущее положение синусоиды. Она представлена в радианах.
-
Функция Stream является здесь самой важной. Она получает двумерный массив, который мы собираемся заполнить. Каждое измерение представляет левый и правый канал сигнала.
-
Мы перебираем каждую точку выборки и вычисляем значение синуса фазы.
-
Для вычисления следующей фазы нам нужно учесть частоту.
-
math.Modf
дает нам дробную составляющую, чтобы фаза колебалась между 0 и 1.
Для полноты картины я скопировал метод инициализатора SineTone.
func SineTone(sr beep.SampleRate, freq float64) (beep.Streamer, error) {
dt := freq / float64(sr)
if dt >= 1.0/2.0 {
return nil, errors.New("samplerate must be at least 2 times grater then frequency")
}
return &SineWave{dt, 0.1}, nil
}
И еще одно… Вам нужно выполнить команду tidy, чтобы добавить операторы импорта в ваш файл модуля:
go mod tidy
Наконец, вы можете запустить его, используя:
go run main.go
Наслаждайтесь бесконечным великолепием синусоиды 100 Гц! 🙂
Проект на GitHub