Синтез музичних інструментів

Подібно до того, як волинка, клавесин і тамбурин виробляють звуки зовсім по-різному, люди експериментували з різноманітними способами цифрового синтезу звуку B цьому розділі ми розглянемо кілька рішень цього завдання

При розробці рішення даної проблеми переслідуються дві достатньо раз-

ві цілі

Перша досягти максимальної схожості звучання з оригінальним інструментом Наприклад, багато виробників синтезаторів будь-якими способами намагаються отримати звук, ідентичний звучанню справжнього фортепіано

Другою метою є отримання звуків музичного якості навіть в тому випадку, якщо ці звуки не мають реальних прототипів

Семплери

Очевидним способом відтворення звучання музичного інструменту є запис звуків, вироблених інструментом, який ми хочемо імітувати Семпл містить в собі одну або кілька записів звуків, для відтворення необхідних нот Ясно, що з одного фрагмента не можна витягти всі потрібні ноти, тому треба або записати всі можливі ноти (що вимагає чимало дискового простору), або зуміти скористатися тональним зсувом вже наявної запису Для економії місця на диску багато звукові формати використовують один фрагмент, а для отримання з нього інших нот доводиться застосовувати тональний зсув

Для виконання справжньої операції тонального зсуву потрібно багато часу тому в деяких звукових програвачах прийнято поширене спрощення: змінюється швидкість відтворення, пропускаючи відліки, щоб отримати апроксимацію частотної варіації Однак даний метод неідеальний Зміна швидкості відтворення змінює не тільки частотний спектр, але і швидкість зміни самого спектру Адже у звичайних інструментах звучання різних нот затихає з однаковими швидкостями Ho, незважаючи на недосконалість, цей метод залишається кращим з варіантів, доступних на багатьох системах

Іншою проблемою, з якою ми стикаємося при використанні одиночної записи, є різниця звучання нот на більшій частині реальних інструментів Саме з цієї причини у багатьох музичних форматах записується кілька фрагментів аж до окремого запису на кожну октаву, зводячи до мінімуму величину тонального зсуву

Базовий клас SampledInstrument насправді являє собою контейнер для вибірок даних Значна частина обробки проводиться в класі SampleNote Змінні _basePitch і _baseSampleRate містять принципово важливу інформацію про семплі чутну висоту тону при відтворенні прототипу на певній швидкості B класі SampleNote ця інформація використовується для визначення перетворення семпла при формуванні звуків різної висоти тону

Лістинг 211 Програма sampledh

#ifndef SAMPLED_H_INCLUDED

#define SAMPLED_H_INCLUDED

#include &quotaudioh&quot

#include &quotinstrumth&quot

class SampledNote : public AbstractNote {

friend class SampledInstrument

}

class SampledInstrument : public AbstractInstrument {

private:

AudioSample *_samples

int _sampleLength int _repeatStart int _repeatEnd float _basePitch

float _baseSampleRate

public: SampledInstrument()

SampledInstrument(AudioSample * samples, int length, int repeatStart, int repeatEnd)

virtual ~SampledInstrument()

void BasePitch(float basePitch, float baseSampleRate)

friend class SampledNote

AbstractNote * NewNote(float pitch, float volume) {

return new SampledNote(this, pitch, volume)

}

}

#endif

При створенні обєкта класу SampledInstrument йому передається запис для використання як шаблон Важлива характеристика цього запису – висота тону на деякій частоті дискретизації По суті справи, обєкт

SampledInstrument буде змінювати частоту дискретизації для коригування

ки висоти тону

Лістинг 212 Програма sampledcpp

#include &ltcmath&gt

#include &quotinstrumth&quot

#include &quotsampledh&quot SampledInstrument::SampledInstrument() {

_basePitch = 440

_baseSampleRate = 8000

_samples = 0

_sampleLength = 0

_repeatStart = _repeatEnd = 0

}

SampledInstrument::SampledInstrument(AudioSample * samples, int length, int repeatStart, int repeatEnd) {

_basePitch = 440

_baseSampleRate = 8000

_samples = 0

if (length> 0) {/ / Копіюємо відліки в локальний буфер

_samples = new AudioSample[length]

for(int i=0 i&ltlength i++)

_samples[i] = samples[i]

}

_repeatStart = repeatStart

_repeatEnd = repeatEnd

_sampleLength = length

}

SampledInstrument::~SampledInstrument() {

if (_samples) delete [] _samples

}

void SampledInstrument::BasePitch(float basePitch, float baseSampleRate) {

_basePitch=basePitch _baseSampleRate=baseSampleRate

}

