PDA

Просмотр полной версии : BEEP (freq, ms) для бипера, AY и даже Windows



Oleg N. Cher
25.09.2015, 23:37
Здравия всем.

Вот, подталкиваемый jerri, я решил таки стряхнуть пыль с болдердаша. В связи с чем возник такой вопрос: в игре используются простые звуки на бипер, притом не в фоне, а занимающие время процессора. Сходу нарисовалось вот такое решение эпохи Hisoft Pascal:

static void BEEPER (unsigned int a, unsigned int b) {
__asm
PUSH IX
LD IX,#4
ADD IX,SP
LD E,0(IX)
LD D,1(IX)
LD L,2(IX)
LD H,3(IX)
CALL #0x3B5
POP IX
__endasm;
}

void Sound_Beep (unsigned int freq, unsigned int ms) {
/*
DE = freq * time ; freq (Hz), time (sec) = ms/1000
HL = 437500/freq-30.125
*/
BEEPER(freq*ms/1000, 437500/freq-30);
}В лоб, быстро, однако работает. Но тянет за собой длинное умножение, длинное деление, от чего хотелось бы избавиться. Есть соображения?

Также было бы полезно увидеть реализацию BEEP (freq, ms) для AY и даже для Windows (или SDL). Сам я ничего лучше не придумал, чем генерировать в памяти wav'ку и потом воспроизводить.

shurik-ua
26.09.2015, 00:35
и даже для Windows

https://msdn.microsoft.com/en-us/library/windows/desktop/ms679277%28v=vs.85%29.aspx

Oleg N. Cher
26.09.2015, 02:19
Переделать в beeper(tone, ms), и использовать табличку.tone - как я понимаю, это не частота в герцах, а высота звука, подобная ZX-бейсиковскому BEEP? Не, боюсь, мне такое решение не подойдёт. Смотрите как выглядит генерация типичного звука:


unsigned p = 0;

while (1) {
unsigned m = p, k;

m = ++ p;
for (k = 0; m; m >>= 1)
if (m & 1)
k ++;
snd (30 + (k << 4), 30);
}Т.е. здесь нужна именно частота. В герцах. В тон такое даже спьяну не конвертирую. :)

shurik-ua, начнём с того, что ф-ция винапи Beep пикает именно на встроенный динамик, а на некоторые компы его сейчас даже не устанавливают. До такого решения я и сам бы додумался. Если бы оно меня устраивало...

jerri
26.09.2015, 10:35
Есть еще вариант использовать процедуру Стива Тернера
вешается на прерывания занимает 1/7 фрейма
при этом можно получать довольно сложные звуки

результат работы можно увидеть в играх
RanaRama, Quazatron/Magnetron

