Как устроен WAV файл. Запись сэмплов
Естественный вопрос - как обойти этот достаточно хлопотливый и вносящий помехи процесс, нельзя ли направить музыку непосредственно в WAV файл? А если это можно, то нельзя ли при этом заменить звук оператора SOUND ( не скажу, что он плохой, но достаточно пищащий) на более благородный звук, звук рояля, например?
Тут имеются 2 пути.
Первый путь. Модифицировать программу Пианола. Сделать так, чтобы звук указанной частоты и длительности не только обрабатывался оператором SOUND F,L но и вызывал запись в WAV файл отрезков сэмплов подходящей длины, взятых из подготовленной для этого библиотеки. Этот процесс проходит достаточно быстро, если вставлять сэмплы большими группами (до 32 кбайт - допустимая длина символьной переменной в Турбо бейсике). Однако мы не сможем смешивать в нём звук, создавая аккорды, если не перейдём от больших групп сэмплов к сэмплам индивидуальным. А это замедляет процесс существенно - в сравнении с просто проигрыванием, в 7 раз (забегая вперёд, скажу, что нашёл и третий, комбинированный путь - http://www.proza.ru/2015/05/09/707 )
Второй путь, более перспективный. Создать отдельную программу по переводу мнемонической нотной записи из текстового файла в WAV файл. Включили её, и пусть она работает, создавая музыку, хоть полчаса. А мы будем заниматься на копме другими делами.
Итак, программа смотрит на ноты и пишет в WAV файл музыку.
И в первом, и во втором случаях нам нужно знать, как устроен WAV файл. Когда я обратился в интернет с этим запросом, то не сразу нашёл, а то, что нашёл, оказалось не совсем точным. Кроме того многие рекомендации по записи WAV файлов рассчитаны на умных программистов, а я кроме Турбо бейсика (простейшая программа DOSовских времён) ничего не знаю. Однако Турбо бейсик знаю хорошо, знаю, как среду способную на многое даже внутри современных продвинутых систем, и вам её рекомендую.
Итак, WAV файл. Состоит из шапки или головной части "head" - от нулевого байта до байта номер 43 включительно (всего 44 байта), и области данных "DATA" - от байта номер 44 и до конца файла.
Местоположение Описание
0..3 (4 байта) содержит символы “RIFF” в ASCII кодировке.
4..7 (4 байта) это = длина файла – 8
8..11 (4 байта) содержит символы “WAVE”
12..15 (4 байта) содержит символы “fmt “
16..19 (4 байта) 16 для формата PCM (не надо знать, что это такое)
20..21 (2 байта) аудио формат, для PCM = 1 (то есть, Линейное квантование). Значения, отличающиеся от 1, обозначают некоторый формат сжатия.
22..23 (2 байта) количество каналов. Моно = 1, Стерео = 2 и т.д.
24..27 (4 байта) частота дискретизации. 44100 Гц или 32000 Гц = 32 сэмпла / мсек
28..31 (4 байта) число байт, переданных за сек воспроизведения
32..33 (2 байта) количество байт для одного сэмпла, включая все каналы.
34..35 (2 байта) количество бит в сэмпле, “глубина” или точность звучания. 8 , 16 бит и т.д.
36..39 (4 байта) содержит символы “data”
40..43 (4 байта) количество байт в области данных = длина файла-44
Свежее замечание. А КАК ОБСТОЯТ ДЕЛА В ВИЗУАЛ БЕЙСИКЕ?
В среде VB6 шапку WAV файла для стереозвука со скоростью раздачи 44100 сэмпл/сек можно записать в файл "T.wav" следующим образом:
'___________________________
Dim Header(10) As Long
fName = "T.wav"
Open fName For Binary As #1
Seek #1, 1
Header(0) = 1179011410
Header(1) = Lof(1) - 36 'NextSise
Header(2) = 1163280727
Header(3) = 544501094
Header(4) = 16
Header(5) = 131073
Header(6) = 44100 '= VEL (скорость раздачи)
Header(7) = 44100 * 4 '= VEL * 4
Header(8) = 1048580
Header(9) = 1635017060
Header(10) = Lof(1) - 44 'DataSize
Put #1, , Header()
Close #1
Обращаю внимание, что оператор Seek #1,1 в Визуал Бейсике укажет на начальный байт файла,
а в Турбо Бейсике на этот же первый байт нужно указать так - Seek #1,0
В настоящее время для работы с WAV файлами создана удобная утилита, объединяющая все наработки в Турбо бейсике.
Утилита называется WAV_манипулятор, о ней рассказывается тут - http://www.proza.ru/2017/10/20/1279
для экспериментов с WAV файлами рекомендую использовать именно её. По ссылке можно найти указание на исходники этой программы в Визуал бейсике.
ВЕРНЁМСЯ К ТУРБО БЕЙСИКУ
Теперь обратимся к практике. Сделаем звук и запишем его в WAV файл.
Эта (см. ниже) программа на Турбо бейсике поможет вам понять как всё работает. Не будем пока писать музыку, но будем создавать звуковые сэмплы для звука заданной частоты.
Вы запускаете эту программу и оператор INPUT выводит на экран знак вопроса, спрашивая Вас, что делать? Хотите выйти из программы (exit) - печатаете "e" и Enter
Прежде всего нужно открыть файл (open) - вводите "or", откроется файл "r.wav"
Можете поэкспериментировать -
"l" покажет длину файла LOF и положение поинтера LOC
"s44" установит поинтер на 44-й байт файла
"p123" считает (от положения поинтера) в виде символов 123 байта в переменную S$
"g" запишет в файл (от положения поинтера) значение переменной S$
Но давайте записывать звук - напечатайте "w" и нажмите Enter. Мы попадаем на участок программы, в котором идет многократное обращение к функции, вычисляющей амплитуду звука для каждого семпла - FNSS2(260.74,1). В программе записывается два звука - звук с частотой 260.74 Гц ( До первой октавы), определяемый, по тембру и особенностям звучания, параметрами таблицы под номером 1, и следом - второй звук, звук чистой синусоиды той же частоты для сравнения (таблица параметров 0). Перед началом записи звуков функция FNSS2, их создающая, устанавливает по умолчанию свои внутренние переменные, это происходит при обращении к ней с F=0.
10 INPUT A$
SELECT CASE MID$(A$,1,1)
CASE "e"
STOP
CASE "o"
A1$=A$ : IF LEN(A1$)=2 THEN A1$=MID$(A1$,2)+".wav"
OPEN "B",#1,A1$ : GOTO 10
CASE "c"
CLOSE #1 : GOTO 10
CASE "l"
PRINT LOF(1),LOC(1) : GOTO 10
CASE "s"
SEEK #1,VAL(MID$(A$,2)) : GOTO 10
CASE "p"
PUT$ #1,MID$(A$,2) : GOTO 10
CASE "g"
GET$ #1,VAL(MID$(A$,2)),S$
PRINT S$,"loc=";LOC(1) : GOTO 10
CASE "h" 'put the head of wav file
GOSUB 100 : GOTO 10
CASE "w" 'make wav sound
SEEK #1,44
VEL=32000 : A=FNSS2(0,0)
FOR I=1 TO 80000 : M%=INT(8000*FNSS2(260.74,1)) : PUT$ #1,MKI$(M%) : NEXT I
FOR I=1 TO 40000 : M%=INT(8000*FNSS2(260.74,0)) : PUT$ #1,MKI$(M%) : NEXT I
GOTO 10
END SELECT
'====================
100 SEEK #1,0
L=LOF(1) : S$="RIFF"+MKL$(L-4)+"WAVEfmt " : S0$=CHR$(0)
S$=S$+CHR$(16)+S0$+S0$+S0$ ' 16 bit/sempl
S$=S$+CHR$(1)+S0$+CHR$(1)+S0$ ' 1 - line format + 1 - mono
S$=S$+MKL$(32000)+MKL$(64000) ' 32 sempl/msek + 64 bite/mcek
S$=S$+CHR$(2)+S0$+CHR$(16)+S0$ ' 2 bite/sempl_all + 16 bit/sempl_mono
S$=S$+"data"+MKL$(L-44) ' date size
WHEAD$=S$ : PUT$ #1,WHEAD$
RETURN
После того, как записывающая звук функция отработала, и на экране появился знак вопроса, нужно ввести "h" и записать этим действием "head" WAV файла. Подпрограмму 100, записывающую "head", Вы видите чуть выше.
MKI$(...) функция, переводящая целое число в его двухбайтовое символьное представление,
MKL$(...) функция, переводящая вещественное число в его 4-х байтовое символьное представление. Эти функции есть в Турбо бейсике.
Затем нужно закрыть файл (close) - введите "c", и выйти из программы - введите "e".
Теперь можете экспериментировать - просматривать файл в редакторе и прослушивать его.
Конечно, интересно знать, как устроена делающая звук функция FNSS2(F,K).
О той, которую сделал я, хочу рассказать, и порассуждать о возможностях компьютерного моделирования звука, в следующей статье. А здесь приведу более простой пример.
DEF FNSS2( F,K)
STATIC FS,KS,A0,A1,A2,MA2,X,DX,D
LOCAL A,Z,ZZ
IF F=0 THEN 'first setup
FS=F : KS=K
FNSS2=0 : EXIT DEF
END IF
IF NOT(F=FS AND K=KS) THEN
'new frequency
FS=F : X=2 : DX=2*FS/VEL
'new parameters
KS=K : A0=1 : A1=0 : A2=0 : MA2=0 : D=1 'параметры по умолчанию
SELECT CASE K
CASE 0 : A0=1 : A1=0 : A2=0 : D=1
CASE 1 : A0=0 : A1=1 : A2=0 : D=0.99
CASE 2 : A0=0 : A1=0 : A2=1 : MA2=1 : D=1
END SELECT
END IF
X=X+DX : IF X>2 THEN ' \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
X=X-2 : A0=A0*D : A1=A1*D : A2=A2*D
END IF ' \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
IF X<1 THEN Z=X*2-1 ELSE Z=(X-1)*2-1 : Z=-Z
ZZ=1-Z*Z : A=A0*(ZZ*3+ZZ*ZZ)/4+A1*ZZ*(1-2.3*Z)
IF MA2=1 THEN A=0 : IF X>1-2*DX AND X<1+2*DX THEN A=A2
IF X<1 THEN A=-A
FNSS2=A
END DEF
'===================
Как видно, начальное обращение к функции устанавливает параметры набора K=0, а следующее обращение с другой частотой или с другим K вызывает переустановку начальных условий и определение шага изменения фазы по формуле:
ШАГ = 2*(Частота звука)/(Частота дискретизации)
Переменная X, изменяемая при каждом обращении на шаг DX, всё время находится в интервале (0,2), что соответствует привычному нахождению фазы в интервале от 0 до 2*Пи.
Первый полупериод колебания программируется по формулам для -1<Z<1 :
Y = (1 - Z^2) *3/4 + (1 - Z^2)^2 *1/4 - очень хорошее представление синусоиды
Y = (1 - Z^2)*(1-2.3*Z) - колебание с отрицательным выбросом в конце полупериода
Второй полупериод колебания программируется по этим же формулам, но проходится в противофазе по Z и знак A меняется на противоположный. Противофазное прохождение второго полупериода позволяет плавно "сшивать" выбросы между собой.
Набор параметров CASE 2, который вы можете видеть внутри функции, предназначен для генерирования короткого импульса. Имея такой импульс удобно изучать функцию ревеберации (я её сделал, но о ней речь пойдёт в дальнейшем).
Если некоторые формы импульса уже созданы случаями CASE, то совсем нетрудно создать и промежуточную форму, задав новое CASE с иными значениями начальных амплитуд A0, A1 и A2. Можно задать и различные D - параметры ослабления для каждой амплитуды. Таким образом, создавая различные случаи CASE, можно математическим путём варьировать как общую форму звукового колебания, так и изменять звук (тембр и громкость) в процессе его звучания.
Алгоритм, как мне представляется, достаточно эффективный. Рисунок над заголовком статьи демонстрирует это, на нём в окне редактора звука Wave Editor показаны последовательно - моделированный звук рояля, и, вслед за ним, просто синусоида.
Давайте сейчас немного пообсуждаем то, как представляются числа в файле формата WAV.
Они следуют друг за другом в байтах, каждый из которых может быть прочитан как символ.
Двухбайтовое число читается так -
если оно положительно, то код второго символа оказывается меньше чем 128. Тогда этот код умножается на 256 и к полученному числу прибавляется код первого символа.
если оно отрицательно, то код второго символа оказывается больше чем 127. Тогда из этого кода вычитается число 256, полученное отрицательное число умножается на 256, и к полученному отрицательному результату прибавляется (со знаком плюс!) код первого символа.
В стереозаписи двухбайтовые числа-амплитуды двух каналов перемежают друг друга.
Вот и всё. Рад, если чем-то помог.
Заинтересованные лица могут обращаться на почту dima-masht@rambler.ru
о программе Пианола тут - http://www.proza.ru/2015/04/02/1593
Свидетельство о публикации №215041101943
Приятно читать мысли и советы увлеченного своим делом человека.
Так и хочется вздохнуть - давненько я не брал в руки "шашки".
Рад видеть Вас!
С уважением,
Сергей
Кандидыч 13.04.2015 12:37 Заявить о нарушении
Я, например, по наивности своей думал, что если сложить три синусоиды (для нот До, Ми и Фа первой октавы), то мы услышим аккорд. Ничего подобного! Эти синусоиды в сумме показывают ярко выраженную низкочастотную периодичность, которая звучит, достаточно красиво, но совсем не так, как хотелось бы.
А вот если складывать не просто синусоиды, а их нечётные степени - 3-и, 5-е, 7-е, то аккорд прорезается, не звук его какой-то хилый, дистрофичный. А хочется ведь полно звучащего звука. И введение случайных фазовых множителей не помогает, и расстройство частот тоже.
Но реальные фортепьянные звуки прекрасно смешиваются, а посмотришь на их осцилляции - звук каждого тона от синусоиды на вид не отличается. Такая интересная загадка - почему так происходит? И что делать?
Дьявол всегда находится в тонкостях.
Или вот - резонатор в виде "эхо" вроде бы легко сделать, но опять же понимаешь, что две смещённые синусоиды ничего кроме синусоиды с новой фазой в таком смешении не дадут. И вряд ли эхо получится. Но проэкспериментировать можно. Создаёшь кольцевой буфер, записываешь туда амплитуды, а потом из этого буфера с запозданием читаешь эхо, смешивая ряд точек, запаздывающих в гармонической пропорции, но чувствуется, что надо ещё и амплитуду составляющих эхо искажать.
В общем, функцию "эхо" уже написал, теперь буду испытывать :)
Дм.
Дмитрий Маштаков 14.04.2015 01:37 Заявить о нарушении
Про синусоиды - понравилось. Столько тонкостей. Никогда не задумывался - как аккорд звучит, какими синусоидами.:)
Удачи и вдохновения, Дмитрий!
С уважением,
Сергей
Кандидыч 14.04.2015 11:48 Заявить о нарушении