Главная » Программирование звука » Воспроизведение файлов MOD

0

Теперь, когда мы знаем, как читать файлы, пора подумать о том, как их проигрывать.   Поскольку   класс   ModRead является   наследником   класса   AudioAbstract, ему необходима реализация метода GetSamples. Проще всего реализовать  метод  PlayBeat,  который  проигрывает  один  такт  в  буфер  (или  в  два буфера для стереозвука). Затем GetSamples копирует данные из этого буфера, чтобы ответить на запрос, вызывая по мере надобности PlayBeat для последующего заполнения буфера.

Листинг 23.10. Члены класса МоdRеаd (продолжение)

public:

size_t GetSamples(AudioSample *buffer, size_t numSamples);

Метод  PlayBeat воспроизводит  отсчеты  стереозаписи  одновременно  в  два буфера, и GetSamples должен соответствующим образом чередовать отсчеты.

Листинг 23.3. Программа mod.cpp (продолжение)

size_t ModRead::GetSamples(AudioSample *buff, size_t length) {

int requestedLength = length;

do {

switch(Channels()) {

case 1:  // Моно: копируем  и  преобразовываем.

while ((_sampleLength > 0)&&(length > 0)) {

*buff++ = *_sampleStartLeft++;

_sampleLength–;

length–;

}

break;

case 2:            // Стерео: значения  для каналов

// чередуются.

while ((_sampleLength > 0)&&(length > 0)) {

*buff++ = *_sampleStartLeft++;

*buff++ = *_sampleStartRight++;

_sampleLength–;

length-=2;

}

break;

// Внутренняя  ошибка!

default: cerr << "Internal error\n";

exit(1);        // Такого не   бывает!!!

}

if (length > 0)    // Еще  нужны  данные?

PlayBeat();     // Генерируем   следующий такт.

if (_sampleLength == 0) break;   // Больше нет  данных!

} while (length>0);

return requestedLength-length;

}

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

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

Листинг 23.10. Члены класса ModRead (продолжение)

private:

AudioSample *_sampleBufferLeft;      // Левый   канал. AudioSample *_sampleBufferRight;     // Правый   канал. int _sampleBufferSize;

AudioSample *_sampleStartLeft;       // Позиция

// в sampleBufferLeft.

AudioSample *_sampleStartRight;

int _sampleLength;                   // Длина   области  данных

// в буферах отсчетов.

void SetSampleBufferSize();

Листинг 23.11. Инициализация переменных класса ModRead

_sampleBufferLeft = 0;

_sampleBufferRight = 0;

_sampleBufferSize = 0;

_sampleLength = 0;

Метод  SetSampleBufferSize вызывается  при  любом  изменении  длительности  такта.  Он  отвечает  за  обновление  буферов  для  того,  чтобы  гарантировать наличие  достаточно  большой  области  свободного  места.  Он  может  вычислить  необходимый  размер  по  текущей  частоте  дискретизации  и  двум  параметрам  синxpoнизации MOD: _ticksPerMinute и _ticksPerBeat. O6paтите внимание, что в целях повышения точности я считаю количество тиков в минуту, а не в секунду.

Листинг 23.10. Члены класса ModRead (продолжение)

private:

int _ticksPerMinute;  // Тиков в  минуту. int _ticksPerBeat;

int _samplesPerBeat;

int _samplesPerTick;  // Это   аппроксимация!

Как упоминалось ранее, значения по умолчанию 50 тиков в секунду и 6 ти-

ков на такт.

Листинг 23.11. Инициализация переменных класса ModRead (продолжение)

_ticksPerMinute = 50 * 60;

_ticksPerBeat = 6;

Для простоты будем считать, что в методе SetSampleBufferSiz левый буфер  используется  для  воспроизведения  в  монорежиме,  а  затем,  при  необходимости,  настраивает  правый  буфер.  Он  заново  вычисляет  значения  samplesPerBeat и  samplesPerTick для  использования  внутри  класса.  Заметьте,  что  значение samplesPerTick необязательно в точности кратно samplesPerBeat. Программа,  в  которой  полный  такт  необходимо  проиграть  за  один  тик,  становится  сложнее, зато это обеспечивает более точную общую синхронизацию.

Листинг 23.3. Программа mod.cpp (продолжение)

// Убедимся, что  буфер отсчетов  достаточно  большой. void ModRead::SetSampleBufferSize() {

_samplesPerBeat = SamplingRate() * _ticksPerBeat * 60

/ _ticksPerMinute;

_samplesPerTick = _samplesPerBeat / _ticksPerBeat;

if (_sampleBufferLeft && (_sampleBufferSize >=

_samplesPerBeat))

return;

if (_sampleBufferRight == _sampleBufferLeft)

_sampleBufferRight = 0;

if (_sampleBufferLeft)

delete [] _sampleBufferLeft;

_sampleBufferLeft = new AudioSample[_samplesPerBeat];

switch(Channels()) {

// При  моносигнале оба  канала  используют один и  тот же  буфер.

case 1: _sampleBufferRight = _sampleBufferLeft; break;

case 2:

if (_sampleBufferRight)

delete [] _sampleBufferRight;

_sampleBufferRight = new AudioSample[_samplesPerBeat];

break;

default:

// Недопустимое число  каналов:

cerr << "Illegal number of channels: "

<< Channels()

<< "\n";

exit(1);

}