вот здесь лежит плеер звуков (https://www.dropbox.com/s/zon8n9r7kep8g2g/sound.a80?dl=0)

вот здесь лежит редактор звуков (https://www.dropbox.com/s/bb4xvtv4257wfzd/sfx_test.sna?dl=0)

вызов производится по номеру заранее созданного звука.

Oleg N. Cher
26.09.2015, 12:10
Хороший кандидат на библиотеку в ZXDev, но использовать в порте я его, пожалуй, не буду - чтобы звук был максимально похожий на оригинал.

Oleg N. Cher
26.09.2015, 16:11
Тэкс, похоже, задача выглядит слегка не так, как написано в заголовке темы. Тут я чуть поковырялся, и теперь не уверен, что частота должна поступать на вход в герцах. Кто хочет мне помочь со звуками для игры болдердаш? (jerri, между прочим, если твой интерес самый большой, то заканчивай трёп и подключайся). Нужно переписать для ZX такой код с ТурбоСи:

void cdecl sndoff (void)
{{
outportb (0x61, inportb (0x61) & 0xFC);
}}

static unsigned get_timer (void)
{{
unsigned z;

outportb (0x43, 0);
z = inportb (0x40);
return z = z | (inportb (0x40) << 8);
}}

void snd (unsigned fr, unsigned ln)
{{
unsigned timing = get_timer ();
long LN = ln;

LN *= fr *= 6;
outportb (0x43, 0xB6);
outportb (0x42, ((unsigned char *)&fr)[0]);
outportb (0x42, ((unsigned char *)&fr)[1]);
outportb (0x61, inportb (0x61) | 0x3);
while (LN > 0) {
unsigned time2 = get_timer ();

LN -= (timing - time2);
timing = time2;
}
sndoff ();
}}

jerri
26.09.2015, 17:39
Если хочешь коллекцию звуков для игры вот тебе еще
https://shiru.untergrund.net/software.shtml#zxspectrum

---------- Post added at 18:39 ---------- Previous post was at 18:32 ----------

мой интерес уже исполнен, я увидел что хотел.
у меня к конверсии игр подход абсолютно другой.

Andrew771
26.09.2015, 20:10
Наиболее простой способ получения звука определенной длительности и высоты - обратиться к подпрограмме ПЗУ, ответственной за выполнение оператора Бейсика BEEP. Мы уже упоминали о ней в шестой главе, но тем не менее напомним, что располагается она по адресу 949 и требует определения регистровых пар HL и DE. Например:
LD DE,440
LD HL,964
CALL 949
RET
Таким способом можно получить звук практически любой высоты и продолжительности - ограничения Бейсика здесь отсутствуют. Однако при этом отсутствуют и удобства, предоставляемые интерпретатором. Чтобы написать даже очень коротенькую музыкальную фразу, придется немало попотеть, рассчитывая значения задаваемых параметров. А рассчитываются они так. В регистровую пару DE заносится число, определяемое как fxt, где f - частота, измеряемая в герцах, а t - время в секундах (при извлечении звука ЛЯ первой октавы, который имеет частоту 440 Гц, длительностью в 1 секунду получится 440x1=440). Пара HL на входе должна содержать число, равное 437500/f-30.125 (если выполнить указанные вычисления, то получим величину 964). Таким образом, приведенная выше программа делает то же самое, что и оператор Бейсика
BEEP 1,9
Из книги "Как написать игру на ассемблере".

Я бы через таблицу сделал, благо нот и длительностей не так много.

Alex Rider
27.09.2015, 18:18
Если во время игры BASIC жив, можно попробовать выполнить прямо опертор BEEP по адресу #03f8. Требует параметров на стеке калькулятора, положить можно через #2d3b.

---------- Post added at 18:18 ---------- Previous post was at 18:08 ----------


BEEP (freq, ms) для AY
Могу поделиться идеями, но нужны взодные данные:
1. BEEP нужен блокирующий или нет? Если нет, то есть ли IM2, можно ли подцепиться в обработчик и устроить ли точность в 1/50 секунды?
2. Можно ли таблицы нот положить в ОЗУ? Если нет, то устроит ли такое оганичение: на 48К + AY или при запуске из 48К-Бейсика будет играть ахинею (можно сделать проверку и не играть совсем).

Sergey
27.09.2015, 21:19
в игре используются простые звуки на бипер, притом не в фоне, а занимающие время процессора.

Процедура из книги "Как написать игру на ассемблере", стр.278.
Так что, АБСОЛЮТНО, рабочая.
RET не используется, т.к. последняя в п/п процедура 1016 сама заканчивается RET, а на стэке у нас адрес возврата.

Semitone - номер полутона, len - длительность в сотых секунды.


void beep(char semitone, char len) __naked
{ semitone, len;
__asm
pop af
pop bc
push bc
push af
ld a,b
ld b,#0
push bc
call 11560
ld a,#100
call 11560
rst 40
.db 5, 56
pop bc
ld a,c
and a
jp m,1$
call 11560
jp 1016

1$: neg
call 11560
rst 40
.db 27,56
jp 1016
__endasm;
}

Oleg N. Cher
27.09.2015, 23:45
Sergey, очень странный код. А что делает ex (sp),hl после pop af : pop bc? Как по мне, она просто портит значение на стеке, которое располагается до аргументов и адреса возврата. Вы уверены, что так можно? Кстати, а зачем первой же командой во флаговый регистр посылается младший байт адреса возврата? Я слышал краем уха, что так можно, но боялся включить какие-то не те флаги. Только не смейтесь. :)

---------- Post added at 23:45 ---------- Previous post was at 23:35 ----------

Я к тому, что вот: сделали ex (sp),hl - положили значение hl в стек, а тут прерывание приходит и его портит. Потом мы его восстанавливаем уже некорректным... Я согласен, такое бывает редко. Но метко. Криво как-то написано. Нет? Я что-то не так понял?

Oleg N. Cher
28.09.2015, 02:32
Alex Rider, да, благодарю за предложенную помощь, я ограничусь подпрограммой ПЗУ #3B5, в задаче просто хотелось бы избежать расчётов - умножений, делений (они занимают много тактов), но, видимо, не получится. Таблицу тонов не вижу смысла делать - на вход snd подаются абсолютно произвольные значения частоты, а вовсе не частоты нот. К тому же частота на вход snd подаётся не в герцах. Герцы перед подачей в порт #42 спикера ПЦ подвергаются длинному делению: OUT(#42, 1193181 / Hz), а в snd значение fr множится на 6 и пихается в порт. Кстати, в процедуре, которую я привёл выше, есть ошибка, угадайте, какая. :)

Пока же мои продвижки в этом деле таковы:

PB = порт спикера 42H на IBM PC
Hz = частота звука в Герцах
fr = частота, подаваемая в функцию snd() Bolder Dash
HL = частота, подаваемая на вход подпрограммы ПЗУ #3B5

Расчёт для ПЦ
----------------

PB = 1193181 / Hz
PB = fr * 6
1193181 / Hz = fr * 6
fr * 6 * Hz = 1193181
fr * Hz = 198863.5
Hz = 198863.5 / fr

Расчёт для ZX
----------------

HL = 437500 / Hz - 30.125
HL + 30.125 = 437500 / Hz
Hz * (HL + 30.125) = 437500
Hz = 437500 / (HL + 30.125)

Вывод
-------

437500 / (HL + 30.125) = 198863.5 / fr
437500 * fr = 198863.5 * (HL + 30.125)
HL + 30.125 = (437500 * fr) / 198863.5
HL = (437500 * fr) / 198863.5 - 30.125

Осталось решить ещё вопрос с длительностью, её тоже нужно рассчитывать через умножение, как минимум. Проверьте, пожалуйста, расчёты, если не лень. :)

