Синтезатор звука на D с библиотекой dlib

Синтезатор звука на D с библиотекой dlib

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

Параметры звука

При работе с цифровым звуком важно учитывать следующие параметры:

  • Частота дискретизации (Sample Rate): количество измерений амплитуды звука в секунду. Обычно выражается в герцах (Гц). Стандартное значение для CD качества звука — 44100 Гц.
  • Битовая глубина (Bit Depth): количество битов, используемых для представления каждой амплитуды. Чем выше битовая глубина, тем более точно можно представить амплитуду.
  • Количество каналов (Channels): количество отдельных звуковых дорожек. Один канал соответствует моно-звуку, два канала — стерео.

Создание звука с помощью dlib.audio

Библиотека dlib предоставляет множество полезных инструментов для работы с цифровым звуком. Рассмотрим, как создать объект Sound, модифицировать его и сохранить в файл WAV.

Генерация синусоидального сигнала

Пример кода для генерации синусоидального сигнала:

import dlib.audio;
import std.math : PI, sin;
import std.stdio : File;

void generateSineWave() {
    double frequency = 440.0; // Частота сигнала (Гц)
    double duration = 2.0; // Длительность сигнала (секунды)
    int sampleRate = 44100; // Частота дискретизации
    double amplitude = 0.5; // Амплитуда сигнала (0.0 - 1.0)

    // Вычисление количества сэмплов
    int numSamples = cast(int)(sampleRate * duration);

    // Создание объекта Sound
    auto sound = new Sound(sampleRate, 1, numSamples);

    // Заполнение сэмплов синусоидальным сигналом
    foreach (i; 0 .. numSamples) {
        double t = i / sampleRate;
        sound[0][i] = cast(short)(amplitude * 32767 * sin(2 * PI * frequency * t));
    }

    // Сохранение в файл
    auto file = File("sine_wave.wav", "wb");
    sound.writeWave(file);
    file.close();
}

void main() {
    generateSineWave();
}

Использование ADSR-огибающей

ADSR-огибающая (Attack, Decay, Sustain, Release) позволяет моделировать изменение громкости звука в четыре стадии:

  • Attack (Атака): время, за которое звук достигает максимальной громкости.
  • Decay (Спад): время, за которое громкость уменьшается до уровня поддержки.
  • Sustain (Поддержка): уровень громкости, который сохраняется после стадии спада.
  • Release (Затухание): время, за которое громкость уменьшается до нуля после окончания звука.

Пример кода для моделирования ADSR:

import dlib.audio;
import std.math : PI, sin;
import std.stdio : File;

void applyADSR(double[] samples, double attack, double decay, double sustain, double release, int sampleRate) {
    int attackSamples = cast(int)(attack * sampleRate);
    int decaySamples = cast(int)(decay * sampleRate);
    int releaseSamples = cast(int)(release * sampleRate);
    int sustainSamples = samples.length - attackSamples - decaySamples - releaseSamples;

    // Атака
    foreach (i; 0 .. attackSamples) {
        samples[i] *= i / double(attackSamples);
    }

    // Спад
    foreach (i; 0 .. decaySamples) {
        samples[attackSamples + i] *= 1.0 - (1.0 - sustain) * (i / double(decaySamples));
    }

    // Поддержка
    foreach (i; 0 .. sustainSamples) {
        samples[attackSamples + decaySamples + i] *= sustain;
    }

    // Затухание
    foreach (i; 0 .. releaseSamples) {
        samples[attackSamples + decaySamples + sustainSamples + i] *= sustain * (1.0 - i / double(releaseSamples));
    }
}

void generateSineWaveWithADSR() {
    double frequency = 440.0; // Частота сигнала (Гц)
    double duration = 2.0; // Длительность сигнала (секунды)
    int sampleRate = 44100; // Частота дискретизации
    double amplitude = 0.5; // Амплитуда сигнала (0.0 - 1.0)

    double attack = 0.1; // Время атаки (секунды)
    double decay = 0.1; // Время спада (секунды)
    double sustain = 0.7; // Уровень поддержки
    double release = 0.2; // Время затухания (секунды)

    // Вычисление количества сэмплов
    int numSamples = cast(int)(sampleRate * duration);

    // Создание объекта Sound
    auto sound = new Sound(sampleRate, 1, numSamples);

    // Заполнение сэмплов синусоидальным сигналом
    foreach (i; 0 .. numSamples) {
        double t = i / sampleRate;
        sound[0][i] = cast(short)(amplitude * 32767 * sin(2 * PI * frequency * t));
    }

    // Применение ADSR
    auto samples = sound[0];
    applyADSR(samples, attack, decay, sustain, release, sampleRate);

    // Сохранение в файл
    auto file = File("sine_wave_adsr.wav", "wb");
    sound.writeWave(file);
    file.close();
}

void main() {
    generateSineWaveWithADSR();
}

Частотная модуляция

Частотная модуляция (FM) — это метод генерации звука, при котором частота одного сигнала изменяется в зависимости от амплитуды другого сигнала. Пример кода для FM-синтеза:

import dlib.audio;
import std.math : PI, sin;
import std.stdio : File;