_sampleBufferSize = _samplesPerBeat;

_sampleStartLeft = _sampleBufferLeft;

_sampleStartRight = _sampleBufferRight;

_sampleLength = 0;   // B буфере сейчас  пусто.

}

Метод PlayBeat

Метод PlayBeat, в сущности, является ядром всего класса. B нем отслеживается  текущий  такт,  различные  ноты  воспроизводятся  в  нужные  буферы  отсчетов и  накладываются  все  нотные  эффекты.  Оставшаяся  часть  данной  главы  посвящена в основном рассмотрению отдельных частей этого большого метода.

PlayBeat также   отвечает   за   проигрывание   нот   в   соответствующих   каналах.  C  каждым  каналом  ассоциируется  некоторое  количество  информации  о  статусе, поэтому используется структура ModChannel.

Листинг 23.10. Члены класса ModRead (продолжение)

private:

// "Живые" на   текущий  момент  ноты. enum {numberChannels = 4};

ModChannel _channel[numberChannels];

void PlayBeat();   // Воспроизводим   следующий такт

// в sampleBuffer.

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

Листинг 23.12. Члены класса ModChannel

public:

ModNoteData _currentNote;

int _currentVolume; ModNote *_liveNote;

unsigned char _defaultParameter[ModNoteData::effectCount]; ModChannel();

~ModChannel();

Листинг 23.3. Программа mod.cpp (продолжение)

ModChannel::ModChannel() {

_liveNote = NULL;

for(int i0=0;i0<ModNoteData::effectCount;i0++)

_defaultParameter [i0] =0;

};

ModChannel::~ModChannel() {

if (_liveNote) delete _liveNote;

};

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

Листинг 23.3. Программа mod.cpp (продолжение)

/* Воспроизводим  данный  такт. */

void ModRead::PlayBeat() { AudioSample *sampleBuffer;

if (_song.Advance()) return;

for(int ch0=0;ch0<numberChannels;ch0++) { ModNoteData currentNote = _song.ThisNote(ch0); switch(currentNote._effect) {

default: break;

SetSampleBufferSize();

memset(_sampleBufferLeft, 0, sizeof(_sampleBufferLeft[0]) * _samplesPerBeat);

if (Channels()==2)

memset(_sampleBufferRight, 0, sizeof(_sampleBufferRight[0]) * _samplesPerBeat);

// Настраиваем  каждый   канал и  осуществляем

// воспроизведение.

for(int ch=0;ch<numberChannels;ch++) {

switch(ch) {

case 0: case 3: case 4: case 7:

sampleBuffer = _sampleBufferLeft; break;

case 1: case 2: case 5: case 6: default:

sampleBuffer = _sampleBufferRight; break;

}

ModNoteData currentNote = _song.ThisNote(ch);

// Если   код   инструмента или   величина периода  не   равны  0,

// начинаем  новую   ноту.

if (   (currentNote._instrument != 0)

|| (currentNote._period != 0)) {

// Во-первых, сверяем  параметры по   умолчанию

// из  предыдущей ноты.

if (currentNote._instrument == 0)

currentNote._instrument =

_channel[ch]._currentNote._instrument;

if (currentNote._period == 0)

currentNote._period =

_channel[ch]._currentNote._period;

if (currentNote._instrument >= numInstruments) {

// Недопустимый номер  инструмента.

cerr << "Illegal instrument number "

<< int(currentNote._instrument) << ".\n";

break;

}

} else { // B противном случае  продолжить исполнение

// предыдущей  ноты. currentNote._instrument =

_channel[ch]._currentNote._instrument;

currentNote._period = _channel[ch]._currentNote._period;

}

if (_channel[ch]._liveNote) {

ModNote &currentLiveNote = *_channel[ch]._liveNote;

int defaultParameter

=

_channel[ch]._defaultParameter[currentNote._effect]; int thisParameter = currentNote._parameter; switch(currentNote._effect) {

default:

// Внутренняя  ошибка: недопустимый  код   эффекта.

cerr << "Internal error: illegal effect code.\n";

break;

}

// Сохраняем  текущий  (обновленный) параметр

// в качестве  нового по   умолчанию.

_channel[ch]._defaultParameter[currentNote._effect]

=thisParameter;

}

// Сохраняем значения  по   умолчанию  до   следующего

// раза.

_channel[ch]._currentNote = currentNote;

}

// Устанавливаем значения  переменных для  метода

// GetSamples.

_sampleLength = _samplesPerBeat;

_sampleStartLeft = _sampleBufferLeft;

_sampleStartRight = _sampleBufferRight;

}

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

Листинг 23.13. Настройка параметров новой ноты

if (currentNote._instrument != 0) {

if ( (currentNote._effect == ModNoteData::tremolo)

&& ((_channel[ch]._defaultParameter[ModNoteData::tremolo] & 4)==0) )

_channel[ch]._currentTremolo = 0;

if ( (currentNote._effect == ModNoteData::vibrato)

||(currentNote._effect == ModNoteData::vibratoPlusVolumeSlide)) {

if ((_channel[ch]._defaultParameter[ModNoteData::vibrato] & 4)==0)

_channel[ch]._currentVibrato = 0;

}

if (currentNote._effect == ModNoteData::pitchSlide) {

if (currentNote._period) {

_channel[ch]._pitchGoal = currentNote._period;

} else if (_channel[ch]._pitchGoal == 0) {

// Прикладной цели нет, так  что  игнорируем этот

// эффект.

currentNote._effect = ModNoteData::none;

currentNote._parameter = 0;

}

// Помещаем   реальное  отношение период/инструмент

// в currentNote.

currentNote._period = _channel[ch]._currentNote._period;

currentNote._instrument =

_channel[ch]._currentNote._instrument;

}

// Замечание: это  не   просто "иначе". if (currentNote._effect != ModNoteData::pitchSlide) {

if (_channel[ch]._liveNote) delete _channel[ch]._liveNote;

_channel[ch]._liveNote =

_instrument[currentNote._instrument]-> NewModNote(currentNote._period);

}

}

Воспроизведение с эффектами

Самый простой случай отсутствие эффектов. Он чаще всего встречается и от-

лично обрабатывается классом SampledInstrument.

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов

case ModNoteData::none: currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 0: арпеджио

Данный  эффект  реализует  быстрое  проигрывание  тройки  нот.  Параметр  рассматривается  как  два  полубайта  x  и  у.  Три  проигрываемые  ноты   это  текущая нота, текущая нота плюс x полутонов и текущая нота плюс у полутонов. Для выполнения   соответствующего   сдвига   нот   я   предварительно   рассчитал   простую таблицу (см. листинг 23.14).

Отметим  одну  неприметную  деталь:  значение  переменной  samplesPerBeat может нацело не делиться на величину samplesPerTick, так что нам следует позаботиться  о  последнем  тике.  Цикл  вычислений  по  samplesPerTick и  условие окончания  сформированы  таким  образом,  что  мы  уверены,  что  цикл  закончится одним тиком раньше. После этого можно проиграть samplesPerBeat-l тиков, что в точности соответствует оставшейся части такта.

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

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::arpeggio:

{ const float halfTones[] = {

0.0F,     1.05946309F, 1.12246205F, 1.18920712F,

1.25992105F, 1.33483985F, 1.41421356F, 1.49830708F,

1.58740105F, 1.68179283F, 1.78179744F, 1.88774863F,

2.0F,     2.11892619F, 2.24492410F, 2.37841423F}; AudioSample *currentSampleBuffer = sampleBuffer;

float pitches[3]; currentLiveNote.SetModPeriod(currentNote._period); pitches[0] = currentLiveNote.Pitch();

pitches[1] = pitches[0] *

halfTones[(thisParameter>>4)&0xF];

pitches[2] = pitches[0] * halfTones[(thisParameter&0xF)];

int currentPitch = 0;

int i ;

for(i=0;i<_samplesPerBeat-_samplesPerTick*3/

2;i+=_samplesPerTick) {

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerTick);

currentSampleBuffer += _samplesPerTick;

if (++currentPitch > 2) currentPitch = 0;

currentLiveNote.Pitch(pitches[currentPitch]);

};

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerBeati);

