PDA

Просмотр полной версии : Таблично-волновой синтез.



ALKO
04.04.2025, 02:08
ORG 32768

sin_table:
DB 8,9,11,12,13,14,14,15,15,14,14,13,12,11,9,8
DB 7,5,4,3,2,1,1,0,0,1,1,2,3,4,5,7

LD HL, sin_table
LD BC, 0xFFFD

; Инициализация микширования
LD A, 7
OUT (C), A
LD BC, 0xBFFD
LD A, 0xFF
OUT (C), A

main_loop:
; Установка громкости
LD BC, 0xFFFD
LD A, 8
OUT (C), A
LD BC, 0xBFFD
LD A, (HL)
OUT (C), A

; Обновление индекса
INC HL
LD A, L
AND 31
OR sin_table & 255
LD L, A
LD A, H
AND sin_table >> 8
LD H, A

; 16-битная задержка (BC = 1000)
LD BC, 1000
delay: DEC BC
LD A, B
OR C
JR NZ, delay

JR main_loop


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

- - - Добавлено - - -

Также интересует, каким макаром можно реализовать плавное затухание амплитуды.
Чтоб значение таблицы при определённой итерации уменьшалось на одно значение.
Своего родна недо-ADSR.

ZXMAK
04.04.2025, 09:54
Что тут не так?
Динамик будто после каждого кванта таблицы становится на своё исходное нулевое состояние. Нужен какой-то сигнал строба, чтоб громкость была на заданном значении до следующей выборки?

Не совсем понятно, что понимается под тем что "динамик становится на своё исходное нулевое состояние"?
https://i.imgur.com/naiZcQm.png

ALKO
04.04.2025, 11:58
Не совсем понятно, что понимается под тем что "динамик становится на своё исходное нулевое состояние"?
https://i.imgur.com/naiZcQm.png

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

- - - Добавлено - - -

и... почему на другом канале горбы фоном? Что это за наводка... нипанятна.

ZXMAK
04.04.2025, 12:30
Что тут не так?
и... почему на другом канале горбы фоном? Что это за наводка... нипанятна.

это в эмуляторе эмулируется взаимопроникновение каналов :D

ALKO
04.04.2025, 14:06
Не совсем понятно, что понимается под тем что "динамик становится на своё исходное нулевое состояние"?
https://i.imgur.com/naiZcQm.png

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

- - - Добавлено - - -

вместо четырëх бит получаем эффективных 2.5 бита.

- - - Добавлено - - -

Мне интересно, а как вот во всякие сэмплтрекеры, в демосцены и тд загоняли сэмплы.
Как из условного wav получить табличку с учëтом нелинейности DAC в AY ?

Sandro
04.04.2025, 16:38
Напоминаю: у человека слух не линейный, а логарифмический. См, откуда пошло, что громкость звука измеряется в децибелах, и как они определяются.

ALKO
04.04.2025, 18:55
Напоминаю: у человека слух не линейный, а логарифмический. См, откуда пошло, что громкость звука измеряется в децибелах, и как они определяются.

Хм.. а мне всегда казалось, что это издержки техпроцесса.

- - - Добавлено - - -

Однако использование AY не подразумевалось, как сэмплер, в отличие от того же чипа аркадного автомата Pac-man. Так что коррекция сэмпла в любом случае нужна.

ZXMAK
04.04.2025, 21:16
Можно просто ближайшее значение выходной амплитуды брать, чтобы получалась минимальная ошибка с требуемой амплитудой - это минимизирует ошибки квантования.
Более точно амплитуду можно получить за счет более высокой частоты сэмплирования.

Вот как сигнал выглядит после наложения полосового фильтра 15 Гц - 20 кГц:
https://i.imgur.com/A5RWrPn.png

Destr
05.04.2025, 10:54
от как сигнал выглядит после наложения полосового фильтра 15 Гц - 20 кГц
Ого! А послушать его можно wav`кой?

ZXMAK
05.04.2025, 12:09
Ого! А послушать его можно wav`кой?

https://transfiles.ru/7oay1

alco.asm - исходный код для генерации
alco.psg - вывод в AY регистры записанный в PSG формате
alco-panABC-raw.flac - результат на частоте сэмплирования AY эмуляции (432 кГц)
alco-panABC-bandpass-15-20000.flac - результат после полосового фильтра 15 Гц - 20 кГц и децимации до сэмплрейта 48 кГц

