No description
| README.md | ||
Digital Delay (Line6 DL4) — восстановленный алгоритм
Источник
- Прошивка: 4×4 Delay, версия 1.03
- Процессор: Motorola DSP56300
- Ассемблерный дамп: адреса 0x0000e8–0x000110, 0x001d67–0x001d70, 0x000053–0x000060, 0x00247d–0x00247e, 0x0019c9, 0x00247c
- Примечание: Восстановлен только эффект Digital Delay (Analog Delay, Tube Echo и другие не включены).
Структура алгоритма
graph LR
A[Вход] --> B[Программная задержка<br>7×MAC];
B --> C[Кольцевой буфер];
C --> D[Обратная связь<br>0xA4];
D --> B;
C --> E[Смешивание Wet/Dry];
E --> F[Выход];
Константы
| Константа | Значение | Назначение |
|---|---|---|
FEEDBACK_COEF |
0xA4 (164) |
Коэффициент обратной связи (64%). Адреса: 0x0000f4, 0x0000fe |
DELAY_PARAM |
0x7D (125) |
Параметр программной задержки. Адрес: 0x001d67 |
BUFFER_SIZE |
4096 | Размер линии задержки (макс. ~85 мс при 48 кГц) |
Полный код на C (восстановленный)
#include <stdint.h>
#define FEEDBACK_COEF 0xA4
#define DELAY_PARAM 0x7D
#define BUFFER_SIZE 4096
typedef struct {
int16_t buffer[BUFFER_SIZE];
uint16_t write_ptr;
uint16_t read_ptr;
int16_t feedback_gain;
int16_t wet_gain;
int16_t dry_gain;
} DigitalDelay;
static inline int16_t saturate(int32_t v) {
if (v > 32767) return 32767;
if (v < -32768) return -32768;
return (int16_t)v;
}
/* Программная задержка: 7 × mac -y1 (0x001d67–0x001d70) */
static int16_t software_delay(int16_t input, int16_t coef) {
int32_t acc = 0;
for (int i = 0; i < 7; i++) {
acc += (int32_t)input * coef;
acc >>= 1;
}
return saturate(acc >> 8);
}
/* Цикл обработки (0x000053–0x000060) */
static int16_t delay_loop(int16_t input, DigitalDelay *delay, int32_t *a, int32_t *b) {
*a += (int32_t)input * delay->feedback_gain; // macr +x0
*a += input; // add x0,a
*a += *b; // addr b,a
*a -= *b; // subr b,a
*a += (int32_t)input * delay->feedback_gain; // mpy +x0
return saturate(*a >> 8);
}
/* Основной Digital Delay (0x0000e8–0x000110) */
int16_t process_digital_delay(int16_t input, DigitalDelay *delay, int32_t *a, int32_t *b) {
int16_t delayed = delay->buffer[delay->read_ptr];
// Обратная связь (0xA4)
int32_t feedback = (int32_t)delayed * delay->feedback_gain;
feedback >>= 8;
// Запись в буфер (0x0000fb)
int16_t new_sample = saturate(input + feedback);
delay->buffer[delay->write_ptr] = new_sample;
// Wet/Dry (0x000101, 0x00010b)
int32_t wet = (int32_t)delayed * delay->wet_gain >> 8;
int32_t dry = (int32_t)input * delay->dry_gain >> 8;
int16_t output = saturate(wet + dry);
// Обновление указателей
delay->read_ptr = (delay->read_ptr + 1) % BUFFER_SIZE;
delay->write_ptr = (delay->write_ptr + 1) % BUFFER_SIZE;
return output;
}
/* Таймер (0x00247d–0x00247e) */
void timer_init(uint16_t prescaler, uint16_t control) {
// *(volatile uint16_t*)TPLR = prescaler;
// *(volatile uint16_t*)RSMB1 = control;
}
/* Вызов из прошивки (0x0019c9, 0x00247c) */
int16_t call_delay(int16_t input, DigitalDelay *delay, int32_t *a, int32_t *b) {
int16_t tmp = software_delay(input, DELAY_PARAM); // 0x0019c9
return process_digital_delay(tmp, delay, a, b); // 0x00247c
}
/* Инициализация */
void init_digital_delay(DigitalDelay *delay, uint8_t feedback, uint8_t mix) {
for (int i = 0; i < BUFFER_SIZE; i++) delay->buffer[i] = 0;
delay->write_ptr = 0;
delay->read_ptr = 0;
delay->feedback_gain = (int16_t)(feedback - 128); // 0xA4 → 36
delay->wet_gain = mix;
delay->dry_gain = (uint8_t)(255 - mix);
}
Таблица соответствий ассемблер → C
| Адрес | Ассемблер | C-код |
|---|---|---|
0x0000e8 |
mac -x1, n0, y... |
*a += input * feedback_gain |
0x0000f4, 0x0000fe |
addr a,b #0xa4 |
feedback_gain = 0xA4 |
0x0000fb |
addr a,b |
buffer[write_ptr] = new_sample |
0x0000fc |
sbc x,b |
delayed = buffer[read_ptr] |
0x000101 |
mac +y1 |
wet = delayed * wet_gain |
0x00010b |
add y0,a |
output = wet + dry |
0x001d69–0x001d6f |
7×mac -y1 |
цикл for (i=0;i<7;i++) |
0x000053–0x000060 |
macr, add, addr, subr, mpy |
delay_loop() |
0x00247d |
movep TPLR |
timer_init(prescaler, ...) |
0x0019c9 |
jsclr FUN... |
software_delay() |
0x00247c |
jsr FUN... |
process_digital_delay() |
Как это работает
- По прерыванию таймера вызывается функция
call_delay(input, ...). - Сначала выполняется программная задержка из 7 MAC-операций. Она создает микро-смещение и стабилизирует работу алгоритма.
- Затем запускается основной алгоритм:
- Из кольцевого буфера (линии задержки) читается сэмпл по указателю
read_ptr. - К нему применяется обратная связь с коэффициентом
feedback_gain(164/256 ≈ 64%), и результат прибавляется к входному сэмплу. - Полученная сумма записывается обратно в буфер по указателю
write_ptr. - Параллельно задержанный сэмпл умножается на
wet_gain, а входной — наdry_gain. После они складываются, формируя выходной сигнал. - Указатели
read_ptrиwrite_ptrинкрементируются, реализуя кольцевой буфер.
- Из кольцевого буфера (линии задержки) читается сэмпл по указателю
- Выходной сэмпл отправляется на ЦАП.
Весь цикл выполняется за фиксированное число тактов, что гарантирует обработку в реальном времени без пропусков.
Параметры и их влияние
- Feedback (
0xA4): Чем выше значение, тем больше повторений эха.0xA4= 64% — классическое значение для этого эффекта. - Wet/Dry (mix): Баланс между обработанным и сухим сигналом. Значение 128/128 = 50% является стандартным.
- Размер буфера (4096): Определяет максимальную возможную задержку (≈85 мс при 48 кГц).
- Частота дискретизации: Задается таймером (обычно 48 кГц или 44.1 кГц).