currentLiveNote.Pitch(pitches[0]);    // Сбрасываем

// первоначальное

// значение высоты

// тона.

}

break;

Эффект 1: нарастающее скольжение

Нарастающее   скольжение,   также   известное   как   нарастающее   портаменто (portamento up), увеличивает высоту тона после каждого  тика, но не в начале или конце  каждого  такта.  Параметр  представляет  собой  8-битное  число,  которое  вычитается  из  значения  периода на  каждом  тике.  Вспомним,  что  уменьшение  периода соответствует увеличению высоты.

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::slideUp:

{

if (thisParameter == 0) thisParameter = defaultParameter; AudioSample *currentSampleBuffer = sampleBuffer;

int i;

for(i=0;i<_samplesPerBeat-_samplesPerTick*3/2;

i+=_samplesPerTick) {

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerTick); currentSampleBuffer += _samplesPerTick; currentNote._period -= thisParameter; currentLiveNote.SetModPeriod(currentNote._period);

};

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerBeati);

}

break;

Эффект 2: ниспадающее скольжение

Это  в  точности то  же  самое,  что  и нарастающее  скольжение, за исключением того, что высота уменьшается (а период увеличивается).

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::slideDown:

{

if (thisParameter == 0) thisParameter = defaultParameter; AudioSample *currentSampleBuffer = sampleBuffer;

int i = 0;

for(;i<_samplesPerBeat-_samplesPerTick;i+=_samplesPerTick)

{

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerTick); currentSampleBuffer += _samplesPerTick; currentNote._period += thisParameter; currentLiveNote.SetModPeriod(currentNote._period);

};

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerBeati);

}

break;

Эффект З: плавная смена высоты

Плавная  смена  высоты,  также  известная  в  качестве  тонального  портаменто (tonal  portamento),  рассматривает  указанную  ноту  как  цель,  к  которой  надо  прийти. Существующая нота постепенно изменяется до тех пор, пока не будет получена указанная нота. Можно продолжать изменение высоты, не указывая новой ноты и  не  повторяя  параметров:  нулевое  значение  параметра  означает,  что  должны  использоваться предыдущие значения.