jerri
28.09.2015, 09:02
Я к тому, что вот: сделали ex (sp),hl - положили значение hl в стек, а тут прерывание приходит и его портит. Потом мы его восстанавливаем уже некорректным... Я согласен, такое бывает редко. Но метко. Криво как-то написано. Нет? Я что-то не так понял?



void beep(char semitone, char len) __naked
{ semitone, len;
__asm
pop af
pop bc
; ex (sp),hl
push bc
push af
push ix
ld a,b
ld b,#0
push bc
; ld a,l
call 11560
ld a,#100
call 11560
rst 40
.db 5, 56
pop bc
ld a,c
and a
jp m,1$
call 11560
jp 1016
1$: neg
call 11560
rst 40
.db 27,56
call 1016
pop ix
ret
__endasm;
}

выделенные ЖЫРНЫМ строки имеют вначале символ ;
что означает комментарий

Sergey
28.09.2015, 12:59
выделенные ЖЫРНЫМ строки имеют вначале символ ;
что означает комментарий
Извини, Jerri, - это я позже изменил. Но дело не в том, что было непраильно, - правильно, только под HiTech C.
EX (SP),HL на 2 такта быстрее POP/PUSH.
В данном случае хранить аргументы бессмысленно.
Да и сама команда не нужна: SDCC смежные однобайтные аргументы пакует в слово: 2 аргумента - 1 слово. Хайтек передаёт однобайтный аргумент только словом: 2 аргумента - 2 слова,



---------- Post added at 13:59 ---------- Previous post was at 13:51 ----------

Йо-майо! Если в игре будут использоваться несколько заданных звуков, избежать вычислений ЛЕГКО!
Посчитайте на калькуляторе константы! Далее условная компиляция. Проффит!

Sergey
28.09.2015, 23:05
Обновил пост с процедурой. Работа под SDCC протестирована.

Sergey, очень странный код.
Код был заточен под интерфейс Hitech C.