Клас SampledInstrument на практиці є контейнером для запису інструменту Коли ми запитуємо у нього ноту, він створює обєкт SampledNote, який і виконує основну роботу

Програмі SampledNote необхідно невелику кількість приватних змінних Ясно, що ноті потрібен покажчик на обєкт інструменту Також їй будуть потрібні покажчики на необроблені вибірки і на деякі ключові області всередині них

Лістинг 213 Члени класу SampledNote

private:

SampledInstrument *_instrument AudioSample *_currentSample

AudioSample *_endData AudioSample *_startLoop AudioSample *_endLoop

bool _repeating / / Зациклитися

public:

/ / Щоб припинити відтворення ноти,

/ / Просто перериваємо цикл

void EndNote(float) { _repeating = false }

Конструктор цього класу мінімальний Так як нам необхідно перемикати ноту і встановлювати її висоту і гучність, визначимо ці більш складні операції як окремі функції-члени

Лістинг 213 Члени класу SampledNote (продовження)

protected:

SampledNote(SampledInstrument *instr) SampledNote(SampledInstrument *instr, float pitch, float

volume)

Лістінг212 Програма sampledcpp (продовження)

SampledNote::SampledNote&ltSampledInstrument *instr) {

_instrument = instr

_requestPitch = instr-&gt_basePitch

_requestSampleRate = instr-&gt_baseSampleRate

}

SampledNote::SampledNote(SampledInstrument *instr, float pitch, float volume) {

_instrument = instr

_requestPitch = instr-&gt_basePitch

_requestSampleRate = instr-&gt_baseSampleRate Pitch(pitch)

Volume(volume) Restart()

}

Найпростішою з цих операцій є повторний запуск (рестарт) відтворення ноти, який вимагає повторної ініціалізації різних покажчиків Пізніше буде пояснено значення змінної _fraction

Лістинг 213 Члени класу SampledNote (продовження)

public:

void Restart()

Лістинг 212 Програма sampledcpp (продовження)

void SampledNote::Restart() {

_repeating = true

_currentSample = _instrument-&gt_samples

_endData = _instrument-&gt_samples + _instrument-&gt_sampleLength

_startLoop = _instrument-&gt_samples + _instrument-&gt_repeatStart

_endLoop = _instrument-&gt_samples + _instrument-&gt_repeatEnd

_fraction = 0

}

Для підвищення ефективності значення гучності буде оброблятися засобами плаваючою арифметики Bo час відтворення кожна вибірка буде множитися на константу, а потім ділитися на 8192

Лістинг 213 Члени класу SampledNote (продовження)

private:

int _volume

enum {volumeBits = 13} / / _volume Одно 1/8192

public:

void Volume(float volume) {

_volume = int(volume * (1&lt&ltvolumeBits))

}

float Volume() {

return static_cast&ltfloat&gt(_volume) / (1&lt&ltvolumeBits)

}

Тепер треба описати фактичну логіку процедури відтворення зву-

ка, а потім звернутися до більш складних обчислень значення Pitch

Функція AddSamples варіює швидкість відтворення шляхом зміни швидкості кроків функції по буферу відліків Зміна висоти тону вимагає зміни величини приросту

Прирощення необовязково має бути цілим B даній програмі значення _increment є числом з фіксованою точкою, яке використовується для переміщення покажчика відліків Мінлива _fraction зберігає дробову частину покажчика Кожен раз при зверненні до відліку треба додавати значення

_increment до _fraction, а потім перемістити покажчик на величину, рівну цілої частини _fraction Залишилося частина методу AddSamples являє собою просту «бухгалтерію»

Замість взяття найбільш близького за часом відліку (що призводить до появи деякого спотворення) можна було б використовувати ряд математичних методів для оцінки величини проміжного відліку

Лістинг 213 Члени класу SampledNote (продовження)

private:

enum {fractionBits = 10}

public:

size_t AddSamples(AudioSample *buffer, size_t samples)

Лістинг 212 Програма sampledcpp (продовження)

size_t SampledNote::AddSamples(AudioSample *buffer, size_t samplesRequested) {

int samplesRemaining = samplesRequested

if ( _currentSample) return 0 / / Ні даних

while(samplesRemaining) {

if (_repeating &amp&amp (_currentSample &gt= _endLoop)) {

if (_startLoop == _endLoop) / / Ні циклу

_repeating = false / / He повторювати

else

_currentSample = _endLoop _startLoop

}

if (_repeating &amp&amp (_currentSample &gt= _endData))

return samplesRequested samplesRemaining

/ / Припустимо, що long

/ / Більше, ніж найбільша

/ / Вибірка * (1 << volumeBits)

long newSample = (*_currentSample) *

static_cast&ltlong&gt(_volume)

newSample &gt&gt= volumeBits

*buffer++ += newSample

_fraction += _increment

_currentSample += _fraction &gt&gt fractionBits

/ / 8-бітна порція

_fraction &amp= (1&lt&ltfractionBits)-1

samplesRemaining–

}

return samplesRequested samplesRemaining

}