Sandro
05.04.2025, 23:15
Хм.. а мне всегда казалось, что это издержки техпроцесса.

В AY-8910 совершенно целенаправленно размеры выходых транзисторов растут экспоненциально на каждый из 15 вариантов (на 0 ток выдавать не надо), как раз чтобы соответствовать особенностям человеческого слуха.
log(exp(x)) = x.

Можно убедиться по снимкам кристалла.

ZXMAK
06.04.2025, 01:46
а есть нетлист симулятор для AY?

Sandro
06.04.2025, 09:45
а есть нетлист симулятор для AY?

Настолько лень гуглить?
https://github.com/lvd2/ay-3-8910_reverse_engineered

Destr
07.04.2025, 14:13
ZXMAK, Вот чудесам нет предела, ты напоминаешь мне чат-робота (DeepSeek) - попросил wav послушать - накидано чёрт-те что, ну я прям восхищён...
Ладно, по случаю откомпилю, вдруг да прозвучит что-то...
(и что за ересь PSG такое... я походу лихо отстал в звукогенерации, это попахивает вообще линухами, а не дай боже ещё и крэями...)
Ох обленился, пора снова грызть теорию (думал на пенсию выйду - буду облизывать кванты-бозоны-немножечко лептонов, ан вот тут со звукообразованием затык)

ALKO
07.04.2025, 19:41
ещë мысля таблички хранить в 8 бит, а по мере воспроизведения дробить на полубайты, и посылать туда и туда на два разных канала.
8 бит ЦАП. Правда логарифмическое искажение получится двухкратное... а может есть готовые решения, где wav конаертится в 8 бит на два канала AY ? Или всем влом таким маяться?

- - - Добавлено - - -

Ещë вариант - остаться на 4ëх битах, но использовать по два старших на двух разных каналах, тем самым минимизировав разброс напряжений.

- - - Добавлено - - -

дажы ни знаю, какой варик лутшы...

ZXMAK
09.04.2025, 02:27
попросил wav послушать - накидано чёрт-те что, ну я прям восхищён...
Ладно, по случаю откомпилю, вдруг да прозвучит что-то...
(и что за ересь PSG такое... я походу лихо отстал в звукогенерации, это попахивает вообще линухами, а не дай боже ещё и крэями...)

FLAC - это тот-же WAV но со сжатием без потерь, правда поддерживает только целочисленный формат сэмплов и не все частоты дискретизации, но зато в 2 раза меньше места на диске занимает и использует сжатие без потерь, т.е. что записал, то в точности и прочитается - без искажений сигнала. У меня диск всего 64 GB, из них только 15 свободно, поэтому использую FLAC вместо WAV, где возможно. Звуковые редакторы и плееры его понимают на всех платформах, поэтому проблем быть не должно.

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


PSG

These files are produced by 'x128' Speccy Emulator by James McKey and 'fMSX' emulator (I never saw this one). x128 creates PSG with errors. Therefore more better to use 'Z80 Stealth' Speccy Emulator by Kirill Kolpakov (Mr.Kirill). This emulator contains special features for creating PSG files and also has good debugger which very simplifies PSG creating process. Z80 Stealth Home Page is http://z80.da.ru.

This is all data about PSG found in Internet:

Offset Number of byte Description
+0 3 Identifier 'PSG'
+3 1 Marker “End of Text” (1Ah)
+4 1 Version number
+5 1 Player frequency (for versions 10+)
+6 10 Unknown

Further byte strings follows. Each string begins from byte 0FFh or 0FEh. Byte 0FFh is marker of interrupt beginning. If after it byte exists (in range 0–15) then it is number of AY register, and next byte is value of this register. Further next pair of byte follows, first byte of which is register number, and second byte is register value. And so on, until end of file, or byte 0FFh (next interrupt), or byte 0FEh will be meet. Byte after 0FEh marker is multiplied by four is number of interrupts without outing to AY. For example sequence “FE 01 FF” is equivalent to sequence “FF FF FF FF FF”. If in PSG you will find register number in range 16–252, then you can ignore this and next byte (this is outing to other MSX devices).