Аргумент  представляет  собой  8-битную  величину,  которая  добавляется  к  периоду или вычитается из него. Если период достиг целевого значения, его изменение  прекращается,  и  для  последующего  использования  эффекта  необходимо  снова задать новое целевое значение.

Листинг 23.12. Члены класса ModChannel (продолжение)

public:

int _pitchGoal;    // Текущий заданный период  для  эффекта

// плавной  смены   высоты  тона.

Листинг 23.15. Инициализация переменных класса ModChannel

_pitchGoal = 0;

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::pitchSlide:

{

if (thisParameter == 0) thisParameter = defaultParameter; AudioSample *currentSampleBuffer = sampleBuffer;

int i = 0;

for(;i<_samplesPerBeat-_samplesPerTick;i+=_samplesPerTick)

{

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerTick) ;

currentSampleBuffer += _samplesPerTick;

if (_channel[ch]._pitchGoal == 0) {

// Ничего  не   делаем.

} else if (currentNote._period >

_channel[ch]._pitchGoal) {

currentNote._period -= thisParameter;

if (currentNote._period <= _channel[ch]._pitchGoal) {

currentNote._period = _channel[ch]._pitchGoal;

_channel[ch]._pitchGoal = 0;

}

} else {

currentNote._period += thisParameter;

if (currentNote._period >= _channel[ch]._pitchGoal) {

currentNote._period = _channel[ch]._pitchGoal;

_channel[ch]._pitchGoal = 0;

}

}

currentLiveNote.SetModPeriod(currentNote._period);

};

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerBeati);

}

break;

Эффект 4: вибрато

Вибрато   это  периодическое  изменение  высоты  тона.  Оно  реализуется  через   использование   64-элементной   волновой   формы   для   изменения   периода   на каждом   тике.   Параметр   представляет   собой   два   полубайта.   Старший   полубайт определяет   скорость   перемещения   по   волновой   форме,   младший     амплитуду изменений.

Возможно,  вы  обратили  внимание  на  то,  что  эффекты,  изменяющие  период, сначала  изменяют  значение  переменной  currentNote._period,  используемой затем  для  задания  периода.  Это  было  сделано  для  того,  чтобы  его  новые  данные сохранялись  на  протяжении  нескольких  тактов.  B  случае  с  вибрато  важно  сохранить  базовое  значение  периода,  чтобы  последующее  применение  указанного  эффекта начиналось с той же отправной точки. По этой причине в программе не обновляется значение currentNote._period.

Листинг 23.12. Члены класса МоdСhаnnеl (продолжение)

public:

const float *_vibratoWaveform;  // Текущая  форма   волны.

int _currentVibrato;            // Позиция в  волновой форме.

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::vibrato:

{

if (thisParameter == 0)

thisParameter = defaultParameter; int step = (thisParameter >> 4)&0xF; int amplitude = (thisParameter&0xF)*2;

AudioSample *currentSampleBuffer = sampleBuffer;

// Настраиваем  вибрато.

currentLiveNote.SetModPeriod(currentNote._period

+int(_channel[ch]._vibratoWaveform[_channel[ch]._currentVibrato]

*amplitude) );

// Остановить вибрато.

_channel[ch]._currentVibrato += step;

_channel[ch]._currentVibrato &= 63;

int i=0;

for(;i<_samplesPerBeat-_samplesPerTick;i+=_samplesPerTick)

{

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerTick);

currentSampleBuffer += _samplesPerTick;

// Устанавливаем  генератор.

currentLiveNote.SetModPeriod(currentNote._period

+int(_channel[ch]._vibratoWaveform[_channel[ch]._currentVibrato]

*amplitude));

// Остановить вибрато.

_channel[ch]._currentVibrato += step;

_channel[ch]._currentVibrato & = 63;

};

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerBeati);

}

break;

Эффект 5: плавный сдвиг по высоте

с одновременным плавным сдвигом по уровню

Данный  эффект  комбинирует  плавную  смену  высоты  (эффект  3)  и  последовательную  смену  громкости.  Параметр  для  плавной  смены  высоты  берется  из  действующего  значения  по  умолчанию,  в  то  время  как  параметр  рассматриваемого эффекта определяет изменение громкости.

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

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::pitchSlidePlusVolumeSlide:

{

int portParameter

=

_channel[ch]._defaultParameter[ModNoteData::pitchSlide];

// Нет   значения по   умолчанию для  плавного  сдвига  уровня.

int volumeChange = (thisParameter & 0xF0)? (thisParameter >> 4) & 0xF :

-(thisParameter & 0xF);

AudioSample *currentSampleBuffer = sampleBuffer;

int i = 0;

for( ; i<_samplesPerBeat-_samplesPerTick;i + =_samplesPerTick)

{

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerTick) ;

currentSampleBuffer += _samplesPerTick;

// Настраиваем  громкость.

currentLiveNote.SetModVolume(_channel[ch]._currentVolume

+volumeChange);

// Сдвигаем высоту  тона,

if (_channel[ch]._pitchGoal == 0) {

// Ничего  не   делаем.

} else if (currentNote._period >

_channel[ch]._pitchGoal) {

currentNote._period -= portParameter;

if (currentNote._period <= _channel[ch]._pitchGoal) {

currentNote._period = _channel[ch]._pitchGoal;

_channel[ch]._pitchGoal = 0;

}

} else {

currentNote._period += _channel[ch]._pitchGoal;

if (currentNote._period >= _channel[ch]._pitchGoal) {

currentNote._period = _channel[ch]._pitchGoal;

_channel[ch]._pitchGoal = 0;

}

}

currentLiveNote.SetModPeriod(currentNote._period);

};

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerBeat-i);

}