А что делает ex (sp),hl после pop af : pop bc? Как по мне, она просто портит значение на стеке, которое располагается до аргументов и адреса возврата.
обменивает содержимое HL и ячейки по указателю в SP. На 2 такта быстрее (19), чем брать значение через POP, а потом восстанавливать указатель стэка через PUSH (10+11).


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



Я к тому, что вот: сделали ex (sp),hl - положили значение hl в стек, а тут прерывание приходит и его портит.
Нам это значение здесь не нужно, тем более, что оно случайно. - нам нужен был аргумент со стэка, - мы его получили.


Я согласен, такое бывает редко. Но метко.
Приходя, прерывание сохранит адрес возврата в следующей ячейке а не в этой. Сначала уменьшается значение SP, а только потом записывается содержимое PC. LD -(SP),PC

shurik-ua
28.09.2015, 23:13
Посчитайте на калькуляторе константы! Далее условная компиляция. Проффит!
тоже об этом подумал - ведь все тона какие издаёт программа известны на этапе компиляции - поэтому компилятор и должен чтото там умножать и делить.
Оставьте уже просто вызов бейсиковой подпрограммы и всё.

Oleg N. Cher
29.09.2015, 00:09
Гм, господа :) Если бы всё было так просто - я бы и не спрашивал у вас. Ну да ладно. Просто звуки делаются целой серией вызовов snd(), т.е. нужно просчитать не просто два числа, а целую функцию вывести на основе таблиц, притом даже две - одну для частоты, другую для длительности - у #3B5 с этим тоже дела обстоят непросто. Но в общем, думаю, справлюсь.

По тому, что Sergey сказал про Hitech C, добавлю: я разочарован. Здесь его превозносили как идеальный по коду компилятор. Ну да, конечно. SDCC оптимизирует довольно важную вещь - однобайтовые параметры сливает в слово. А Hitech, получается, это не делает. Ай-яй-яй.

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

fn1(2); fn2(2);

ld hl,#2
push hl
call _fn1
call _fn2
pop afВажно. Не буду утверждать, что такое поведение вообще типично для SDCC, но оно ему иногда свойственно. SDCC может полагаться на эти значения повторно. Если не верите, давайте уточним у Филиппа Краузе. Но я точно помню, что по граблям прыгал.

Sergey
29.09.2015, 00:26
По тому, что Sergey сказал про Hitech C, добавлю: я разочарован. Здесь его превозносили как идеальный по коду компилятор. Ну да, конечно. SDCC оптимизирует довольно важную вещь - однобайтовые параметры сливает в слово. А Hitech, получается, это не делает. Ай-яй-яй.
Я бы так не расстраивался ;) Выковыривание аргументов со стека по сравнению с выполнением самой подпрограммы по времени ничтожно. Так что такая передача аргументов на скорострельности процедур не сказывается. Кстати, так делают ВСЕ Си-компиляторы для CP/M или ZX.
Хайтек, не идеален, - но это единственный нативный ANSI-совместимый компилятор. При этом он генерит код существенно плотнее, чем SDCC 3.5.

Barmaley_m
29.09.2015, 00:44
А можно более детально прояснить условия задачи?

В каком виде задается высота тона? В виде частоты (Гц) или номера полутона (дробного, в общем случае)?

Преобразование номера полутона в частоту:

f = 440*2^(n/12)

где n - номер полутона, 0 соответствует ля первой октавы (440Гц).

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

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

Oleg N. Cher
29.09.2015, 19:00
Высота звука задаётся в виде значения, подаваемого на порт спикера 42H (на IBM PC-железе), перед запихиванием в порт умножаемого на 6. Точнее я вам не скажу. :)