Для обчислення висоти тону велику роль відіграє значення змінної

_increment, яка керує тим, наскільки швидко покажчик _currentSample переміщається за даними Необхідна величина збільшення залежить від необхідної висоти тону і частоти дискретизації вихідного звуку, а також висоти тону, одержуваної при використанні одиничного приросту на деякій заданій частоті дискретизації Змінні _basePitch і _baseSampleRate в обєкті SampledInstrument містять стандартні (задаються за умовчанням) значення висоти тону і частоти дискретизації Таким чином, щоб коректно задати прирощення, необхідно обчислити відношення бажаної висоти тону до базової і, для отримання значення з фіксованою точкою, провести масштабування

Лістинг 213 Члени класу SampledNote (продовження)

private:

int _increment, _fraction

float _requestPitch

float _requestSampleRate

void SetIncrement()

public:

void Pitch(float pitch) {

_requestPitch  =  pitch SetIncrement()

}

float Pitch() { return _requestPitch }

Лістинг 212 Програма sampledcpp (продовження)

void SampledNote::SetIncrement() {

_increment = int(_requestPitch/_instrument-&gt_basePitch

* _instrument-&gt_baseSampleRate/_instrument-

&gtSamplingRate()

* (1&lt&ltfractionBits))

}

Деяким клієнтам цього класу може знадобитися більш точне управління відтворенням B Зокрема, необхідно змусити звучати інструмент з певного місця запису в семплі

Лістинг 213 Члени класу SampledNote (продовження)

public:

void SetSampleOffset(int offset)

Лістинг 212 Програма sampledcpp (продовження)

void SampledNote::SetSampleOffset(int offset) {

_currentSample = _instrument-&gt_samples + offset

while (_currentSample &gt= _endData) {

if (_startLoop == _endLoop) {

_currentSample = 0

return

}

_currentSample = _startLoop + (_currentSample _endData)

_endData = _endLoop

}

}

Генератор синусоїдального сигналу

Існує безліч способів використання вищеописаного класу SampledInstrument Один спосіб вважати семпли із зовнішнього файлу Інший створити нові класи інструментів, що використовують SampledInstrument B Як приклад другого підходу розглянемо інструмент, який просто програє синусоидальную хвилю Він створює короткий уривок синусоїдальної хвилі, а потім використовує SampledInstrument для відтворення цього семпла

B деяких обєктно-орієнтованих мовах можна організувати підклас SampledInstrument, перевизначивши його конструктор іншим, який давав би дискретне уявлення синусоїди, а потім викликав би звичайний конструктор SampledInstrument Однак такий підхід не працює в C + + Замість цього отримано новий клас AbstractInstrument, з якого виник керований ним клас SampledInstrument

Лістинг 214 Оголошення класу SineWavelnstrument

class SineWavelnstrument: public AbstractInstrument {

private:

SampledInstrument *_sampledInstrument

void CreateInstrument()

public:

SineWaveInstrument() { _sampledInstrument = 0 }

~SineWaveInstrument() {

if (_sampledInstrument)

delete _sampledInstrument

}

AbstractNote *NewNote(float pitch, float volume) { CreateInstrument()

return _sampledInstrument-&gtNewNote(pitch,volume)

}

void SamplingRate(long samplingRate) { AbstractInstrument::SamplingRate(samplingRate) CreateInstrument()

_sampledInstrument-&gtSamplingRate(samplingRate)

}

long SamplingRate() {

return AbstractInstrument::SamplingRate()

}

}

Програмісти, що пишуть на обєктно-орієнтованих мовах, називають цей тип обєктів обєктами-агентами (Proxy object) B суті, він не більше ніж заступник іншого: всю роботу виконує базовий обєкт SampledInstrument Особливий інтерес представляє та частина даного класу, в якій створюється шаблон інструменту і на його основі инициализируется обєкт SampledInstrument Основна частина реалізації буде розглянута в наступному розділі, а поки що треба розібратися в деяких модифікаціях, які роблять звук більш природним

Лістинг 212 Програма sampledcpp (продовження)

void SineWaveInstrument::CreateInstrument() {

if(_sampledInstrument) return

}

Джерело: Кінтцель Т Керівництво програміста по роботі зі звуком = A Programmers Guide to Sound: Пер з англ М: ДМК Пресс, 2000 432 с, іл (Серія «Для програмістів»)

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*