break;

Эффект 6: вибрато плюс сдвиг по громкости

Как и в случае с эффектом 5, здесь идет использование предыдущего вызова эффекта вибрато с одновременным изменением громкости.

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::vibratoPlusVolumeSlide:

{

// Считываем параметры из  последнего  вызова  вибрато. int vibratoParameter

=

_channel[ch]._defaultParameter[ModNoteData::vibrato];

int step = (vibratoParameter >> 4)&0xF;

int amplitude = (vibratoParameter&0xF)*2;

// Для  сдвига  уровня  нет  значений, используемых

// по  умолчанию.

int volumeChange = (thisParameter & 0xF0)? (thisParameter >> 4) & 0xF :

-(thisParameter & 0xF); AudioSample *currentSampleBuffer = sampleBuffer;

// Настраиваем  вибрато.

currentLiveNote.SetModPeriod(currentNote._period

+int(_channel[ch]._vibratoWaveform[_channel[ch]._currentVibrato]

*amplitude));

// Шаг  вибрато.

_channel[ch]._currentVibrato += step;

_channel[ch]._currentVibrato &= 63;

int i=0;

for(;i<_samplesPerBeat-_samplesPerTick;i+=_samplesPerTick)

{

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerTick);

currentSampleBuffer += _samplesPerTick;

// Настраиваем  уровень.

currentLiveNote.SetModVolume(_channel[ch]._currentVolume

+volumeChange);

// Настраиваем  вибрато.

currentLiveNote.SetModPeriod(currentNote._period

+int(_channel[ch]._vibratoWaveform[_channel[ch]._currentVibrato]

*amplitude));

// Шаг  вибрато.

_channel[ch]._currentVibrato += step;

_channel[ch]._currentVibrato &= 63;

};

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerBeat-i);

}

break;

Эффект 7: тремоло

Эффект тремоло очень похож на вибрато, за исключением того, что вместо изменения  периода  происходит  изменение  громкости.  Есть  одна  особенность   амплитуда умножается на 4, а не на 2.

Листинг 23.12. Члены класса ModChannel (продолжение)

public:

const float *_tremoloWaveform; // Используемая волновая  форма.

int _currentTremolo;            // Положение в  этой  волновой

// форме.

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::tremolo:

// Применен  эффект  тремоло. cerr << "Effect tremolo exercised.\n";

{

if (thisParameter == 0)

thisParameter = defaultParameter;

int step = (thisParameter >> 4)&0xF;

int amplitude = (thisParameter&0xF)*2*2; AudioSample *currentSampleBuffer = sampleBuffer;

// Устанавливаем  громкость.

currentLiveNote.SetModVolume(_channel[ch]._currentVolume

+int(_channel[ch]._tremoloWaveform[_channel[ch]._currentTremolo]

*amplitude));

// Шаг  тремоло.

_channel[ch]._currentTremolo += step;

_channel[ch]._currentTremolo &= 63;

int i=0;

for(;i<_samplesPerBeat-_samplesPerTick;i+=_samplesPerTick)

{

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerTick);

currentSampleBuffer += _samplesPerTick;

// Устанавливаем громкость

currentLiveNote.SetModVolume(_channel[ch]._currentVolume

+int(_channel[ch]._tremoloWaveform[_channel[ch]._currentTremolo]

*amplitude));

// Шаг  тремоло.

_channel[ch]._currentTremolo += step;

_channel[ch]._currentTremolo &= 63;

};

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerBeat-i);

}

break;

Эффект 8: не используется

Эффекта с порядковым номером 8 нет.

Эффект 9: изменение смещения в семпле

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

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::setSampleOffset: currentLiveNote.Restart(); currentLiveNote.SetSampleOffset(thisParameter*512); currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 10: плавная смена громкости

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

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::volumeSlide:

{

AudioSample *currentSampleBuffer = sampleBuffer;

int volumeChange = (thisParameter & 0xF0)? (thisParameter >> 4) & 0xF :

-(thisParameter & 0xF);

int i = 0;

for(;i<_samplesPerBeat-_samplesPerTick;i+=_samplesPerTick)

{

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerTick);

currentSampleBuffer += _samplesPerTick;

_channel[ch]._currentVolume += volumeChange;

currentLiveNote.SetModVolume(_channel[ch]._currentVolume);

};

currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerBeat-i);

}

break;

Эффект 11:дальний переход

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

Дальний  переход  обрабатывается  в  начале  метода  PlayBeat,  так  как  он  оказывает  влияние  на  весь  процесс  воспроизведения.  Проигрывание  осуществляется так же, как и для none.

Листинг 23.16. Наложение глобальных эффектов для текущей ноты

case ModNoteData::farJump:

_song.NextIndex(currentNote._parameter); // Переходим

// на   этот трафарет.

_song.NextBeat(0);                     // на   такте 0

break;

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::farJump: currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 12: настройка громкости

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

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::setVolume:

currentLiveNote.SetModVolume(thisParameter);

_channel[ch]._currentVolume=thisParameter; currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 13: разрыв трафарета (ближний переход)

Ближний   переход,  также  известный   как  разрыв  трафарета   (pattern   break), используется  для  преждевременного   прерывания   воспроизведения   текущего   трафарета.   Параметр   представляет   собой   двоично-десятичное   число,   применяемое для  задания  такта,  с  которого  должно  начаться  воспроизведение  следующего  трафарета.  Обычно  этот  параметр  равен  нулю.  Несмотря  на  то  что  64-тактовый  трафарет подходит для музыкального размера 4:4, такой трафарет менее пригоден для размеров  3:4,  6:8  или  других.  Ближний  переход  полезен  для  преждевременного окончания   воспроизведения   трафарета.   Это   позволяет   создать   впечатление,   что длина трафарета отлична от стандартной.

Листинг 23.16. Наложение глобальных эффектов для текущей ноты

(продолжение)

case ModNoteData::smallJump:

_song.AdvanceNextIndex(); // Переход  на   следующий  трафарет.

_song.NextBeat((currentNote._parameter>>4)*10

+ (currentNote._parameter &0x0F)); // Ha этом такте.

break;

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::smallJump: currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 14/0: установка фильтра

Если  первый  полубайт  этого  12-битного  эффекта  равен  14,  то  второй  полу-

байт задает эффект, а третий значение аргумента.

Эффект  установки  фильтра  активирует  или  дезактивирует  фильтр  низких  (до

4000  Гц)  частот  фирмы  Amiga  для  выходного  сигнала.  Так  как  моделирование фильтра  программным  способом  отнимает  много  времени  и  доступно  не  на  всех звуковых картах, этот эффект не слишком распространен и нечасто реализуется.

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::setFilter:

// Эффект   установки  фильтра не   реализован.

cerr << "Effect setFilter not implemented.\n"; currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 14/1: медленный нарастающий сдвиг

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

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::pitchUp:

// Эффект   повышения высоты тона  реализован.

cerr << "Effect pitchUp exercised.\n"; currentNote._period -= thisParameter; currentLiveNote.SetModPeriod(currentNote._period); currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 14/2: медленный ниспадающий сдвиг

Это  то  же  самое, что  и медленный  нарастающий  сдвиг, за  исключением  того,

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

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::pitchDown:

// Эффект   понижения тона  реализован.

cerr << "Effect pitchDown exercised.\n"; currentNote._period += thisParameter; currentLiveNote.SetModPeriod(currentNote._period); currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 14/3: глиссандо

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

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::setGlissando:

// "Эффект   установки глиссандо  не   реализован".

cerr << "Effect setGlissando not implemented.\n"; currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 14/4: настройка волновой формы вибрато

Эффекты   вибрато   и   тремоло   используют   64-элементную   волновую   форму. Можно  применять  три  волновые  формы:  синусоидальную,  прямоугольную  и  пилообразную. Эти три формы выбираются соответственно числами 0, 1 и 2. Кроме

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

Листинг 23.12. Члены класса ModChannel (продолжение)

public:

static const float _waveform[3][64];

// Библиотека  волновых форм.

Листинг 23.17. Волновые формы для вибрато и тремоло

// Волновые  формы  для  вибрато  (изменение высоты тона)

// и  тремоло  (изменение уровня).

static float vibratoTremoloWaveforms[3][64] = {

// Синусоида

{0.0F, .09802F,.19509F,.29028F,.38268F,.47140F,.55557F,

.63439F,.70711F,.77301F,.83147F,.88192F,.92388F,.95694F,

.98079F,.99518F,1.00000F,.99518F,.98079F,.95694F,.92388F,

.88192F,.83147F,.77301F,.70711F,.63439F,.55557F,.47140F,

.38268F,.29028F,.19509F,.09802F,-.00000F,-.09802F,-.19509F,

-.29028F,-.38268F,-.47140F,-.55557F,-.63439F,-.70711F,

-.77301F,

-.83147F,-.88192F,-.92388F,-.95694F,-.98079F,-.99518F,-1.0F,

-.99518F,-.98079F,-.95694F,-.92388F,-.88192F,-.83147F,

-.77301F,

-.70711F,-.63439F,-.55557F,-.47140F,-.38268F,-.29028F,

-.19509F,

-.09802F },

// Прямоугольная  волна: 32×1, потом  32x-1

{1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F,

1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F,

1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F,

1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F, 1.0F,

-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,

-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,

-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,

-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,-1.0F,-1.0F },

// Пилообразная  волна: 0->1, затем -1->0

{0.0F, .03125F,.06250F,.09375F,.12500F,.15625F,.18750F,

.21875F,.25000F,.28125F,.31250F,.34375F,.37500F,.40625F,

.43750F,.46875F,.50000F,.53125F,.56250F,.59375F,.62500F,

.65625F,.68750F,.71875F,.75000F,.78125F,.81250F,.84375F,

.87500F,.90625F,.93750F,.96875F

-1.0F,-.96875F,-.93750F,-.90625F,-.87500F,-.84375F,-.81250F,

-.78125F,-.75000F,-.71875F,-.68750F,-.65625F,-.62500F,

-.59375F,

-.56250F,-.53125F,-.50000F,-.46875F,-.43750F,-.40625F,

-.37500F,

-.34375F,-.31250F,-.28125F,-.25000F,-.21875F,-.18750F,

-.15625F,

-.12500F,-.09375F,-.06250F,-.03125F }

};

Листинг 23.15. Инициализация переменных класса ModChannel

(продолжение)

_vibratoWaveform = &(vibratoTremoloWaveforms[0][0]);

_currentVibrato = 0;

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::setVibrato:

// Реализован  эффект установки  Vibrato.

cerr << "Effect setVibrato exercised.\n";

// Результат  случайного выбора всегда  0.

if ((thisParameter & 3) == 3) thisParameter & = 4;

_channel[ch]._vibratoWaveform

= &(vibratoTremoloWaveforms[thisParameter & 0x3][0]); currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 14/5: точная настройка

Этот эффект позволяет изменять параметр точной настройки для каждой ноты.

Используется редко.

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::setFinetune:

// Эффект   setFinetune не   реализован.

cerr << "Effect setFinetune not implemented.\n"; currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 14/6: зацикливание трафарета

Если параметр равен нулю, то этот эффект отмечает начало цикла. Если параметр не равен нулю, то отмечается конец цикла. B рассмотренном случае параметр определяет количество необходимых повторений цикла.

Листинг 23.16. Наложение глобальныхэффектов для текущей ноты (продолжение)

case ModNoteData::patternLoop:

// Поддержка  циклических эффектов для трафаретов

// не   реализована.

cerr << "Loop pattern effect not implemented.\n";

break;

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::patternLoop: currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 14/7: настройка параметров волновой формы тремоло

Этот  эффект  идентичен  эффекту  настройки  параметров  волновой  формы  виб-

рато (эффект 14/4), но оказывает влияние на волновую форму тремоло.

Листинг 23.15. Инициализация переменных класса ModChannel

(продолжение)

_tremoloWaveform = &(vibratoTremoloWaveforms[0][0]);

_currentTremolo = 0;

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::setTremolo:

// Эффект   настройки  тремоло реализован.

cerr << "Effect setTremolo exercised.\n";

// При  случайном выборе всегда  0

if ((thisParameter & 3) == 3) thisParameter &= 4;

_channel[ch]._tremoloWaveform

= &(vibratoTremoloWaveforms[thisParameter & 0x3][0]); currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 14/8: не используется

Эффекта с таким номером нет.

Эффект 14/9: повторный запуск

Серию  быстрых  ударов  в  барабан  или  дробь  можно  получить  путем  периодической инициализации ноты во время такта. Эффект повторного запуска использует  параметры, задающие  частоту  повторной инициализации  ноты  (в  тиках).  B своей  программе  я  вычисляю  величину  samplesPerTick  количество  отсчетов, которые необходимо проиграть до следующей повторной инициализации.

Листинг 23.14. Воспроизведениеноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::retrigger:

// Перезапуск ноты   несколько раз.

{

if (thisParameter == 0) thisParameter = defaultParameter; AudioSample *currentSampleBuffer = sampleBuffer;

int samplesPerNote = _samplesPerTick * thisParameter;

int i=0;

for(;i<_samplesPerBeat-samplesPerNote-_samplesPerTick/2

;i+=samplesPerNote) {

currentLiveNote.AddSamples(currentSampleBuffer,samplesPerNote); currentSampleBuffer += samplesPerNote; currentLiveNote.Restart();

}; currentLiveNote.AddSamples(currentSampleBuffer,_samplesPerBeati);

}

break;

Эффект 14/10: увеличение громкости

Весьма  напоминает  эффект  медленного  восходящего  сдвига  (эффект  14/1),  но изменяет громкость.

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::volumeUp:

_channel[ch]._currentVolume += thisParameter; currentLiveNote.SetModVolume(_channel[ch]._currentVolume); currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 14/11: уменьшение громкости

To же, что и предыдущий эффект, только уровень понижается.

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::volumeDown:

// Эффект   уменьшения громкости  реализован.

cerr << "Effect volumeDown exercised.\n";

currentLiveNote.SetModVolume(_channel[ch]._currentVolumethisParameter);

currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat);

break;

Эффект 14/12: укорачивание нот

Громкость  ноты  уменьшается  до  нуля  после  проигрывания  заданного  количества   тиков.   Важно   продолжать   проигрывание   ноты   после   уменьшения   громкости,  так  как  в  следующем  такте  громкость  снова  может  увеличиться.  Хотя  для такого   инструмента,   как   фортепиано,   применение   этого   эффекта   малорезультативно,   14/12   также   может   использоваться   для   извлечения   одного   барабанного удара из семпла.

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::cutNote:

//Воспроизводим  ‘parameter’ тактов, затем  понижаем уровень

// до   0 и  продолжаем  воспроизведение.

cerr << "Effect cutNote exercised.\n";

{

if (thisParameter == 0) thisParameter = defaultParameter;

int playSamples = _samplesPerTick * thisParameter;

if (playSamples < _samplesPerBeat) {

currentLiveNote.AddSamples(sampleBuffer, playSamples);

_channel[ch]._currentVolume = 0;

currentLiveNote.SetModVolume(_channel[ch]._currentVolume);

currentLiveNote.AddSamples(sampleBuffer+playSamples,

_samplesPerBeat-playSamples);

} else { // Воспроизвести такт  целиком.

currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat);

}

}

break;

Эффект 14/13: задержка воспроизведения ноты

Эффект  задержки  воспроизведения  ноты  не  начинает  проигрывание  ноты  до тех  пор,  пока  не  пройдет  заданное  количество  тиков.  Некоторые  новые  форматы MOD  слегка  переопределяют  этот  эффект:  изменение  ноты  не  будет  происходить, пока  не  пройдет  заданное  количество  тактов.  При  этом  требуется  продолжение проигрывания предыдущей ноты в начале такта.

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::delayNote:

{

if (thisParameter == 0) thisParameter = defaultParameter;

int skipSamples = _samplesPerTick * thisParameter;

if (skipSamples < _samplesPerBeat)

currentLiveNote.AddSamples(sampleBuffer+skipSamples,

_samplesPerBeat-skipSamples);

}

break;

Эффект 14/14: задержка воспроизведения трафарета

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

Для  реализации  данного  эффекта  я  добавил  в  статусную  информацию  канала

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

Листинг 23.12. Члены класса ModChannel (продолжение)

public:

int _delayPatternCount;   // >= 0, если  задействован  эффект

// задержки воспроизведения

// трафарета.

Листинг 23.15. Инициализация переменныхкласса МоdСhаnnеl

(продолжение)

_delayPatternCount = -1;

Листинг 23.16. Наложение глобальныхэффектовдля текущей ноты

(продолжение)

case ModNoteData::delayPattern:

// Задержка трафарета  это, по   сути, цикл в  один такт.

if (_channel[ch0]._delayPatternCount < 0) { // Начинаем

// новый   цикл

// задержки.

_channel[ch0]._delayPatternCount =

currentNote._parameter;

}

if (_channel[ch0]._delayPatternCount >0) {  // Положительный

// счет.

_song.Back();

_channel[ch0]._delayPatternCount-;  // Счет

// задерживается.

} else {

_channel[ch0]._delayPatternCount = -1;

// Больше задержка

// не   нужна.

}

break;

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::delayPattern: currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 14/15: инвертирующий цикл

Этот   эффект   является   хорошим   примером   изобретательности,   проявляемой программистами  при  разработке  интересных  звуковых  эффектов.  Он  задает  два цикла:  первый   обычный  цикл  воспроизведения,  выбирающий  и  проигрывающий отсчеты; второй цикл перемещения по звуковым данным на более медленной скорости,  при  котором  происходит  инвертирование  значений  отсчетов.  Реализуются два  интересных  эффекта:  звуковые  данные  меняются  по  мере  воспроизведения, и  некоторая  область  звуковых  данных  инвертируется.  Точный  эффект  варьируется в  зависимости  от  длины  области  повторения  звуковых  данных  и  скорости  цикла  инвертирования.  По  существу,  звук  комбинируется  с  переменной  прямоугольной волной.

Параметр  принимает  значение  приращения  из  табл.  23.7.  Ha  каждом  тике  это приращение  прибавляется  к  счетчику.  Когда  значение  счетчика  превышает  128, следующая  выборка  области  повторения  инвертируется.  После  достижения  конца  области  повторения  цикл  продолжается  с  ее  начала.  Эта  процедура  продолжается до тех пор, пока активизирован эффект инвертирующего цикла.

Из-за своей сложности этот эффект реализуется не часто.

Таблица 23.7. Приращения для эффекта инвертирующего цикла

Параметр       0

1

2

3

4

5      6      7      8      9       10    11    12    13    14    15

Приращение  0

5

6

7

8

10    11    13    16    19    22    26    32    43    64    128

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::invertLoop:

// Эффект   инвертирующего цикла  не   реализован.

cerr << "Effect invertLoop not implemented.\n"; currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Эффект 15: установка скорости

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

?  если  параметр  равен  нулю,  воспроизведение  музыкального   произведения приостанавливается;

?  если параметр меньше или равен 32, он определяет количество тиков на такт;

?  если параметр больше 32, он задает количество тиков в секунду, при этом

используется соглашение, что значение 125 представляет  стандартную ско-

рость следования тиков 50 тиков в секунду.

Листинг 23.16. Наложение глобальныхэффектов для текущей ноты (продолжение)

case ModNoteData::setTempo:

if (currentNote._parameter == 0) { // Остановить песню.

// Применена  установка

// нулевого темпа.

cerr << "Set tempo 0 exercised.\n";

_song.Stop();

_sampleLength = 0;

return;

} else if (currentNote._parameter <= 32) {

// Устанавливаем

// количество тиков на   бит.

_ticksPerBeat = currentNote._parameter;

} else {                          // Устанавливаем скорость.

_ticksPerMinute = 50L * 60 * currentNote._parameter/125;

}

break;

}

}

Листинг 23.14. Воспроизведение ноты currentLiveNote

с наложением эффектов (продолжение)

case ModNoteData::setTempo: currentLiveNote.AddSamples(sampleBuffer,_samplesPerBeat); break;

Источник: Кинтцель Т.  Руководство программиста по работе со звуком = A Programmer’s Guide to Sound: Пер. с англ. М.: ДМК Пресс, 2000. 432 с, ил. (Серия «Для программистов»).

По теме:

  • Комментарии