void snd (unsigned fr, unsigned ln)
{{
unsigned timing = get_timer ();
long LN = ln;

LN *= fr *= 6; // вот тут частота умножается на 6
// а длительность, заметьте, умножается на частоту, умноженную на 6
outportb (0x43, 0xB6);
outportb (0x42, ((unsigned char *)&fr)[0]);
outportb (0x42, ((unsigned char *)&fr)[1]);Частоту я почти расколол, формула для первого звука, которая переводит частоту fr в значение HL для процедуры #3B5 выглядит очень просто: (fr+1)*35

С длительностью всё гораздо сложнее. Не пойму зачем Мутель варьирует длительность звука в зависимости от его частоты (умножает на fr*6). Впрочем, может так устроена генерация звука в БК-0010, с которого был перенесён болдердаш? Вот тут я уступлю своим принципам и сделаю на Спеке длительность звуков другую. Упрощу. Впрочем, на слух это не очень заметно, даже, я бы сказал, вообще незаметно.

Eagle
29.09.2015, 21:25
С длительностью всё гораздо сложнее. Не пойму зачем Мутель варьирует длительность звука в зависимости от его частоты (умножает на fr*6).
Наверное потому, что чем больше частота, тем короче период волны, а счётчик длительности считает эти периоды, что весьма топорно. Сам делал еще во времена программирования под орион программу, которая считает не количество периодов, а количество единиц времени (всё было выверено по тактам), как результат длительность звучания от частоты звука перестала зависеть. Ежели исходники откопаю, надо?

Destr
30.09.2015, 07:59
Ежели исходники откопаю, надо?
Надо!

Eagle
03.10.2015, 20:15
Что делают команды КР580ВМ80А забыл напрочь.
Кое-что откопалось с кассет :

BEEP:MVI A,0FH
STA FREQU
LXI H,250H
SHLD DLITB
MVI A,1FH
LBEEP1:INR A
CPI 70H
STA FREQU
PUSH PSW
CALL BPBEEP
POP PSW
JNZ LBEEP1
RET
BPBEEP:LHLD DLITB
LDA FREQU
MOV B,A
MOV C,A
MVI D,0
MVI E,0FFH
LBEEP2:MOV C,B
EI
MVI A,7
STA PORT
CALL PBEEP
CMP D
RZ
MOV C,B
DI
MVI A,0
STA PORT
CALL PBEEP
CMP D
JNZ LBEEP2
RET
PBEEP:DCR C
MOV A,C
CMP E
RZ
DCX H
MOV A,L
ORA H
JNZ PBEEP
RET
DLITB:DW 0
FREQU:NOP

Oleg N. Cher
07.10.2015, 00:32
Ну что, господа, есть смелые помочь мне с вычислением длительности звука для ZX? :)

В аттаче реализация звука для PC и ZX. Частоту я (кажется) расколол (хотя не помешало бы проверить), а вот длительность явно указал наобум.

Если никто не захочет заморачиваться - придётся оставить как есть. Но тут понадобится знание низкого уровня ПЦ и желание поковыряться.

Eagle
07.10.2015, 00:49
Ну что, господа, есть смелые помочь мне с вычислением длительности звука для ZX?
Такты посчитать да перемножить на циклы и частоту процессора? К моей программке применимо, только EI и STA PORT заменить на OUT.

Oleg N. Cher
07.10.2015, 01:03
Eagle, я просто не понял пока как вычислить длину звука (в ms) для ПЦ. А перевести в ms для Спека я уже бы как-то осилил (наверно). С этим и прошу помощи.

Видишь, jerri, в основном затыки вот такого плана как этот. Из-за них проект порой откладывается на года. Хотя ещё не написано ни строчки кода на Обероне, камне преткновения. ;)

Oleg N. Cher
07.10.2015, 02:23
Найди десять отличий! ;) Оцениваем заставку болдердаша со звуком, не забывая сравнивать с оригиналом (http://goldies.ru/games/1329). ;) Для смены палитры в ПЦ-версии жмём F1 и F2, в ZX - соответственно просто 1 и 2.

P.S. Люблю заставки портировать. ;)

jerri
07.10.2015, 09:20
Видишь, jerri, в основном затыки вот такого плана как этот. Из-за них проект порой откладывается на года. Хотя ещё не написано ни строчки кода на Обероне, камне преткновения. ;)

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

Oleg N. Cher
07.10.2015, 22:03
Всё, всё, убедил - ты гораздо круче меня. :)

Oleg N. Cher
15.10.2015, 00:40
Сообщаю, что успешно расколол частоту, формула работает и вычисляет правильно. Удалось обойтись без делений и даже без умножений. Интересно, что для одного звука значение я подобрал на слух и ошибся всего на единичку :)