RDOSPLAY documentation describes yet another marker is 253 called as 'End Of Music', but this marker is not supported in Emulator.

Also, RDOSPLAY documentation and some my researching of existing PSG-files talk about is more simple header of PSG. This header consists of only first 4 bytes of described header and outing log starts at +4 file offset (instead of +16). So, for correct playing this PSG, need to add zero to expanse header size to 16 bytes length. If you are trying to play such PSG-files without header correcting, you don't hear differences in the most cases.

EPSG

Z80 Stealth emulator creates these files. EPSG additionally contents information about time of outing to AY registers.

Next text from z80s.faq file.

Q: What is it – this EPSG format?
A: It's PSG format improved just a bit to handle output of digitized samples

Here's the description:

Offset Length Value
============================
Header
============================
0 4 'EPSG'
4 1 0x1A marker
5 1 Machine type: 0x00 – ZX Spectrum 128 0x01 – Pentagon
0xFF – Other machines
6 4 Zero for machine type 0x00 and 0x01 or
Number of Z80 tacts between interrupt markers
for other machines
10 6 zeroes
============================
AY(YM) log during 1 frame
============================
16 1 AY(YM) register number
17 1 value written to this register
18 3 T-state
.....
?? 5 0xFFFFFFFFFF – interrupt marker

ZXMAK
09.04.2025, 11:42
Правда логарифмическое искажение получится двухкратное... а может есть готовые решения, где wav конаертится в 8 бит на два канала AY ? Или всем влом таким маяться?

вот набросал MATLAB/Octave скрипт для конвертации wav/flac в бинарник, где 1 байт это 2 сэмпла для левого и правого канала, с учетом логарифмического масштабирования. Скрипт также читает созданный файл и производит обратное преобразование во flac, чтобы послушать что получилось. Результаты не очень - искажения сильнее, чем на 4 битном ЦАП-е. Как улучшить не знаю, может кто другой подскажет...



if exist('OCTAVE_VERSION', 'builtin')
pkg load signal;
end

folderOut = '/dev/shm/OCTAVE';
if ~exist(folderOut, 'dir')
mkdir(folderOut);
end

fileNameIn = 'TEST/test.flac';
fileNameBin = 'test-ay-%d.bin'; % %d will be replaced with sample rate
fileNameOut = 'test-ay.flac';

if 0
% YM2149 volume table (taken from emulator)
x = [0x0000,0x0000,0x00EF,0x01D0,0x0290,0x032A,0x03EE,0 x04D2,...
0x0611,0x0782,0x0912,0x0A36,0x0C31,0x0EB6,0x1130,0 x13A0,...
0x1751,0x1BF5,0x20E2,0x2594,0x2CA1,0x357F,0x3E45,0 x475E,...
0x5502,0x6620,0x7730,0x8844,0xA1D2,0xC102,0xE0A2,0 xFFFF];
else
% AY8910 volume table (taken from emulator)
x = [0x0000,0x0340,0x04C0,0x06F2,...
0x0A44,0x0F13,0x1510,0x227E,...
0x289F,0x414E,0x5B21,0x7258,...
0x905E,0xB550,0xD7A0,0xFFFF];
end
y = 0:(length(x)-1);
x = double(x);
y = double(y);
% normalize to 0..1 range
x = x / max(x);
y = y / max(y);
% remove duplicated values
[x, unique_idx] = unique(x);
y = y(unique_idx);

% read wave
[signal, Fs] = audioread(fileNameIn);

% get first 5 seconds
signal = signal(1:(Fs*5), :);

if 0
% change sample rate from Fs to Fs2
Fs2 = 24000;
signal = resample(signal, Fs2, Fs);
Fs = Fs2;
end