void generateFMSound() {
    double carrierFreq = 440.0; // Частота несущего сигнала (Гц)
    double modulatorFreq = 220.0; // Частота модулирующего сигнала (Гц)
    double modulationIndex = 100.0; // Индекс модуляции
    double duration = 2.0; // Длительность сигнала (секунды)
    int sampleRate = 44100; // Частота дискретизации
    double amplitude = 0.5; // Амплитуда сигнала (0.0 - 1.0)

    int numSamples = cast(int)(sampleRate * duration);
    auto sound = new Sound(sampleRate, 1, numSamples);

    foreach (i; 0 .. numSamples) {
        double t = i / sampleRate;
        double modulator = sin(2 * PI * modulatorFreq * t);
        double carrier = sin(2 * PI * carrierFreq * t + modulationIndex * modulator);
        sound[0][i] = cast(short)(amplitude * 32767 * carrier);
    }

    auto file = File("fm_sound.wav", "wb");
    sound.writeWave(file);
    file.close();
}

void main() {
    generateFMSound();
}

Считывание нот из строки и создание объекта звука

Таблица частот для нот:

НотаЧастота (Гц)
C4261.63
D4293.66
E4329.63
F4349.23
G4392.00
A4440.00
B4493.88

Пример кода для считывания нот:

import dlib.audio;
import std.math : PI, sin;
import std.stdio : File;

double getFrequency(string note) {
    switch (note) {
        case "C4": return 261.63;
        case "D4": return 293.66;
        case "E4": return 329.63;
        case "F4": return 349.23;
        case "G4": return 392.00;
        case "A4": return 440.00;
        case "B4": return 493.88;
        default: return 440.00;
    }
}

void generateMelody(string

 melody, double duration, int sampleRate) {
    double amplitude = 0.5;
    int numSamples = cast(int)(sampleRate * duration);
    auto sound = new Sound(sampleRate, 1, numSamples);

    int sampleIndex = 0;
    foreach (note; melody.split(" ")) {
        double frequency = getFrequency(note);
        int noteSamples = cast(int)(sampleRate * (duration / melody.split(" ").length));
        foreach (i; 0 .. noteSamples) {
            double t = (sampleIndex + i) / sampleRate;
            sound[0][sampleIndex + i] = cast(short)(amplitude * 32767 * sin(2 * PI * frequency * t));
        }
        sampleIndex += noteSamples;
    }

    auto file = File("melody.wav", "wb");
    sound.writeWave(file);
    file.close();
}

void main() {
    generateMelody("C4 E4 G4 C5", 4.0, 44100);
}

Полный исходный код программы

import dlib.audio;
import std.math : PI, sin;
import std.stdio : File;

double getFrequency(string note) {
    switch (note) {
        case "C4": return 261.63;
        case "D4": return 293.66;
        case "E4": return 329.63;
        case "F4": return 349.23;
        case "G4": return 392.00;
        case "A4": return 440.00;
        case "B4": return 493.88;
        default: return 440.00;
    }
}

void applyADSR(double[] samples, double attack, double decay, double sustain, double release, int sampleRate) {
    int attackSamples = cast(int)(attack * sampleRate);
    int decaySamples = cast(int)(decay * sampleRate);
    int releaseSamples = cast(int)(release * sampleRate);
    int sustainSamples = samples.length - attackSamples - decaySamples - releaseSamples;

    foreach (i; 0 .. attackSamples) {
        samples[i] *= i / double(attackSamples);
    }

    foreach (i; 0 .. decaySamples) {
        samples[attackSamples + i] *= 1.0 - (1.0 - sustain) * (i / double(decaySamples));
    }

    foreach (i; 0 .. sustainSamples) {
        samples[attackSamples + decaySamples + i] *= sustain;
    }

    foreach (i; 0 .. releaseSamples) {
        samples[attackSamples + decaySamples + sustainSamples + i] *= sustain * (1.0 - i / double(releaseSamples));
    }
}

void generateMelody(string melody, double duration, int sampleRate) {
    double amplitude = 0.5;
    double attack = 0.1;
    double decay = 0.1;
    double sustain = 0.7;
    double release = 0.2;
    int numSamples = cast(int)(sampleRate * duration);
    auto sound = new Sound(sampleRate, 1, numSamples);

    int sampleIndex = 0;
    foreach (note; melody.split(" ")) {
        double frequency = getFrequency(note);
        int noteSamples = cast(int)(sampleRate * (duration / melody.split(" ").length));
        foreach (i; 0 .. noteSamples) {
            double t = (sampleIndex + i) / sampleRate;
            sound[0][sampleIndex + i] = cast(short)(amplitude * 32767 * sin(2 * PI * frequency * t));
        }
        sampleIndex += noteSamples;
    }

    auto samples = sound[0];
    applyADSR(samples, attack, decay, sustain, release, sampleRate);

    auto file = File("melody_adsr.wav", "wb");
    sound.writeWave(file);
    file.close();
}

void main() {
    generateMelody("C4 E4 G4 C5", 4.0, 44100);
}

Этот код создаст файл melody_adsr.wav, содержащий мелодию с примененной ADSR-огибающей, что придаст звуку более естественное звучание.


Карпов Ярослав

Автор статьи:

Обновлено:

24.05.2024


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *