Главная » Программирование звука » Синтез в поддиапазонах

0

Хотя  в  форматах  Layer  1  и  Layer  2  данные  хранятся  по-разному,  принципы, лежащие в их основе, одни и те же. Начнем с рассмотрения, каким образом реконструируется ИКМ-звук после декодирования битов. B следующих разделах вы увидите, как для этих уровней биты записываются в файл.

После  предварительного  декодирования  мы  получаем  группы  из  32  отсчетов поддиапазонов. Каждый отсчет это амплитуда поддиапазона конкретной частоты.  Чтобы  реконструировать  выходной  сигнал,  сначала  надо  преобразовать  32 отсчета поддиапазонов в 64 отсчета ИКМ, складывая пакет косинусоид. После этого последовательные наборы ИКМ-значений смешиваются, в результате чего получаются 32 выходных отсчета.

Листинг 14.12. Переменные для декомпрессии Layer 1/Layer2

private:

long *_V[2][16];      // Синтезируемый интервал  для  левого/

// правого каналов.

void Layerl2Synthesis(long *V[16], long *in, int inSamples, AudioSample *out);

void LayerlDecode(); // Декомпрессия  данных формата  Layer1.

void Layer2Decode(); // Декомпрессия  данных формата  Layer2.

B массивах V хранятся 16 групп из 64 отсчетов ИКМ. Каждый набор из 32 отсчетов  поддиапазонов  (в  частотной  области)  восстанавливается  из  64  отсчетов ИКМ. B массивы V записываются 16 наборов значений, обработанных последними.

Листинг 14.13. Инициализация общих переменных Layer 1/Layer 2

for(int ch=0;ch<2;ch++) {    // V хранится  внутри  объекта. for(int i=0;i<16;i++) {

_V[ch][i] = new long[64];

for(int j=0;j<64;j++)

_V[ch][i][j] = 0;

}

}

Поскольку массив V хранится в объекте, его необходимо уничтожить при уда-

лении объекта.

Листинг 14.14. Очистка ресурсов MPEG

for(int ch=0;ch<2;ch++)

for(int i=0;i<16;i++)

delete [] _V[ch][i];

Для  ускорения  вычислений  при  реализации  синтеза  поддиапазонов  я  воспользовался  32-битной  арифметикой  с  фиксированной  точкой.  B  комментарияхсодержатся  допущения,  сделанные  мною  относительно  различных  переменных.  Я  решил, например, что отсчеты поддиапазонов имеют формат 2.16, то есть 2 разряда перед десятичной точкой и 16 после.

Подобная  форма  записи  удобна  для  сохранения  точности  значений.  Напри-

мер,  при  умножении  чисел,  записанных  как  2.15  и  2.13,  в  произведении  будет

28  разрядов  после  десятичной  точки  (15+13)  и  3   перед  (2  разряда  каждого  из множителей соответствуют 1-у разряду плюс знак, у результата будет 2 значащих разряда  плюс  знак),  что  можно  записать  в  виде  3.28.  Так  как  3+28   это  всего

31  разряд,  то  переполнения  32-битного  значения  не  произойдет.  Аналогичныесо-

ображения широко использовались при написании этой программы.

Листинг 14.15. Синтез поддиапазонов Layer 1 и Layer2

void DecompressMpeg::Layerl2Synthesis(

long *V[16],

long *subbandSamples, int numSubbandSamples, AudioSample *pcmSamples) {

long *t = V[15];

for(int i = 15;i>0;i–) // Сдвигаем буферы   V.

V[i] = V[i-1]; V[0] = t;

// Преобразуем отсчеты  поддиапазонов

// в отсчеты ИКМ   в V[0].

Matrix(V[0],subbandSamples,numSubbandSamples);

// Переставляем   коэффициенты

// синтезируемого интервала

// в более  удобном порядке и  масштабируем

// их  в  формат 3.12.

static long D[512];

static bool initializedD = false;

if (!initializedD) { long *nextD = D; for(int j=0;j<32;j++)

for(int i=0;i<16;i+=2) {

*nextD++ = SynthesisWindowCoefficients[j+32*i]>>4;

*nextD++ = SynthesisWindowCoefficients[j+32*i+32]>>4;

}

initializedD = true;

}

// D имеет  формат 3.12, V имеет  формат 6.9, на   выходе

// нужно   1б-битное  число.

long *nextD = D;

for(int j=0;j<32;j++) {

long sample = 0; // 8.16 for(int i=0;i<16;i+=2) {

sample += (*nextD++ * V[i][j]) >> 8;

sample += (*nextD++ * V[i+l][j+32]) >> 8;

}

*pcmSamples++ = sample >> 1;      // Выходные   отсчеты

// являются 16-разрядными.

}

}

Матрицирование

32 отсчета поддиапазонов соответствуют амплитудам 32 различных частот. Ha шаге  матрицирования  производится  преобразование  их  значений  в  64  отсчета ИКМ. Это является ядром всего процесса декодирования.

Название  «матрицирование»  произошло  от  способа  восстановления,  описан-

ного  в  стандарте  ISO.  Стандарт  определяет  особую  матрицу  размером  64×32

I представляет  реконструкцию как процесс умножения  матриц; вектор  из 32 отсчетов поддиапазонов умножается на эту матрицу, и получаются 64 отсчета ИКМ. Эти  значения  подвергаются  дальнейшей  обработке,  в  результате  которой  реконструируются 32 отсчета ИКМ.

Строгое  определение  процесса  матрицирования  заключается  в  том,  что  в  ре-

зультате его выполнения из 32 отсчетов поддиапазонов S0, S1, … , S31 по формуле

31

Vn  = ? Sk  cos[(16 + n)(2k + 1)? / 64]

=0

получается 64 выходных значения V0, V1,…, V63.

Заметим, что каждый ряд этой матрицы представляет собой отличную от дру-

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

Медленное матрицирование

Так как значения в матрице косинусов 64×32 фиксированы, их можно вычислить  заранее,  записать  в  таблицу  и  непосредственно  реализовать  приведенную выше формулу, как это сделано в следующей программе:

Листинг 14.16. Матрицирование отсчетов Layer 1/Layer2медленная версия

static void IntMatrix(long *V, long *subbandSamples) {

int numSubbandSamples = 32;

// N константа, поэтому хранится  в  статичной переменной.

static long N[64][32];

static bool initializedN = false;

if (!initializedN) {

for(int n=0;n<64;n++) {

for(int k=0;k<32;k++)

N[n][k] = static_cast<long>

(8192.0*cos((16+n)* (2*k+l)*(3.14159265358979/

64.0)));

}

initializedN = true;

}

for(int n=0; n<64; n++) {            // Матрицирование. long t=0;                         // Сумма  3 2-х  чисел,

// каждое в  формате 2.21.

long *samples = subbandSamples;  // 1.16. long *matrix = N[n];              // 2.13. for(int k=0;k<numSubbandSamples;k++)

t += (*samples++ **matrix++) >> 8; V[n] = t>>10; // V is .13

}

}

У приведенной программы есть два серьезных недостатка. Во-первых, невысокая  точность.  Каждый  раз,  складывая  или  перемножаем  два  числа  с  фиксированной  точкой,  мы  потенциально  можем  потерять  один  разряд точности  из-за ошибки округления. Так  как для  получения  каждого  выходного значения  требуется  произвести  32  операции  умножения  и  32   сложения,  точность  результата вызывает серьезные опасения.

Самым  большим  недостатком  является  низкая  скорость  выполнения  вычис-

лений.  Для  каждой  операции  перемножения  матриц  требуется  произвести  более

2000 операций умножения. Ha компьютере с 486-м процессором и тактовой частотой 66 МГц выполнение программы матрицирования потребует около двухмиллисекунд. K сожалению, даже при самой низкой из используемых в MPEG-1 частоте дискретизации в 32000 отсчета в секунду на декомпрессию каждой группы из 32 отсчетов можно потратить всего 1 миллисекунду (при этом надо помнить о том, что матрицирование это лишь один из шагов в процессе декомпрессии).

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

метода  можно  было  бы  декодировать MPEG  файл  со  стереозаписью  качества  CD,

выполнять еще что-нибудь в процессе декодирования было бы затруднительно.

Быстрое матрицирование

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

Переписав  формулу  матрицирования,  стоит  привести  ее  к  виду,  отчасти  сходному  с  формулой  дискретного  преобразования  Фурье  (ДПФ).  После  этого  можно использовать  описанные  в  24-й  главе  методы,  что  позволит  нам  разработать  алгоритм, сходный с БПФ.

Чтобы   убедиться,   что   формула   матрицирования   по   форме   сходна   с   ДПФ, вспомним,  что  cos(x)   это  действительная  часть  показательной  функции  комплексного  числа  eix   (i   квадратный  корень  из  -1).  Тогда  я  могу  переписать  формулу для вычислений при матрицировании следующим образом:

31

Vn   = Re al ? ?

? k = 0

S k e

(16 + n )( 2 k +1) ?  / 64  ?

?

?

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

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

V   = Re al ?

?

31

e i (16 + n ) ?  / 64 ?

k = 0

S k e

(16 + ) 2 k? / 64  ?

?

?

Теперь применим одну хитрость пронумеруем Vn как V16, V17,…,V79. B резуль-

тате мы можем отбросить член «16+»:

V   = Re al ?

?

31

e in ?  / 64 ?

k = 0

S k e

i?nk / 32  ?

?

?

Если  нам  удастся  придумать,  как  быстро  вычислить  внутреннюю  сумму,  то  мы сможем  просто  домножить  ее  на  константу  е’"р/64, взять  от  результата  действительную  часть  и  обратно  перенумеровать  все  должным  образом,  получив  тем  самым искомый результат. Итак, нужен быстрый способ вычисления суммы.

Наша  сумма  очень  похожа  на  формулу  ДПФ,  описанного  в  24  главе.  Мы  име-

ем дело с одним из частных случаев преобразования более общего вида:

?1

?

= 0

S k e

i?nk / N

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

? ( / 2 ) ?1

?

i?( 2 ) / N  ?

? ( / 2 ) ?1

?

i?( 2 +1) / N  ?

?        S k e

? + ?

2 k +1e                ?

?   = 0

?    ?   = 0                                             ?

Следуя  выводу  алгоритма  БПФ,  запишем  формулу  в  виде  двух  преобразова-

ний с числом точек N/2:

( N / 2 )?1

?  ? k e

?   =0

i?nk /( N / 2 ) ?

? + e

?

i?n / N ?

?

?

( N / 2 )?1

? +1e

=0

i?nk / N  ?

?

?

Если  внимательно  посмотреть  на  эту  формулу,  становится  ясно:  определить преобразование  для  32  точек  (что  нам  требуется)  можно  путем  вычисления  двух преобразований для 16 точек. A то же самое для 16 точек получим через преобразования  для  8  точек  и  т.д.  B  конце  концов  эта  формула  покажет  вам,  как  из  16 двухточечных преобразований получить то же самое для 32 точек.

Теперь вспомним, что преобразование 32 точек дает 64 значения. Аналогичным образом  преобразование  двух  точек дает 4  значения. Если  исходными значениями являются а и b, то двухточечное преобразование даст нам a+b, a+ib, a-b и a-ib.

Основываясь на проделанной работе и используя приемы, описанные в 24-й главе, мы можем построить предлагаемый ниже алгоритм. B этой функции используются несколько способов оптимизации работы, которые так же могут быть применены к реализации БПФ, о чем идет речь в 24 главе.

Листинг 14.17. Матрицирование отсчетов Layer 1 и Layer 2 быстрая версия

// Ha вход  подаются отсчеты  поддиапазонов  в  формате 2.16. static void Matrix (long *V, long *subbandSamples, int numSamples) {

for(int i=numSamples;i<32;i++)

subbandSamples[i]=0 ;

static const double PI=3.14159265358979323846;

long *workR=V;     // Повторное использование  V в качестве

// рабочего хранилища.

long workI[64];    // Мнимая  часть.

static const, char order[] =

{0,16,8,2 4,4,20,12,28,2,18,10,26,6,22,14,30,

1,17,9,25,5,21,13,29,3,19,11,27,7,23,15,31};

// B этой  части  шага инициализации

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

// 2-точечных преобразований  (из  двух  входных четыре выходных).

// Пользуемся  тем, что у  входных значений есть  только

// действительная часть.

long *pWorkR = workR;     // Формат   2.16.

long *pWorkI = workI;     // Формат   2.16.

const char *next = order;

for(int n=0;n<16;n++) {

long a = subbandSamples[*next++];

long b = subbandSamples[*next++];

*pWorkR++ = a+b; *pWorkI++ = 0;

*pWorkR++ = a;  *pWorkI++ = b;

*pWorkR++ = a-b; *pWorkI++ = 0;

*pWorkR++ = a;   *pWorkI++ = b;

}

//

Это   быстрая версия  преобразования,

//

описанного в стандарте  ISO.

//

Используются те  же  принципы, что и  для БПФ, но   наше

//

преобразование  преобразованием Фурье   HE является.

//

Для  экономии времени заранее  вычисляем

//

все  значения  фазовых сдвигов.

static long phaseShiftsR[32], phaseShiftsI[32];    // 1.14.

static bool initializedPhaseShifts = false;

if (!initializedPhaseShifts) {   // Однократная  инициализация.

for(int i=0;i<32;i++) {       // 1.14.

phaseShiftsR[i] = static_cast<long>(16384.0*cos(i*(PI/

32.0)));

phaseShiftsI[i] = static_cast<long>(16384.0*sin(i*(PI/

32.0)));

}

initializedPhaseShifts = true;

}

// При  каждой итерации отбрасывается  один значащий бит.

// B результате  получаем один лишний   бит,

// защищающий  от переполнения.

int phaseShiftIndex, phaseShiftStep = 8;

for(int size=4; size<64; size <<= 1) {

// Так   как  величина первого фазового сдвига  всегда  равна 1,

// можно  сократить  количество  операций, продублировав цикл,

for(int n=0; n < 64; n += 2*size) { long tR = workR[n+size]; workR[n+size] = (workR[n] tR)>>1; workR[n] = (workR[n] + tR)>>1;

long tI = workI[n+size]; workI[n+size] = (workI[n] tI)>>1; workI[n] = (workI[n] + tI)>>1;

}

phaseShiftIndex = phaseShiftStep;

for(int fftStep = 1; fftStep < size; fftStep++) { long phaseShiftR = phaseShiftsR[phaseShiftIndex]; long phaseShiftI = phaseShiftsI[phaseShiftIndex]; phaseShiftIndex += phaseShiftStep;

for(int n=fftStep; n < 64; n += 2*size) {

long tR = (phaseShiftR*workR[n+size]

phaseShiftI*workI[n+size])>>14 ;

long tI = (phaseShiftR*workI[n+size]

+ phaseShiftI*workR[n+size])>>14; workR[n+size] = (workR[n] tR)>>1; workI[n+size] = (workI[n] tI)>>1; workR[n] = (workR[n] + tR)>>1;

workI[n] = (workI[n] + tI)>>1;

}

}

phaseShiftStep /= 2;

}

// Получаем окончательные  значения  V, обрабатывая

// полученные  в результате  преобразования выходные  значения.

{

static long vShiftR[64], vShiftI[64];      // 1.13 static bool initializedVshift = false;

int n;

if (!initializedVshift) {   // Инициализируем  только

// один  раз.

for(n=0;n<32;n++) {      // 1.14 vShiftR[n] =

static_cast<long>(163 84.0*cos((32+n)*(PI/64.0)));

vShiftI[n] =

static_cast<long>(163 84.0*sin((32+n)*(PI/64.0)));

}

initializedVshift = true;

}

// Теперь получаем значения  V из  комплексных чисел,

// полученных  в результате  преобразований.

long *pcmR = workR+33;       // 6.12 long *pcmI = workI+33;       // 6.12

V[16] = 0;    // V[16] всегда  равно 0

for(n=l;n<32;n++) {          // V действительная

// часть, V имеет  формат 6.9.

V[n+16] = (vShiftR[n] * *pcmR++ vShiftI[n] *

*pcmI++)>>15;

}

V[48] = (-workR[0])>>1;      // vShift[32] всегда  равно -1.

// Пользуемся  симметрией

// результата.

for(n=0;n<16;n++) V[n] = V[32-n];

for(n=l;n<16;n++) V[48+n] =V[48-n];

}

}

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

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

Для  конкретики  в  табл.  14.5  указано,  сколько  нужно  времени  для  проведения матрицирования   32   значений   в   нескольких   различных   реализациях   алгоритма. B предыдущем разделе я привел безхитростную программу «медленной» версии в «быстрой» версии используется алгоритм, разработкой которого мы только что занимались. Напомню, что в таблице указано время выполнения только операции матрицирования. Отмечу, что для воспроизведения стереосигнала CD-качества необходимо производить обработку каждой группы из 32 отсчетов за 360 мкс.

Таблица 14.5. Временные характеристики для различных реализаций операции матрицирования

Процессор     Компилятор             ОС                      Целочисленный           С плавающей точкой

Быстрый      Медлен-

Быстрый     Медлен-

                                                                                                           ный                                ный           

Intel

486DX2/66

Intel

GCC 2.7.2

VC++ 5.0

FreeBSD2.1

Windows 95

400

450

1200

1120

720

1390

1100

960

486DX2/66

CW11

Mac OS 7.6

55

90

103

113

PowerPC

603e/2OO

Intel

VC++5.0

Windows 95

100

340

230

210

Pentium/90

Время везде указано в микросекундах.

Как  вы  можете  убедиться,  оптимизация  такого  рода  в  лучшем  случае  является   «черной   магией».   Целочисленная   версия,   применяющая   алгоритм,   описанный в этом разделе, работает в 2-3 раза быстрее (обеспечивая до 30% увеличения скорости  работы  декодера  в  целом).  Версии,  использующие  арифметику  с  плавающей точкой, ведут себя абсолютно по-разному: на второй системе «быстрая» реализация с плавающей точкой работает на 50% медленнее, чем «медленная».

Коэффициенты взвешивания

B  стандарт  ISO  включена  таблица  из  512  девятизначных  чисел  с  плавающей точкой     таблица   коэффициентов   взвешивания.   Показать,   что   каждое   из   этих чисел  в  точности  соответствует  степени  числа  1/65536  не  удается.  Так  же  нельзя сказать, что эта таблица симметрична, поскольку элементы с 257 по 511 это числа,  противоположные числам  с 1  по  255,  взятым  в  обратном  порядке. Если  поделить каждое из чисел, представленных в следующей таблице, на 65536, то в результате получатся числа с плавающей точкой, совпадающие с указанными в стандарте.

Листинг 14.18. Коэффициенты синтезируемого интервала для Layer 1 и Layer 2

static long SynthesisWindowCoefficients[] =  // Числа

// с  фиксированной

// точкой

// формата  2.16.

{0, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -3, -3, -4, -4, -5,

-5, -6, -7, -7, -8, -9, -10, -11, -13, -14, -16, -17, -19, -21,

-24, -26,

-29, -31, -35, -38, -41, -45, -49, -53, -58, -63, -68, -73, -79,

-85, -91, -97, -104, -111, -117, -125, -132, -139, -147, -154,

-161,-169, -176, -183, -190, -196, -202, -208,

213, 218, 222, 225, 227, 228, 228, 227, 224, 221, 215, 208, 200,

189, 177, 163, 146, 127, 106, 83, 57, 29, -2, -36, -72, -111,

-153, -197,-244, -294, -347, -401,

-459, -519, -581, -645, -711, -779, -848, -919, -991, -1064,

-1137, -1210, -1283, -1356, -1428, -1498, -1567, -1634, -1698,

-1759, -1817, -1870, -1919, -1962, -2001, -2032, -2057, -2075,

-2085, -2087, -2080, -2063,

2037, 2000, 1952, 1893, 1822, 1739, 1644, 1535, 1414, 1280, 1131,

970, 794, 605, 402, 185, -45, -288, -545, -814, -1095, -1388,

-1692, -2006, -2330, -2663, -3004, -3351, -3705, -4063, -4425,

-4788,

-5153, -5517, -5879, -6237, -6589, -6935, -7271, -7597, -7910,

-8209, -8491, -8755, -8998, -9219, -9416, -9585, -9727, -9838,

-9916, -9959, -9966, -9935, -9863, -9750, -9592, -9389, -9139,

-8840, -8492, -8092, -7640, -7134,

6574, 5959, 5288, 4561, 3776, 2935, 2037, 1082, 70, -998, -2122,

-3300, -4533, -5818, -7154, -8540, -9975, -11455, -12980, -14548,

-16155, -17799, -19478, -21189, -22929, -24694, -26482, -28289,

-30112, -31947, -33791, -35640,-37489, -39336, -41176, -43006,

-44821, -46617, -48390, -50137, -51853, -53534, -55178, -56778,

-58333, -59838, -61289, -62684, -64019, -65290, -66494, -67629,

-68692, -69679, -70590, -71420, -72169, -72835, -73415, -73908,

-74313, -74630, -74856, -74992, 75038, 74992, 74856, 74630,

74313,

73908,

73415,

72835,

72169,

71420,

70590,

69679,

68692,

67629,

66494,

65290,

64019,

62684,

61289,

59838,

58333,

56778,

55178,

53534,

51853,

50137,

48390,

46617,

44821,

43006,

41176,

39336,

37489,

35640,

33791,

31947,

30112,

28289,

26482,

24694,

22929,

21189,

19478,

17799,

16155,

14548,

12980,

11455,

9975,

8540, 7154, 5818, 4533, 3300, 2122, 998, -70, -1082, -2037,

-2935, -3776, -4561, -5288, -5959, 6574, 7134, 7640, 8092, 8492,

8840, 9139, 9389, 9592, 9750, 9863, 9935, 9966, 9959, 9916, 9838,

9727, 9585, 9416, 9219, 8998, 8755, 8491, 8209, 7910, 7597, 7271,

6935, 6589, 6237, 5879, 5517, 5153, 4788, 4425, 4063, 3705, 3351,

3004, 2663, 2330, 2006, 1692, 1388, 1095, 814, 545, 288, 45,

-185, -402, -605, -794, -970, -1131, -1280, -1414, -1535, -1644,

-1739, -1822, -1893, -1952, -2000, 2037, 2063, 2080, 2087, 2085,

2075, 2057, 2032, 2001, 1962, 1919, 1870, 1817, 1759, 1698, 1634,

1567, 1498, 1428, 1356, 1283, 1210, 1137, 1064, 991, 919, 848,

779, 711, 645, 581, 519,

459, 401, 347, 294, 244, 197, 153, 111, 72, 36, 2, -29, -57, -83,

-106, -127, -146, -163, -177, -189, -200, -208, -215, -221, -224,

-227, -228, -228, -227, -225, -222, -218,

213, 208, 202, 196, 190, 183, 176, 169, 161, 154, 147, 139, 132,

125, 117, 111, 104, 97, 91, 85, 79, 73, 68, 63, 58, 53, 49, 45,

41, 38, 35, 31,

29, 26, 24,

21, 19, 17,

16, 14, 13,

11, 10, 9, 8, 7, 7, 6, 5, 5,

4, 4, 3, 3,

2, 2, 2, 2,

1, 1, 1, 1,

1, 1};

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

По теме:

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