Но затык с длительностью. Вычитал (http://alex-justasu.narod.ru/211.html), что на ПЦ из канала 0 (порт 40H) можно получить значение "тик таймера". Здесь же пишут, что он тикает 1193180 тактов в секунду. На практике у меня получается в два раза быстрее. Вот эта задержка должна работать 10 секунд, отрабатывает за 5. Ау, есть знатоки таймерных тонкостей ПЦ? Тут ведь люди эмули пишут или как? ;)

static unsigned get_timer (void)
{{
unsigned z;

outportb (0x43, 0);
z = inportb (0x40);
return z = z | (inportb (0x40) << 8);
}}

void snd (unsigned fr, unsigned ln)
{{
unsigned timing; long LN=1193180*10; /* Делаем 10 сек, отрабатывает за 5 */

timing = get_timer ();
while (LN > 0) {
unsigned time2 = get_timer ();
LN -= (timing - time2);
timing = time2;
}
}}P.S. Скорость таймера в игре не меняется, она стандартная - 18.2 раза в секунду (точное значение - 1193180/65536 раз в секунду).

Oleg N. Cher
13.10.2020, 04:45
Есть ли формула пересчёта высоты тона из команды BEEP в частоту в герцах? Хочу воспроизводить вот этой процедурой:

#include <windows.h>

// описание заголовка файла WAV
struct WAVHEADER
{
char sigRIFF[4]; // должно быть равно "RIFF"
DWORD sizeRIFFch; // размер чанка RIFF
char sigWAVE[4]; // должно быть равно "WAVE"
char sigFMT[4]; // должно быть равно "fmt "
DWORD sizeFMTch; // размер чанка FMT
WORD wFormatTag; // категория формата, для PCM = 1
WORD wChannels; // кол-во каналов: 1-моно 2-стерео
DWORD dwSamplesPerSec;// кол-во сэмплов в сек.
DWORD dwAvgBytesPerSec;// среднее число байт в сек
WORD wBlockAlign; // выравнивание данных в дата-чанке
WORD wBitPerSample; // бит в сэмпле
char sigDATA[4]; // должно быть равно "data"
DWORD sizeDATAch; // размер data-чанка
};


void Beep(DWORD dwFrequency, DWORD dwMilliSeconds)
{
// частота дискретизации = 44100 Гц
// кол-во бит на сэмпл = 8
// если требуемая частота > 44100 или равна нулю, то выходим
if(!dwFrequency || dwFrequency>44100)
return;

// длина дорожки в байтах
DWORD numSamples = 44100 / dwFrequency;

// выделяем память под дорожку
DWORD size = sizeof(WAVHEADER) + numSamples;
void *buff = new char[size];

if(buff) {

// заполняем WAV-header
WAVHEADER *head = (WAVHEADER*)buff;
strcpy(head->sigRIFF, "RIFF");
strcpy(head->sigWAVE, "WAVE");
head->sizeRIFFch = size - 8;
strcpy(head->sigFMT, "fmt ");
head->sizeFMTch = 16;
head->wFormatTag = 1;
head->wChannels = 1; // моно
head->dwSamplesPerSec = 44100;
head->dwAvgBytesPerSec = 44100;
head->wBlockAlign = 1;
head->wBitPerSample = 8;
strcpy(head->sigDATA, "data");
head->sizeDATAch = size;

// заполняем дорожку периодом синуса
BYTE *samples = (BYTE*)(head+1);
for(DWORD i=0; i<numSamples; i++)
samples[i] = BYTE(255*sin(6.28*double(i)/double(numSamples)));

// проигрываем звук
PlaySound((LPCWSTR)buff, 0, SND_ASYNC|SND_LOOP|SND_MEMORY);

// ждём заданное количество миллисекунд
Sleep(dwMilliSeconds);

// останавливаем звук
PlaySound(0, 0, SND_ASYNC);

delete buff;
}
}
Кто может сказать: правильной ли частоты звуки генерирует эта процедура? Мне кажется, она фальшивит. Как точно это проверить в домашних условиях?

P.S. Beep из WinAPI не годится - её поддержка есть не на всех версиях винды: в старых виндах она воспроизводит на бипер, в новых на звуковуху, на 64-битной висте/XP она ничего не делает.

Barmaley_m
13.10.2020, 22:34
Привет.

Есть ли формула пересчёта высоты тона из команды BEEP в частоту в герцах?
насчёт команды BEEP точно не знаю. Что-то помню о том, что высота тона для BEEP задаётся в полутонах относительно базы. А раз так - то вот формула равномерной темперации (https://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D0%B2%D0%BD%D0%BE%D0%BC%D0%B5%D1%80%D 0%BD%D0%BE_%D1%82%D0%B5%D0%BC%D0%BF%D0%B5%D1%80%D0 %B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B 9_%D1%81%D1%82%D1%80%D0%BE%D0%B9) из теории:

f = 440*2^(n/12)

где f - частота в герцах;
n - номер ноты в полутонах относительно "Ля" первой октавы, которая имеет точно заданную частоту 440Гц. Номер этой ноты n=0. Для "До" второй октавы будет n=3 и т.д. Для более низких нот n отрицательно.

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

1) Частота задаётся целым числом. Для нот это не годится, дробной частью перенебрегать нельзя, иначе будет фальшивить. Также при вычислениях не очень хорошо отрабатываются случаи округления дробных чисел. Опять-таки приводит к фальши.

2) Используется 8 бит разрешение, что приводит к шуму квантования