% split L and R channels
signal_l = double(signal(:,1)');
signal_r = double(signal(:,2)');

% gain normalization
peak = max(max(abs(signal_l)), max(abs(signal_r)));
signal_l = signal_l / peak;
signal_r = signal_r / peak;

if min(signal_l) < -1 || max(signal_l) > 1 || min(signal_r) < -1 || max(signal_r) > 1
fprintf('warn stage w0: min-l: %g, max-l: %g, min-r: %g, max-r: %g\n', min(signal_l), max(signal_l), min(signal_r), max(signal_r));
end

% scale to 0..+1
signal_l = (1+signal_l) / 2;
signal_r = (1+signal_r) / 2;

if min(signal_l) < 0 || max(signal_l) > 1 || min(signal_r) < 0 || max(signal_r) > 1
fprintf('warn stage w1: min-l: %g, max-l: %g, min-r: %g, max-r: %g\n', min(signal_l), max(signal_l), min(signal_r), max(signal_r));
end

% scale from linear to AY
signal_l = spline(x, y, signal_l);
signal_r = spline(x, y, signal_r);

if min(signal_l) < 0 || max(signal_l) > 1 || min(signal_r) < 0 || max(signal_r) > 1
fprintf('warn stage w2: min-l: %g, max-l: %g, min-r: %g, max-r: %g\n', min(signal_l), max(signal_l), min(signal_r), max(signal_r));
signal_l = min(1, max(0, signal_l));
signal_r = min(1, max(0, signal_r));
end

signal_l = round(signal_l * 15);
signal_r = round(signal_r * 15);

if min(signal_l) < 0 || max(signal_l) > 15 || min(signal_r) < 0 || max(signal_r) > 15
fprintf('warn stage w3: min-l: %g, max-l: %g, min-r: %g, max-r: %g\n', min(signal_l), max(signal_l), min(signal_r), max(signal_r));
end

% combine L and R channels into byte
signal_8 = bitshift(signal_r, 4) + signal_l;

if min(signal_8) < 0 || max(signal_8) > 255
fprintf('warn stage w4: min: %g, max: %g\n', min(signal_8), max(signal_8));
end

% write result
fid = fopen(fullfile(folderOut, sprintf(fileNameBin, Fs)), 'wb');
fwrite(fid, signal_8, 'uint8');
fclose(fid);

%-----------------------------------------------------------------------
% read and convert to wav

% read back
fid = fopen(fullfile(folderOut, sprintf(fileNameBin, Fs)), 'rb');
signal_8 = fread(fid, 'uint8');
fclose(fid);

% restore L and R channels from byte
signal_l = bitand(signal_8, 15);
signal_r = bitshift(signal_8, -4);

signal_l = signal_l / 15;
signal_r = signal_r / 15;

if min(signal_l) < 0 || max(signal_l) > 1 || min(signal_r) < 0 || max(signal_r) > 1
fprintf('warn stage r0: min-l: %g, max-l: %g, min-r: %g, max-r: %g\n', min(signal_l), max(signal_l), min(signal_r), max(signal_r));
end

% scale back from AY to linear
signal_l = spline(y, x, signal_l);
signal_r = spline(y, x, signal_r);

if min(signal_l) < 0 || max(signal_l) > 1 || min(signal_r) < 0 || max(signal_r) > 1
fprintf('warn stage r1: min-l: %g, max-l: %g, min-r: %g, max-r: %g\n', min(signal_l), max(signal_l), min(signal_r), max(signal_r));
signal_l = min(1, max(0, signal_l));
signal_r = min(1, max(0, signal_r));
end

% scale to -1..+1
signal_l = signal_l*2 - 1;
signal_r = signal_r*2 - 1;

if min(signal_l) < -1 || max(signal_l) > 1 || min(signal_r) < -1 || max(signal_r) > 1
fprintf('warn stage r2: min-l: %g, max-l: %g, min-r: %g, max-r: %g\n', min(signal_l), max(signal_l), min(signal_r), max(signal_r));
end

signal = [signal_l signal_r];

if Fs > 48000
signal = resample(signal, 48000, Fs);
Fs = 48000;
end

% write result
audiowrite(fullfile(folderOut, fileNameOut), signal, Fs, 'BitsPerSample', 16);


- - - Добавлено - - -

хм, если заменить табличку громкости на вариант от AY8910 (с 16 уровнями), то результат заметно лучше. Видимо результат зависит от линейности используемой кривой.
Update: обновил скрипт, исправил ошибку.

Думаю если поиграть с табличкой уровней, то можно еще лучше результат получить, но нужно пробовать как на самом AY будет звучать.


Вот пример что получилось: https://transfiles.ru/asd3e
FLAC - это то как это по идее должно звучать на AY, а bin - это бинарный файл, где младшие 4 бита - это AY громкость для левого канала, старшие 4 бита- это AY громкость для правого канала.

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