3) Ошибка: отсчёты сигнала для 8-битного файла являются беззнаковыми числами. Из формулы, где синус, выходят и отрицательные. В результате вместо синусоидального получается сигнал страшной формы, с огромными гармоническими искажениями.

Дальше не проверял, могу дать пару советов:

1) Очевидно, функция PlaySound воспроизводит wav-файл. Приведённая процедура создаёт в памяти образ wav-файла с сигналом. Альтернативный вариант - создать wav-файл с требуемым звуком в звуковом редакторе (Audacity и т.п.), загрузить в память и воспроизводить с помощью PlaySound.

2) Функция PlaySound декларирует возможность зацикливания звука. Но не исключено, что зацикливание производится неидеально, со вставлением между циклами посторонних данных или обрезанием цикла до его фактического завершения. Дело в том, что PlaySound рассчитана на воспроизведение достаточно длинных файлов (1 секунда и более), где щелчки при зацикливании некритичны. В данной же процедуре генерируется только один или небольшое число периодов сигнала; любые неточности при зацикливании могут сильно исказить частоту и внести другие искажения.

Вот формула для генерации синусоидального сигнала заданной частоты:
y = sin(2*pi*f*t)

где t - время, в секундах; f - частота в герцах; pi~=3.1415926... .

Если частота дискретизации, к примеру, равна 44100, то:

t[i] = i/44100.0;

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

Я как-то делал генератор синусоидальных сигналов следующим образом. Рассчитал один период синуса очень низкой частоты (0.1Гц) - 10 секунд. Получилось 10*44100 сэмплов. После этого генерировал куски синусов произвольной частоты следующим образом:


int j=0;
int dj = lround(f*10); // f - frequency
for(i=0; i<n; i++)
{
y[i] = x[j];
j += dj;
if(j >= 10*44100)
j -= 10*44100;
}

где n - желаемая длительность сгенерированного сигнала, в сэмплах; x[] - таблица синуса 0.1Гц, 10 секунд; y[] - сгенерированный сигнал.

В строке с if() производится зацикливание таблицы.

Программа быстрая, но имеет недостаток - разрешение составляет всего 0.1Гц, что может быть недостаточно для музыки. Есть и другие способы, более эффективные по памяти и с более высоким разрешением по частоте.


Как точно это проверить в домашних условиях?
На мобилку скачать приложение - спектральный анализатор или анализаторы высоты тона (обучение пению, настройка музыкальных инструментов и т.п.).

Ещё вариант - любым способом (мобилка, магнитофон) записать звук с компьютера в wav-файл, потом загрузить его в звуковой редактор и там анализировать.

P.S. Beep из WinAPI не годится
Во всех версиях винды, начиная с 95, есть интерфейс "Windows Multimedia Audio" (mmsystem.dll). Нужные тебе функции:
waveOutOpen
waveOutPrepareHeader
waveOutWrite
waveOutPause
waveOutRestart

Это API для воспроизведения данных в реальном времени. Если разберёшься - работает очень хорошо. Рекомендую. Сам всегда пользовался.