Compare commits
1 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
320bc2a7e4 | 1 month ago |
674 changed files with 16702 additions and 249323 deletions
@ -1,149 +0,0 @@
|
||||
> Исторический документ. Актуальная реализация и wire-формат описаны в [ETCP Router](src/routing_layer/etcp_router_doc.md). |
||||
|
||||
# План: эпоха (reset_id) для ETCP Router |
||||
|
||||
Аналог фикса `reset_id` из ETCP (см. `61c572c`), применённый к сервисному слою |
||||
маршрутизации `etcp_router` (src/routing_layer/etcp_router.c). |
||||
|
||||
## 1. Контекст |
||||
|
||||
`etcp_router` — «упрощённый TCP поверх ETCP»: своя seq-нумерация (`tx_seq`/`rx_seq`), |
||||
reorder (`recv_q`), inflight-контроль (`inflight_q`), ретрансмиты, START/RST handshake. |
||||
|
||||
Текущий механизм детекции рестарта пира: |
||||
|
||||
- `SVC_ROUTE_HDR.flags & ROUTER_FLAG_START` — флаг на первом data-пакете сеанса |
||||
(`seq==0 && !start_sent && pl_len>0`, `etcp_router.c:207`). |
||||
- `router_check_peer_restart` (`etcp_router.c:1055`) — если пришёл START на уже |
||||
синхронизированное соединение (`peer_sync_done==1`), то это рестарт пира; |
||||
**дедуп ретрансмитов** — побайтовое сравнение с `last_start_dgram` |
||||
(`etcp_router.c:1063`). Совпало → это ретрансмит (не рестарт). Не совпало → |
||||
`etcp_router_conn_restart` (полный сброс + `sess_id++`). |
||||
- `ROUTER_FLAG_RST` + `router_restart_send` (`etcp_router.c:285`) — сброс только |
||||
TX-стороны; повторные RST гасятся через `send_restart_pending`. |
||||
- `sess_id` (`etcp_router.h:121`) — **локальный счётчик переустановок, на wire |
||||
не пишется**. Используется только в `DEBUG_INFO`. |
||||
|
||||
## 2. Проблема (аналог ETCP seq-коллизии) |
||||
|
||||
Дедуп рестарта строится на **побайтовой идентичности** `last_start_dgram`. Это тот |
||||
же класс хрупкости, что убрали в ETCP: |
||||
|
||||
1. **Байт-идентичный START** может быть и ретрансмитом, и (теоретически) новым |
||||
сеансом — если у нового START совпали `seq==0`, `svc_id`, `timestamp` (16-битные |
||||
часы 0.1ms заворачиваются каждые ~6.5 сек) и payload. Тогда рестарт **не |
||||
детектится**. |
||||
2. Если рестарт не распознан: у пира `tx_seq` ушёл в 0, а у нас `rx_seq` остался |
||||
«высоким». В `router_handle_data_packet` (`etcp_router.c:1104`) ветка |
||||
`(rx_seq - seq) > 0` / `queue_find_data_by_index` считает пакеты новой сессии |
||||
**дубликатами** → молчаливый `c_dup_dropped` + пере-ACK (как `RX dup` в ETCP) → |
||||
потеря сервисных пакетов. |
||||
3. `sess_id` не на wire → пир вообще не может отличить «мы перезапустились» от |
||||
«продолжаем сеанс» иначе как по байтам START. |
||||
|
||||
То есть нужен надёжный маркер **эпохи сеанса** на wire — аналог `reset_id`. |
||||
|
||||
## 3. Решение (аналог reset_id) |
||||
|
||||
Семантика та же, что в ETCP: |
||||
|
||||
- эпоха генерится **один раз** при создании `ETCP_ROUTER_CONN`; |
||||
- меняется **только** при (фатальном) рестарте соединения; |
||||
- при приёме START/RST с **другой** эпохой → рестарт (adopt); |
||||
- **первая** увиденная эпоха принимается без рестарта (свежие при создании); |
||||
- дедуп ретрансмитов START — по эпохе, а не по байтам. |
||||
|
||||
Два варианта источника эпохи: |
||||
|
||||
- **(A) Перенести `sess_id` на wire** — он уже есть как `uint32` счётчик |
||||
(0 при создании, `++` при рестарте). Минимально инвазивно. |
||||
- **(B) Новый `reset_id` (uint64, случайный)** — симметрично ETCP; случайный |
||||
надёжнее счётчика (нет wrap-around и предсказуемости), но чуть больше поле. |
||||
|
||||
Рекомендация — **(A)** как минимальный шаг (поле уже есть), при желании апгрейд |
||||
до (B). |
||||
|
||||
### Разрыв цикла (master/slave) |
||||
|
||||
Как и в ETCP, симметричное «увидел другую эпоху → рестарт» зацикливается при |
||||
одновременном рестарте обеих сторон (A++ → B++ → A видит B → A++ → …). |
||||
|
||||
Ти-брейкер по `node_id` (как в ETCP): |
||||
|
||||
- на приёме START/RST с чужой эпохой: |
||||
- **master** (меньший `node_id`) — держит свою эпоху, шлёт RST пиру; |
||||
- **slave** (больший `node_id`) — принимает эпоху пира и рестартится. |
||||
|
||||
Существующие предохранители (`send_restart_pending`, `router_check_peer_restart` |
||||
на стороне master) сохраняются. |
||||
|
||||
## 4. Wire-формат |
||||
|
||||
`SVC_ROUTE_HDR` (`etcp_router.h:26`) сейчас 33 байта: |
||||
|
||||
``` |
||||
cmd(1) group_id(8) dst(8) src(8) seq(4) svc_id(1) flags(1) timestamp(2) |
||||
``` |
||||
|
||||
Добавить поле эпохи (только START/RST, в обычных data/ACK — 0): |
||||
|
||||
- вариант (A): `uint32_t sess_id` → заголовок 33→37 байт (упакован); |
||||
- вариант (B): `uint8_t reset_id[8]` → 33→41 байт. |
||||
|
||||
Заполняется в `router_build_packet` (START) и `router_send_rst`; читается в |
||||
`router_check_peer_restart` / обработчике RST. |
||||
|
||||
## 5. Шаги реализации |
||||
|
||||
Файлы: `src/routing_layer/etcp_router.h`, `src/routing_layer/etcp_router.c`, |
||||
(опц.) `tests/`. |
||||
|
||||
1. **Поле в заголовке**: добавить `sess_id`/`reset_id` в `struct SVC_ROUTE_HDR`; |
||||
обновить `SVC_ROUTE_HDR_SIZE`. |
||||
2. **Локальное состояние** (`struct ETCP_ROUTER_CONN`): |
||||
- `uint32_t sess_id` (уже есть) — оставить как «моя эпоха»; |
||||
- добавить `uint32_t peer_sess_id` — последняя увиденная эпоха пира. |
||||
- `router_conn_reset` (`etcp_router.c:380`) НЕ сбрасывает `sess_id` |
||||
(комментарий на :379 уже это гарантирует). |
||||
3. **Запись эпохи**: |
||||
- `router_build_packet` (`:193`) — при `ROUTER_FLAG_START` писать `sess_id`; |
||||
- `router_send_rst` (`:251`) — писать `sess_id`. |
||||
4. **Детект рестарта по эпохе** — переписать `router_check_peer_restart` (`:1055`): |
||||
- если `peer_sess_id==0` → первая эпоха, принять без рестарта (сохранить); |
||||
- если `hdr->sess_id == peer_sess_id` → ретрансмит (не рестарт); |
||||
- если отличается → master/slave: slave → `etcp_router_conn_restart` + adopt; |
||||
master → держать свою, при необходимости RST. |
||||
- `last_start_dgram`-байт-дедуп заменить на это сравнение (поле можно удалить). |
||||
5. **RST-обработчик** (`:1151`) — при RST также сверять `sess_id` (дедуп RST по |
||||
эпохе вместо/в дополнение к `send_restart_pending`). |
||||
6. **Инициализация** — `peer_sess_id = 0` в `router_conn_reset`/`etcp_router_conn_get`. |
||||
|
||||
## 6. Тест |
||||
|
||||
`tests/test_etcp_router_*` (существующие `test_etcp_router`, `test_etcp_router_reconnect`, |
||||
`test_etcp_router_unit`) — прогнать после правки заголовка (37/41 байт). |
||||
|
||||
Добавить (по возможности) регрессию, аналогичную `test_etcp_seq_collision`: |
||||
|
||||
- два инстанса, роутер-коннект, отправка N пакетов; |
||||
- принудительный рестарт одной стороны (смена эпохи); |
||||
- assert: пакеты новой эпохи доставлены (не `c_dup_dropped`), обе стороны сходятся |
||||
к одной эпохе. |
||||
|
||||
## 7. Решения (зафиксировано) |
||||
|
||||
1. **Вариант (B)** — `uint64_t reset_id`, случайный (`random_bytes`). Причина: вариант (A) |
||||
`sess_id` стартует с 0 и конфликтует с сентинелом «эпоха не задана». На wire — native |
||||
order (как остальные uint64-поля заголовка, SVC_ROUTE не использует htobe*). |
||||
2. **master/slave по node_id** — нужен: `etcp_router_conn_restart` делает полный |
||||
двунаправленный сброс, без ти-брейкера симметричный рестарт зациклится. master |
||||
(меньший node_id) держит эпоху и шлёт RST; slave принимает и ресетится. |
||||
3. **route_crypto** — совместим: заголовок не шифруется (только payload), подписывается |
||||
только в SIGN-режиме; `SVC_ROUTE_HDR_SIZE = sizeof(...)` авто-адаптируется. START |
||||
идёт через `route_crypto_encode`, RST/ACK/CLOSE — plain header-only. |
||||
|
||||
## 8. Статус |
||||
|
||||
Реализовано и проверено: `etcp_router.h/.c` + `tests/test_etcp_router_unit.c`. |
||||
Сборка `make clean && make` без ошибок; `./check.sh` → 74 passed / 0 failed / 1 skipped; |
||||
`test_etcp_router_unit` 34/34, `test_etcp_router` и `test_etcp_router_reconnect` — PASS. |
||||
|
Before Width: | Height: | Size: 1.3 MiB |
@ -0,0 +1,35 @@
|
||||
План доработок |
||||
[ ] продумать архитектуру репликации таблицы узлов группы |
||||
[ ] добавить узлы хелперы (для проксирования трафика) |
||||
|
||||
|
||||
Категории узлов: |
||||
- узлы без нат [] |
||||
- узлы с nat [restricted] |
||||
|
||||
Узлы за nat сами подключаются к прямым |
||||
|
||||
Узлы за eim nat сами подключаются и сообщают обновление адреса. |
||||
|
||||
Прямые узлы должны сами должны объединиться в сеть следующим образом: |
||||
|
||||
Инит: база содержит RTT по узлам. |
||||
выбираем n=16 узлов с минимальным rtt. запускаем подключение к ним. |
||||
синхронизируем роутинг таблицу с ними (обмен маршрутами должен инициироваться сразу после подключения). |
||||
при потере связи узлы через nat должны найти альтернативы. |
||||
|
||||
в таблице обновляется |
||||
|
||||
как обмен завершен |
||||
|
||||
Нужно собирать и обновлять информацию о том |
||||
- к каким публичным нодам подключены nat узлы. |
||||
|
||||
- метрика доступности узла - как давно не менялся ip:port - собираем с клиентов. вместе с рейтигном (каждый клиент по узлу фолрмирует свой рейтинг кандидата в суперузлы и отправляет подписанный рейтинг узлу) |
||||
рейтинг включает: |
||||
- число разных часов фигурирует в замерах |
||||
- число разных дат фигурирует в замерах |
||||
- гистограмма rtt: 0-2ms, 2-4ms, 4-6ms, 6-10ms, 10-13ms, 14-18ms, 18-25ms, 25-35ms, 35-50ms, 50-75ms, 75-100ms, 100-130ms, 130-160ms, 160+ms |
||||
- гистограмма потерь: 0-1% 1-2% 2-4% 4-8% >8% (обновляется каждый час) |
||||
|
||||
Сделать подключение к группе |
||||
@ -1,238 +0,0 @@
|
||||
# P2P аудио-звонок пользователю — архитектура |
||||
|
||||
Статус: реализовано. Звонок использует роутер, CM подготавливает оптимальный путь. |
||||
|
||||
## 1. Область |
||||
|
||||
Голос 1:1. Звонок можно инициировать **из CHAT-группы или из p2p-чата (DM)** — модуль |
||||
не привязан к типу группы: он оперирует `group_id` + `peer_node_id` (для DM `group_id` — |
||||
DM-группа, маршрутизация через `etcp_router` идентична). Протокол расширяем под видео |
||||
(track-модель), реализуется только аудио. Работает desktop↔desktop и desktop↔android |
||||
(обе стороны компилируют общий стек `etcp_router` / `topo_group` (BGP) / `conn_mgr`). |
||||
|
||||
Полный цикл: проверка онлайна (BGP) → INVITE → звонок → ACCEPT/DECLINE → |
||||
аудиопоток через роутер → HANGUP. CM инициируется вызывающим при старте, вызываемым при принятии вызова. |
||||
|
||||
## 2. Разбиение файлов (отдельный каталог `src/call/`) |
||||
|
||||
| Файл | Язык | Сборки | Назначение | |
||||
|---|---|---|---| |
||||
| `src/call/call_proto.h` | C | везде | wire-формат, subcmd, svc_id `ETCP_RT_ID_CALL 0x34` | |
||||
| `src/call/call.h` / `call.c` | C | везде (autotools + GUI) | сигналинг, реестр сессий, таймеры, watchdog (20с по приёму), релей медиа с backpressure, счётчики | |
||||
| `src/call/call_audio.h/c` | C | только GUI-сборки | Opus encode/decode, фрейминг, pull-обвязка с тонами; регистрация через `call_audio_ops` | |
||||
| `src/call/call_tones.h/c` | C | только GUI-сборки | генератор тонов (glitch/ended), синус | |
||||
| `src/call/call_jitter.h/cpp` | C++ | только GUI-сборки | SoundTouch джиттер-буфер, C API | |
||||
|
||||
Autotools-сборка демона остаётся C-only (`.cpp` не компилируется); сигналинг `call.c` |
||||
присутствует и в демоне, аудиопроцессинг — только там, где есть GUI. |
||||
|
||||
Диагностика: категория `call` (`DEBUG_CATEGORY_CALL`); транспортная доставка — `etcp_route`. |
||||
|
||||
## 3. Протокол (svc_id 0x34, первый байт payload = subcmd) |
||||
|
||||
Формат приёма (как media_delivery, одноуровневый): `entry->dgram[0]=svc_id`, |
||||
`entry->dgram[1]=subcmd`, `entry->dgram[2..]=payload`. |
||||
|
||||
``` |
||||
CALL_INVITE 0x01 {call_id:8, group_id:8, tracks:[{type:AUDIO, codec:OPUS, sr:4, ch:1, bitrate:4}]} |
||||
CALL_RINGING 0x02 {call_id:8} |
||||
CALL_ACCEPT 0x03 {call_id:8, agreed_tracks} |
||||
CALL_DECLINE 0x04 {call_id:8, reason:1} |
||||
CALL_BUSY 0x05 {call_id:8} |
||||
CALL_CANCEL 0x06 {call_id:8} |
||||
CALL_HANGUP 0x07 {call_id:8, reason:1} |
||||
CALL_MEDIA 0x10 {call_id:8, track_id:1, seq:2, ts_ms:4, payload} |
||||
``` |
||||
|
||||
- `call_id` — случайный uint64, генерит вызывающий; идентифицирует сессию на обеих сторонах. |
||||
- `group_id` — CHAT-группа или DM-группа (общая маршрутизация). |
||||
- Сигналинг: `etcp_route_send(..., ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT)`. |
||||
- Медиа: без подписи (уже внутри шифрованного канала etcp_router). |
||||
- `CALL_MEDIA` — общий контейнер; `track_id` оставляет место видео (`type=VIDEO`). |
||||
- Аудио: Opus 48kHz mono, 20ms (960 сэмплов), ~24–32 kbps. ~50 кадров/с. |
||||
|
||||
## 4. Машина состояний |
||||
|
||||
``` |
||||
Caller: IDLE → OUTGOING(INVITE) → RINGING(RINGING) → ACTIVE(ACCEPT) → ENDED |
||||
Callee: IDLE → INCOMING(INVITE) → RINGING(sent RINGING) → ACTIVE(accept) → ENDED |
||||
``` |
||||
|
||||
`ENDED` reasons: `LOCAL_HANGUP / REMOTE_HANGUP / DECLINE / BUSY / CANCEL / |
||||
NO_TRAFFIC / RING_TIMEOUT / CONNECT_FAIL`. |
||||
|
||||
Таймауты: |
||||
- **кодограмма INVITE 300мс** — вызывающий шлёт её весь период вызова |
||||
(`OUTGOING` + `RINGING`) как «сердцебиение»: покрывает потерю INVITE/RINGING и |
||||
держит маршрут живым; |
||||
- **watchdog 2с у вызываемого** — нет кодограммы 2с → вызов считается сорванным |
||||
(отмена потерялась), сессия закрывается локально (`CALL_REASON_CANCEL`); |
||||
- ring-timeout 45с (вызывающий, нет ответа → CANCEL); |
||||
- `CONN_EVENT_DOWN/TIMEOUT` CM не завершают звонок: роутер использует доступные пути; |
||||
- **NO_TRAFFIC 20с** — нет входящих медиа-кадров → авто HANGUP. |
||||
|
||||
Завершающие кодограммы (HANGUP/CANCEL/DECLINE) шлются без ретрансмиссий — потеря |
||||
компенсируется watchdog'ом 2с у вызываемого. Запоздалые кодограммы вызова при |
||||
идущем разговоре (`ACTIVE`) игнорируются. |
||||
|
||||
Онлайн-проверка: `topo_node_find_by_id(group, peer) == NULL` → «не в сети» (event). |
||||
|
||||
CM handle инициирует оптимальное подключение и удерживается до завершения звонка. |
||||
Медиа всегда идёт через `etcp_route_send`: роутер сам выбирает лучший путь и fallback. |
||||
Звонок не хранит ETCP_CONN и не завершается при потере отдельного транспорта. |
||||
Очистка записи CM отсоединяет handles и отменяет отложенные UP; handle закрывает владелец. |
||||
Закрытие CM handle не посылает DISCONNECT удалённым владельцам. Механизм NCD не меняется. |
||||
|
||||
## 5. Отправка медиа — backpressure через локальный буфер |
||||
|
||||
Требование: не дропать аудио при переполнении роутера — держим в локальном буфере |
||||
отправки, дренируем при появлении места. |
||||
|
||||
Механика (`etcp_router` уже предоставляет): |
||||
- `etcp_route_send(force=0)` возвращает `-1`, когда `send_q` роутера полон |
||||
(`count >= ROUTER_MAX_SEND_Q_PACKETS`). |
||||
- `etcp_router_send_q_has_room(...)` — есть ли место (порог `ROUTER_MAX_SEND_Q_PACKETS-1`). |
||||
- `etcp_router_on_send_ready(..., &waiter, drain_cb, arg)` — одноразовый waiter: |
||||
колбэк вызывается, когда `send_q->count <= threshold` (освободилось место). |
||||
|
||||
В сессии: |
||||
- `tx_q` — локальная FIFO очередь закодированных кадров (ll_entry с dgram), ёмкостью |
||||
15с (750 кадров). При переполнении — дроп старейшего + счётчик `c_tx_dropped`. |
||||
- Алгоритм (`call_tx_flush`): пока `tx_q` не пуст: |
||||
- если `send_q_has_room` → `queue_data_get` + `etcp_route_send(force=1)` (после проверки места); |
||||
- иначе — если waiter не зарегистрирован → `etcp_router_on_send_ready(... drain_cb)` и выход. |
||||
- `drain_cb(q, arg)` → снова `call_tx_flush(session)` (re-arm при необходимости). |
||||
- Весь код — в uasync-потоке (single-threaded, реентерабельность как в |
||||
`media_delivery.c:stream_send_chunk_cb`). |
||||
|
||||
## 6. Потоки и жизненный цикл |
||||
|
||||
`call_session`, CM handle, очередь TX и таймеры принадлежат uasync-потоку. |
||||
Аудиопоток кодирует PCM и постит `call_send_media`; приём в uasync кладёт Opus в `call_jitter`. |
||||
Декодер, SoundTouch и генератор тонов принадлежат аудиопотоку. |
||||
Кольцо кодированных кадров и снимок статистики защищены мьютексом; статистика не читает SoundTouch из другого потока. |
||||
|
||||
GUI запускает аудиоустройство по CALL_ACCEPTED. При CALL_ENDED начинается финальный тон; |
||||
устройство останавливается перед `call_audio_release`. Явное завершение звонка прекращает |
||||
воспроизведение накопленной речи и включает тон завершения — на обеих платформах. |
||||
Android останавливает гудки до настройки звонкового аудиотракта. После выбора маршрута |
||||
capture/play потоки независимо создают и запускают AudioRecord/AudioTrack; каждый сразу обслуживает своё направление. |
||||
`start()` подтверждает запуск подготовки, ошибки устройств передаются владельцу на main через `onFailure`. |
||||
Ошибка любого направления завершает звонок. Общий `stop()` отменяет подготовку, разблокирует I/O и ждёт оба потока |
||||
до освобождения Android-устройств и native-аудиостека. Отдельные locks защищают подготовку/остановку каждого устройства. |
||||
Замер AEC планируется через 300мс после готовности обоих направлений и не задерживает воспроизведение. |
||||
Маршрутизатор использует один снимок входных/выходных устройств; обновляет его при старте, |
||||
событиях устройств/BT-профиля и явной переоценке. По завершении снимок освобождается. |
||||
INFO-логи Android показывают длительности настройки режима, регистрации callback, BT-профиля, выбора маршрута, |
||||
создания/старта устройств, первого чтения микрофона и первой записи в AudioTrack, а также готовность другого направления. |
||||
Общий `call_audio` логирует время ожидания первого PCM; запись в AudioTrack может содержать начальную тишину. |
||||
|
||||
## 7. Буфер звонка и восстановление |
||||
|
||||
`call_jitter` независим от `radio_jitter`: рация сохраняет свои правила задержки и сброса. |
||||
Буфер звонка хранит до 750 кодированных кадров (15с): 10с перебоя и 5с запаса. |
||||
Целевой запас — `clamp(max-min, 60, max_reserve_ms)` мс по изменению задержки доставки: |
||||
на входящем кадре накапливается разность интервалов приёма и отправки, десять секундных интервалов хранят её экстремумы. |
||||
Отправитель уже передаёт timestamp в CALL_MEDIA; смещение часов пиров исключается, переполнение uint32 учитывается. |
||||
Подготовка местного аудиоустройства и ускоренное воспроизведение очереди не изменяют оценку доставки. |
||||
Минимум/максимум глубины очереди для GUI собираются отдельно в аудиопотоке за те же десять секунд. |
||||
Верхний предел задаётся `call_jitter_max_reserve_ms`: 500–2000 мс, по умолчанию 1000 мс. |
||||
Настройка есть в аудиопараметрах desktop и Android и применяется к следующему звонку. |
||||
Начальное и повторное накопление ждёт этот запас; увеличение запаса не прерывает текущую речь. |
||||
Ускорение зависит от превышения запаса. При отсутствии входящих кадров обычное воспроизведение |
||||
может исчерпать запас: это не жёсткий запрет воспроизведения ниже цели. |
||||
Статистика раз в секунду отображается в desktop-окне и Android-экране звонка (под кнопками): глубина, запас, минимум/максимум, |
||||
темп, RTT, удаления при переполнении и недостачи PCM. Диагностика буфера — категория `call=debug`. |
||||
После восстановления кадры последовательно декодируются; темп плавно растёт до 1.6x |
||||
без изменения высоты голоса: линейный предел роста 1.0x→1.6x за 500мс воспроизведения. |
||||
При уменьшении накопления целевой темп снижается к 1.0x; скорость снижения ограничена тем же пределом. |
||||
Пауза аудиоустройства не сбрасывает буфер. Переполнение удаляет старейший кадр с WARN и счётчиком. |
||||
|
||||
Роутер сохраняет последовательность и неподтверждённые данные при смене пути. |
||||
Для сервиса звонка срок без прогресса ACK увеличен примерно до 20с; прочие сервисы сохраняют прежний срок. |
||||
Перезапуск удалённого процесса/роутерной сессии не является кратковременным перебоем пути и может сбросить данные. |
||||
SoundTouch: `USE_QUICKSEEK=1, SEQUENCE_MS=20, SEEKWINDOW_MS=10, OVERLAP_MS=4`. |
||||
|
||||
## 8. Тоны и диагностика |
||||
|
||||
- **Исходящий звонок — две фазы с разными гудками** (у вызывающего): |
||||
- фаза 1 «пытаемся связаться» (`OUTGOING`: INVITE отправлен/ретранслируется, маршрут |
||||
устанавливается) — гудок «соединение» (короткие бипы); |
||||
- фаза 2 «звоним» (`RINGING`: получен RINGING, у пира идёт вызов) — длинные гудки (ringback). |
||||
Гудки генерируются платформенно-нативно: desktop — цикличные MP3 через `SoundManager` |
||||
(`call_connecting`/`call_ringing`), Android — цикличные MP3 через `CallRingback`/`MediaPlayer`. |
||||
Переключение — по `CHAT_EVT_CALL_RINGING`, |
||||
остановка — по `CALL_ACCEPTED`/`CALL_ENDED`/`CALL_ERROR`/hangup. |
||||
- **Начальное ожидание первого PCM** → тишина без сигнала потери звука. Подготовка удалённого микрофона |
||||
и начальное накопление jitter не считаются перебоем. Существующий таймаут 20с без медиа действует с ACTIVE. |
||||
- **Недобор PCM ≥500мс после начала воспроизведения** → в `call_audio_pull_pcm` воспроизводится |
||||
`CALL_TONE_GLITCH`: `пи-пи (2×150мс, gap 100мс) … тишина до 1.0с … пи(200мс) |
||||
пауза(200мс) пи(200мс)`, 1000 Гц, цикл пока длится сталл. До порога — тишина при нехватке PCM. |
||||
При возобновлении PCM сигнал останавливается; одного входящего пакета без достаточного запаса недостаточно. |
||||
- **Завершение звонка → всегда** `CALL_TONE_ENDED`: 3 нисходящих 700/500/400 Гц по 150 мс. |
||||
- Счётчики: tx/rx кадры, `c_tx_dropped`/`c_rx_dropped`, дропы буфера, сталлы, |
||||
tempo-события, причины завершения. Логи `DEBUG_CATEGORY_CALL` (+стата раз в 1с). |
||||
|
||||
## 9. GUI-события (extend `chat_event.h` + `gui_bridge.h` + android) |
||||
|
||||
``` |
||||
CHAT_EVT_CALL_INCOMING 32 [ch_id_len][ch_id][caller_node_id:8][call_id:8] |
||||
CHAT_EVT_CALL_RINGING 33 [call_id:8] |
||||
CHAT_EVT_CALL_ACCEPTED 34 [call_id:8] |
||||
CHAT_EVT_CALL_ENDED 35 [call_id:8][reason:1] |
||||
CHAT_EVT_CALL_DECLINED 36 [call_id:8][reason:1] |
||||
CHAT_EVT_CALL_STATS 39 [call_id:8][rtt:2][buffer:2][tempo:2][dropped:2][underruns:2][min:2][max:2][reserve:2] |
||||
CHAT_EVT_CALL_ERROR 38 [call_id:8][err:1][text:var] |
||||
CHAT_EVT_CALL_AUDIO_ROUTE 39 (Android: текущий маршрут/наличие гарнитуры) |
||||
``` |
||||
|
||||
## 10. Публичный API (call.h) |
||||
|
||||
``` |
||||
int call_start(ch_id, peer_node_id, out_call_id); // caller (post в uasync) |
||||
void call_accept(call_id); // callee |
||||
void call_decline(call_id); |
||||
void call_hangup(call_id); |
||||
int call_is_online(ch_id, peer_node_id); // BGP-проверка для UI |
||||
// аудио-поток (общий процессинг в C-ядре): |
||||
int call_encode_pcm(call_id, pcm, n, opus_out, cap); // single-writer: audio thread |
||||
int call_send_encoded(call_id, opus, len); // post → uasync → tx_q + flush |
||||
int call_pull_pcm(call_id, pcm_out, max); // single-writer: audio thread (ajb+tones) |
||||
void call_release(call_id); // post → uasync: освободить аудио-объекты |
||||
``` |
||||
|
||||
`call_init / call_destroy` — вызываются из `chat_core_init / chat_core_destroy`. |
||||
|
||||
## 11. Платформенный I/O |
||||
|
||||
- **Desktop:** `tools/chatgui/src/call_audio_engine.cpp` — miniaudio full-duplex |
||||
(capture → `call_encode_pcm`, `call_pull_pcm` → playback) + окно звонка (UI) |
||||
+ события в `gui_bridge_impl.cpp`. |
||||
- **Android:** JNI `nativeCall*` + Kotlin `AudioRecord`/`AudioTrack`. |
||||
**Переключение источника:** `AudioManager` (API31+ `setCommunicationDevice` + |
||||
`availableCommunicationDevices`; fallback `setSpeakerphoneOn` + `startBluetoothSco`), |
||||
детект гарнитуры (`ACTION_HEADSET_PLUG`, BT-профиль, `ACTION_SCO_AUDIO_STATE_UPDATED`), |
||||
кнопка-цикл трубка/громкая/гарнитура, авто-выбор гарнитуры. `nativeCallSetAudioRoute(route)`. |
||||
|
||||
## 12. Сборка |
||||
|
||||
- `src/Makefile.am` + `tests/Makefile.am`: `call.c` (C, без `.cpp`). `src/call/` в include path. |
||||
- `tools/chatgui/libutun/CMakeLists.txt`: +`call_audio.c`, `call_tones.c`, |
||||
`audio_jitter.cpp`, soundtouch sources (AAFilter, FIFOSampleBuffer, FIRFilter, |
||||
InterpolateCubic/Linear/Shannon, RateTransposer, SoundTouch, TDStretch, cpu_detect_x86, |
||||
mmx_optimized, sse_optimized) как C++. `call.c` попадёт через GLOB `src/*.c`. |
||||
- `tools/chatgui-android/libutun_lite/CMakeLists.txt` + `utun_sources.cmake`: |
||||
`LANGUAGES C CXX`, те же файлы. |
||||
- `src/transport_layer/etcp_api.h`: `#define ETCP_RT_ID_CALL 0x34`. |
||||
- `lib/debug_config.h`: `DEBUG_CATEGORY_CALL 31`, `DEBUG_CATEGORY_COUNT` → 32. |
||||
|
||||
## 13. Порядок задач |
||||
|
||||
1. `call_proto.h` + svc_id + `DEBUG_CATEGORY_CALL`. |
||||
2. `call.c` (машина состояний, backpressure-media, watchdog, события, API) + `chat_core` хуки. |
||||
3. `audio_jitter.cpp` (SoundTouch) + `test_audio_jitter.c`. |
||||
4. `call_tones.c` + `call_audio.c` (Opus + фрейминг + тон-обвязка). |
||||
5. Desktop: `call_audio_engine.cpp` + UI + события. |
||||
6. Android: JNI + Kotlin AudioRecord/Track + переключение источника + UI. |
||||
7. Сборка (Makefile.am / CMake обе). |
||||
8. `test_call.c` (два инстанса), интеграция desktop↔desktop и desktop↔android. |
||||
@ -0,0 +1,292 @@
|
||||
# DB Sync Protocol v2 — Техническое задание |
||||
|
||||
## 1. Проблема |
||||
|
||||
chain_hash = SHA256(prev_chain_hash || id || ts || datahash) — хеш-цепочка, где хеш каждой записи зависит от предыдущей. |
||||
|
||||
`db_record_insert` (db_sync.c:341-463) при вставке записи в середину сортированного порядка делает каскадный пересчёт chain_hash всех последующих записей — O(N). При двустороннем обмене PUSH'ами получается O(N²). |
||||
|
||||
## 2. Решение |
||||
|
||||
**Разделить два концерна:** |
||||
- **Сравнение при синхронизации** — перейти на `datahash` (первые 8 байт SHA256(data), позиционно-независимый). Не требует каскада. |
||||
- **Целостность цепочки** — `chain_hash` остаётся в схеме БД, но используется только для однократной стартап-проверки и диагностики. |
||||
|
||||
**Синхронизация:** per-peer позиционная (`synced_pos`). При PUSH каскад не делается. При SEND_DATA батч-вставка + один каскад после батча. |
||||
|
||||
## 3. Модель данных |
||||
|
||||
### 3.1. SI_PEER |
||||
|
||||
```c |
||||
struct SI_PEER { |
||||
uint64_t node_id; |
||||
uint32_t synced_pos; // последняя подтверждённо общая позиция (0-based index) |
||||
uint8_t sync_state; // 0=not_synced, 1=syncing, 2=synced |
||||
}; |
||||
``` |
||||
|
||||
- `synced_pos` = индекс последней записи в глобальном порядке `ORDER BY timestamp, datahash`, для которой chain_hash (а после v2 — datahash) подтверждённо совпадает у обоих пиров. |
||||
- При PUSH-вставке записи на позицию P, для всех peer'ов у которых `synced_pos >= P`: `synced_pos = P - 1`. |
||||
- При SEND_DATA: `synced_pos = from + received_count - 1`. |
||||
|
||||
### 3.2. Схема БД (без изменений) |
||||
|
||||
```sql |
||||
CREATE TABLE "db_sync_<name>_<id>" ( |
||||
timestamp INTEGER NOT NULL, |
||||
datahash INTEGER NOT NULL, |
||||
id INTEGER NOT NULL, |
||||
chain_hash BLOB NOT NULL, |
||||
author INTEGER NOT NULL, |
||||
flags INTEGER NOT NULL DEFAULT 0, |
||||
data BLOB, |
||||
author_signature BLOB, |
||||
delivered_peers INTEGER NOT NULL DEFAULT 0, |
||||
delivery_chain TEXT NOT NULL DEFAULT '', |
||||
PRIMARY KEY (timestamp, datahash) |
||||
); |
||||
``` |
||||
|
||||
## 4. Wire-формат |
||||
|
||||
### 4.1. Сообщения, которые меняются |
||||
|
||||
#### INIT_SYNC |
||||
|
||||
``` |
||||
Было: [type:1][count:4][last_chain_hash:32] = 37 байт |
||||
Стало: [type:1][count:4] = 5 байт |
||||
``` |
||||
|
||||
#### INIT_RESP |
||||
|
||||
``` |
||||
Было: [type:1][tp:4][chain_at_tp:32][sc:1][(pos:4,chain_hash:32)*sc] |
||||
Стало: [type:1][tp:4][dh_at_tp:8] [sc:1][(pos:4,dh:8)*sc] |
||||
|
||||
dh_at_tp — datahash на позиции tp (8 байт вместо 32) |
||||
sparse — datahash вместо chain_hash (8 байт на элемент вместо 32) |
||||
|
||||
При sc=0 (exact match): длина 1+4+8+1 = 14 байт (было 37) |
||||
При sc=16 sparse: длина 14 + 16*12 = 206 байт (было 37 + 16*36 = 613) |
||||
``` |
||||
|
||||
#### SYNC_DONE |
||||
|
||||
``` |
||||
Было: [type:1][count:4][last_chain_hash:32] = 37 байт |
||||
Стало: [type:1][count:4][last_dh:8] = 13 байт |
||||
``` |
||||
|
||||
### 4.2. Сообщения, которые НЕ меняются |
||||
|
||||
- **PUSH**: `[type:1][id:8][ts:8][dh:8][dlen:4][data][sig_len:1][sig]` — без изменений. `prev_ch` НЕ добавляется. |
||||
- **ACK_PUSH**: `[type:1][dh:8][ts:8]` — без изменений. |
||||
- **REFINE**: `[type:1][from:4][to:4][hc:1][(pos:4,dh:8)*hc]` — уже использует datahash, без изменений. |
||||
- **SEND_DATA**: `[type:1][from:4][count:2][(id:8,ts:8,dh:8,dlen:4,data,sig_len:1,sig)*count]` — без изменений. |
||||
- **ERROR**: `[type:1][code:1]` — без изменений. |
||||
|
||||
## 5. Новые/изменённые функции |
||||
|
||||
### 5.1. `db_cascade_from` |
||||
|
||||
```c |
||||
static void db_cascade_from(struct DB_SYNC_INSTANCE* si, uint32_t from_pos); |
||||
``` |
||||
|
||||
Выполняет каскадный пересчёт `chain_hash` для записей с позиции `from_pos` до конца таблицы. |
||||
|
||||
Алгоритм: |
||||
1. BEGIN IMMEDIATE |
||||
2. SELECT chain_hash записи на позиции `from_pos - 1` (или zero если from_pos = 0) как prev_ch |
||||
3. SELECT id, timestamp, datahash от позиции from_pos до конца |
||||
4. Для каждой: chain_hash = SHA256(prev_ch || id || ts || dh), UPDATE в БД, prev_ch = новый chain_hash |
||||
5. COMMIT |
||||
|
||||
Выделяется из текущего кода `db_record_insert` (строки 412-451) в отдельную функцию. |
||||
|
||||
### 5.2. `db_record_insert` — новый параметр |
||||
|
||||
```c |
||||
static int db_record_insert(struct DB_SYNC_INSTANCE* si, |
||||
uint64_t id, uint64_t ts, uint64_t dh, |
||||
const char* json, size_t jlen, |
||||
const uint8_t* sig, size_t sig_len, |
||||
int do_cascade); |
||||
``` |
||||
|
||||
- `do_cascade=1` — после INSERT выполняется cascade (строки 412-451). Используется для локальных вставок. |
||||
- `do_cascade=0` — без cascade. Используется для PUSH и SEND_DATA (каскад делается отдельно). |
||||
|
||||
### 5.3. `db_verify_chain` (стартап-проверка) |
||||
|
||||
```c |
||||
static void db_verify_chain(struct DB_SYNC_INSTANCE* si); |
||||
``` |
||||
|
||||
Вызывается один раз после `db_sync_instance_add` (перед инициацией sync с пирами). |
||||
|
||||
Алгоритм: |
||||
1. Проход записей 0..N-1 в порядке `ORDER BY timestamp, datahash` |
||||
2. Для каждой: chain_hash = SHA256(prev_ch || id || ts || dh) |
||||
3. Сравнить с хранимым в БД |
||||
4. При первом расхождении на позиции P: пересчитать цепочку от P до конца через `db_cascade_from(si, P)`. Завершить. |
||||
|
||||
Сложность: O(N) SHA256, однократно при старте. |
||||
|
||||
### 5.4. `si_find_pos` (новая) |
||||
|
||||
```c |
||||
static uint32_t si_find_pos(struct DB_SYNC_INSTANCE* si, |
||||
uint64_t ts, uint64_t dh); |
||||
``` |
||||
|
||||
Возвращает позицию (0-based index) записи с ключом `(ts, dh)` в глобальном порядке. Используется после вставки PUSH для корректировки `synced_pos`. |
||||
|
||||
### 5.5. `db_datahash_at` — уже существует (стр. 295) |
||||
|
||||
Возвращает `datahash` на заданной позиции. Используется для сравнения в INIT_SYNC/INIT_RESP/SYNC_DONE. |
||||
|
||||
## 6. Обработчики сообщений |
||||
|
||||
### 6.1. PUSH (`db_handle_push`) |
||||
|
||||
``` |
||||
Было: db_record_insert(si, ..., /* cascade встроен */) |
||||
Стало: db_record_insert(si, ..., /* do_cascade= */ 0) |
||||
uint32_t pos = si_find_pos(si, ts, dh); |
||||
for each peer: if peer->synced_pos >= pos: peer->synced_pos = pos - 1; |
||||
``` |
||||
|
||||
### 6.2. INIT_SYNC (`db_handle_init_sync`) |
||||
|
||||
``` |
||||
Изменения: |
||||
- Принимает [count:4] вместо [count:4][chain_hash:32] (проверка len >= 5 вместо 36) |
||||
- Сравнение: datahash вместо chain_hash (8 байт вместо 32) |
||||
- sparse: datahash вместо chain_hash (8 байт на элемент вместо 32) |
||||
- Размер sparse-элемента: 4(pos) + 8(dh) = 12 байт (было 4+32=36) |
||||
- resp буфер: 4096 → достаточно (14 + 16*12 = 206 байт при max sparse) |
||||
``` |
||||
|
||||
### 6.3. INIT_RESP (`db_handle_init_resp`) |
||||
|
||||
``` |
||||
Изменения: |
||||
- Принимает [tp:4][dh:8][sc:1][...] вместо [tp:4][ch:32][sc:1][...] |
||||
- Проверка len >= 13 вместо 37 |
||||
- Сравнение: datahash вместо chain_hash |
||||
- Пустая БД пира: zero-хеш 8 байт вместо 32 |
||||
- Sparse элементы: 12 байт вместо 36 |
||||
- При совпадении: synced_pos = tp (в дополнение к sync_state = 2) |
||||
``` |
||||
|
||||
### 6.4. SEND_DATA (`db_handle_send_data`) |
||||
|
||||
``` |
||||
Стало: |
||||
struct SI_PEER* sp = si_peer_find(si, src); |
||||
uint32_t fix_from = sp ? sp->synced_pos : 0; |
||||
|
||||
for each record: |
||||
db_record_insert(si, ..., /* do_cascade= */ 0); |
||||
|
||||
db_cascade_from(si, fix_from); |
||||
|
||||
sp->synced_pos = from + received_count - 1; |
||||
sp->sync_state = 2; |
||||
``` |
||||
|
||||
Каскад от `fix_from` (позиция, которая была synced ДО этого батча) гарантирует, что все chain_hash после этой точки пересчитаны — независимо от того, какие PUSH'и испортили их между синхронизациями. |
||||
|
||||
### 6.5. SYNC_DONE (`db_handle_sync_done`) |
||||
|
||||
``` |
||||
Изменения: |
||||
- Принимает [count:4][last_dh:8] вместо [count:4][last_chain_hash:32] (len >= 13 вместо 36) |
||||
- Сравнение последнего datahash вместо chain_hash |
||||
``` |
||||
|
||||
## 7. Механика многопировой синхронизации |
||||
|
||||
При наличии нескольких пиров с одинаковым `sync_state` (например, syncing=1) выбирается пир с минимальным `synced_pos` — у него самый старый общий префикс. После завершения его синхронизации выбор повторяется. |
||||
|
||||
Это гарантирует: двигаемся от самого старого несинхронизированного участка, не прыгая. Новые записи (в хвосте) синхронизируются последними. |
||||
|
||||
## 8. Полный сценарий: три участника A, B, C |
||||
|
||||
``` |
||||
Начальное состояние: все пусты. |
||||
|
||||
=== Шаг 1. A создаёт R1 (ts=1000) === |
||||
A: PUSH R1 → B, C. Все вставляют в конец, cascade 0. |
||||
synced_pos = 0 у всех. |
||||
|
||||
=== Шаг 2. B создаёт R2 (ts=2000), C создаёт R3 (ts=1500) === |
||||
B: [R1(1000), R2(2000)], PUSH R2 → A, C. |
||||
A: R2 в конец. C: R2 в конец. |
||||
C: [R1(1000), R2(2000)]? Нет — C создал R3(1500). |
||||
C: [R1(1000), R3(1500)] локально, потом PUSH R2 → R2.ts=2000 → в конец. |
||||
C: [R1(1000), R3(1500), R2(2000)] |
||||
C: PUSH R3 → A, B. |
||||
A: [R1(1000), R2(2000)] + R3(1500) → между R1 и R2: |
||||
A: [R1, R3, R2] ← R3 в середину, cascade ПРОПУЩЕН |
||||
B: [R1, R3, R2] ← аналогично |
||||
|
||||
synced_pos после PUSH R3 на A относительно B: |
||||
A вставил R3 на поз.1 → synced_pos_B: было 1 → стало 0 |
||||
(A считает, что совпадает с B только на позиции 0) |
||||
|
||||
=== Шаг 3. A инициирует sync с B (чемпион: min synced_pos) === |
||||
A→B: INIT_SYNC [count=3] |
||||
B→A: INIT_RESP [tp=min(3,3)-1=2, dh_at_tp=H2, sc=2, sparse:{(1,H3),(0,H1)}] |
||||
A проверяет: A@2=R2→dh=H2✓, A@1=R3→dh=H3✓, A@0=R1→dh=H1✓ |
||||
→ sync complete, synced_pos_B=2, sync_state=2 |
||||
|
||||
=== Шаг 4. Рестарт A === |
||||
db_verify_chain: |
||||
pos 0: R1.ch = SHA256(0||R1) ✓ |
||||
pos 1: R3.ch = SHA256(R1.ch||R3) ✓ (посчитан при PUSH без cascade — корректен) |
||||
pos 2: R2.ch = SHA256(R3.ch||R2) ✗ (STALE: был посчитан как SHA256(R1.ch||R2) до PUSH R3) |
||||
→ расхождение на pos=2 → db_cascade_from(2): пересчёт R2.ch = SHA256(R3.ch||R2) ✓ |
||||
|
||||
=== Шаг 5. A, B, C активно обмениваются === |
||||
Каждый PUSH: вставка без cascade. |
||||
Периодические sync (по min synced_pos): SEND_DATA + один cascade от synced_pos. |
||||
synced_pos растёт. |
||||
``` |
||||
|
||||
## 9. Что НЕ трогать |
||||
|
||||
- `db_sync_insert_signed` — локальная вставка. ts всегда монотонный, запись в конец, `do_cascade=1`, каскад всегда 0 строк. |
||||
- TTL cleanup (`db_sync_instance_ttl_cb`) — удаление старых записей. Нужен cascade после удаления (уже существующая логика, не меняется). |
||||
- ACK_PUSH, ERROR, REFINE — без изменений. |
||||
- Схема БД — без изменений. |
||||
- `etcp_bind`/эпилог — без изменений. |
||||
|
||||
## 10. Сложность операций (итого) |
||||
|
||||
| Операция | Каскад | Сложность | |
||||
|----------|--------|-----------| |
||||
| Локальная вставка | 0 строк (конец) | O(1) | |
||||
| PUSH (приём) | нет | O(1) | |
||||
| SEND_DATA (батч N записей) | 1 каскад после батча | O(N + tail) | |
||||
| Стартап-проверка | 1 раз | O(total) | |
||||
| Sync-протокол (INIT_SYNC/INIT_RESP/REFINE) | нет cascade | O(log N) сообщений | |
||||
|
||||
## 11. Порядок реализации |
||||
|
||||
1. **Выделить `db_cascade_from(si, from_pos)`** из тела `db_record_insert`. |
||||
2. **Добавить параметр `do_cascade`** в `db_record_insert`. При `0` — только INSERT, cascade вызывается отдельно. |
||||
3. **Добавить `synced_pos`** в `struct SI_PEER`. Инициализировать в 0. |
||||
4. **Добавить `si_find_pos(si, ts, dh)`** — поиск позиции записи в глобальном порядке. |
||||
5. **Изменить wire-формат** INIT_SYNC, INIT_RESP, SYNC_DONE (chain_hash → datahash, 32→8 байт). |
||||
6. **Изменить обработчики**: |
||||
- `db_handle_init_sync`: новые размеры, datahash вместо chain_hash |
||||
- `db_handle_init_resp`: новые размеры, datahash вместо chain_hash, обновлять synced_pos |
||||
- `db_handle_send_data`: `do_cascade=0`, `db_cascade_from(fix_from)`, обновить synced_pos |
||||
- `db_handle_push`: `do_cascade=0`, скорректировать все synced_pos |
||||
- `db_handle_sync_done`: обновить synced_pos |
||||
7. **Добавить `db_verify_chain(si)`** — вызвать при старте после `db_sync_instance_add`. |
||||
8. **Многопировая синхронизация**: в `db_sync_peer_check_cb` выбирать пира с min synced_pos вместо первого попавшегося. |
||||
@ -1,222 +0,0 @@
|
||||
# DM: доставка сообщений и файлов |
||||
|
||||
Личные беседы принадлежат `src/dm/`. Общие CHAT-группы используются для знакомства, |
||||
проверки ролей и маршрутизации; группа не становится идентичностью беседы. |
||||
|
||||
## Реализовано |
||||
|
||||
- История и постоянная исходящая очередь сообщений в общей SQLite ядра. |
||||
- Доставка через живой маршрут router, включая BGP-транзит. |
||||
- При отсутствии маршрута — подписанный PUT доступному суперузлу общей группы сторон. |
||||
- Приём после проверки подписи и расшифровки, подтверждение только после commit. |
||||
- Доставка с суперузла после возвращения получателя, независимо от отправителя. |
||||
- Точное удаление промежуточного сообщения и сохранение квитанции для поздних повторов. |
||||
- Подготовка, шифрование, проверка и durable публикация медиа в рабочих потоках. |
||||
- Прямая передача файлов либо custody на отдельном M, доставка после restart и подтверждённое удаление. |
||||
- TTL и квота M; вложения и статусы доставки в Qt и Android. |
||||
- Голосовые Opus и видео MP4 с зашифрованными метаданными; общий ввод и отображение с каналами. |
||||
|
||||
## Идентичность и шифрование |
||||
|
||||
```text |
||||
conv_id = SHA256("utun_dm_v1" || min(node_a,node_b) || max(node_a,node_b))[..63] |
||||
content_key = SHA256(X25519(my_private, target_public) || "utun_dm_content") |
||||
msg_nonce = SHA256(conv_id || author || seq)[..13] |
||||
media_nonce = SHA256("utun_dm_media" || author || media_id || part)[..13] |
||||
``` |
||||
|
||||
Сообщения и медиа используют один content_key target, без ключей отдельных файлов. |
||||
AES-256-CCM: nonce 13 байт, tag 16 байт. Максимальная порция plaintext — 65535 байт; |
||||
порция медиа — 32768 байт. UUID медиа новый для каждой подготовки. Повтор доставки |
||||
использует неизменяемый ciphertext. Повторная подготовка после ошибки требует нового UUID. |
||||
|
||||
Каноническое тело сообщения: |
||||
|
||||
```text |
||||
[conv:8][seq:8][ts:8][author:8][ct_len:1][ct][enc_len:2][ciphertext+tag][author_sig:64] |
||||
``` |
||||
|
||||
Автор подписывает все поля до sig. conv связывает автора с получателем. |
||||
Для `ct=file` перед подписью добавляется descriptor `[UUID:16][plaintext_size:8][SHA256(ciphertext):32]`. |
||||
В зашифрованном теле находится `attachment_info`; пути хранения строятся только из UUID. |
||||
Размер plaintext сообщения ограничен 1024 байтами. seq — положительное int64. |
||||
|
||||
```text |
||||
[kind:1][name_len:2][duration_ms:4][width:2][height:2][UTF8_name:name_len][waveform:100, только voice] |
||||
``` |
||||
|
||||
Поля метаданных многобайтной длины имеют big-endian формат. kind: 1=file, |
||||
2=voice, 3=video. Имя — basename до 255 байт, без разделителей, управляющих символов |
||||
и некорректного UTF-8. FILE имеет нулевые duration/dimensions; VOICE — положительную |
||||
длительность и 100 символов уровней waveform; VIDEO — длительность и размеры. |
||||
Длительность ограничена 24 часами, размеры — 16384. Метаданные проверяются до |
||||
локального commit и до подтверждения получателем; S/M видят только ciphertext. |
||||
|
||||
Медиа ciphertext — последовательность порций с отдельным tag каждой порции. |
||||
Последняя порция короче; пустой файл содержит один tag. Размер plaintext и SHA256 |
||||
ciphertext должны входить в подписанный manifest. Потоковая расшифровка проверяет все |
||||
tags, точный размер и хеш; владелец публикует временный файл только после успеха |
||||
и собственного durable сохранения. |
||||
|
||||
## Владение и данные |
||||
|
||||
- `dm_core`: беседы, история, outbox и подтверждение получателя. |
||||
- `dm_mailbox`: промежуточные сообщения суперузла и квитанции. |
||||
- `dm_crypto`: вывод ключей, nonce, CCM и файловые примитивы без БД/сети. |
||||
- `dm_media`: файлы endpoint, загрузки и отдельная custody M. |
||||
- `dm_mailbox_media`: durable задания S, выбор M и повтор удаления до DELETE_ACK. |
||||
- `file_transfer`: подписанная передача байтов, backpressure и отдельный CM handle каждой операции. |
||||
- `attachment`: формат, валидация и тип метаданных, без сети и БД. |
||||
- `voice_file`: общий Opus-кодек отправки; `attachment_send` готовит файл в worker |
||||
и передаёт результат отдельно в `chat_core` либо `dm_media`. |
||||
|
||||
```text |
||||
dm_conversations(conv_id, peer_node_id, peer_x25519, peer_ed25519, peer_name, |
||||
group_id, last_out_seq, last_in_seq, created_at, last_ts) |
||||
dm_messages(conv_id, dir, seq, ts, author, ct, data, sig) |
||||
dm_outbox(conv_id, seq, body, retry_at) |
||||
dm_pending_messages(recipient, sender, seq, msg, group_id, retry_at) |
||||
dm_mail_receipts(recipient, sender, seq, receipt, group_id) |
||||
``` |
||||
|
||||
Запись исходящего сообщения, увеличение seq и запись неизменяемого body в outbox — |
||||
одна транзакция. При ошибке откатывается всё, сообщение не выходит в сеть. |
||||
Приём, создание/обновление беседы и история — также одна транзакция. |
||||
|
||||
Dedup — точный ключ (conv,dir,seq) с проверкой сохранённой подписи. last_in_seq — |
||||
статистика максимального номера, не курсор подтверждения: seq=99 после seq=100 |
||||
сохраняется. Тело с другой подписью для уже сохранённого seq отклоняется. |
||||
|
||||
У каждой локальной очереди таймер 1с; порция обработки до 32 задач, повтор задачи |
||||
через 5с. retry_at предотвращает голодание хвоста очереди. Перед отправкой проверяется |
||||
backpressure router. Потеря пути сохраняет durable задачу; остановка отменяет таймеры, |
||||
но сохраняет БД. При старте очередь продолжает работу. |
||||
|
||||
## Подтверждения и протокол |
||||
|
||||
Сервис `ETCP_RT_ID_DM=0x34`: MSG=1, RECEIPT=2. |
||||
|
||||
```text |
||||
["DMACK001":8][conv:8][seq:8][author:8][recipient:8][SHA256(message):32][recipient_sig:64] |
||||
``` |
||||
|
||||
Квитанцию создаёт только получатель после commit. Подпись охватывает первые 72 байта. |
||||
Отправитель удаляет outbox только после проверки подписи и точного хеша тела. |
||||
История беседы сохраняется. Подтверждение транспорта router не заменяет квитанцию. |
||||
|
||||
Сервис `ETCP_RT_ID_DM_MAILBOX=0x35`: |
||||
|
||||
```text |
||||
PUT=1 [recipient:8][message] |
||||
DELIVER=2 [message] |
||||
RECEIPT=3 [receipt:136] |
||||
STORED=4 [conv:8][seq:8] |
||||
``` |
||||
|
||||
Все пакеты идут с подписью router; unsigned control отклоняется. |
||||
PUT принимается только от автора, при роли supernode=yes у локального узла |
||||
в общей группе автора и получателя. Роль берётся из подписанного adm_tags; |
||||
node_type=4 — производная сетевой классификации, для авторизации не используется. |
||||
storage=yes само по себе не даёт права хранить сообщения. |
||||
|
||||
STORED подтверждает durable запись суперузлом, outbox до квитанции получателя остаётся |
||||
у автора. Суперузел повторяет DELIVER при появлении живого маршрута. RECEIPT проверяется |
||||
по точному сохранённому телу; запись квитанции и удаление копии — одна транзакция. |
||||
Квитанция пересылается автору. Поздний PUT возвращает её и не возрождает задачу. |
||||
|
||||
dm_mail_receipts содержит только служебные квитанции; доставленное содержимое |
||||
с промежуточного узла удаляется. Ранее использовавшийся dm_mail/PULL/HELLO и |
||||
диапазонные ACK не поддерживаются: это devel, совместимость не сохраняется. |
||||
TTL текстовых сообщений не введён; недельный TTL из pm.txt относится к медиа. |
||||
|
||||
## Доставка медиа по pm.txt |
||||
|
||||
1. Общая передача байтов: явные peer/group/file/size, backpressure и CM handle |
||||
на всю активную операцию. Владение handle сохраняется после DOWN/TIMEOUT. |
||||
Используется `ETCP_RT_ID_FILE_TRANSFER=0x37`. Один callback читает/отправляет |
||||
не более 8 КиБ, продолжение через `call_soon`; inflight ограничен 16 пакетами. |
||||
2. Отдельный owner dm_media: подготовка ciphertext, manifest, отправка/скачивание, |
||||
временные файлы, TTL и лимит хранения. Каталоги dm_media/ и dm_pending/ вне |
||||
группового обхода очистки orphan-файлов; групповые объявления HAVE_BLOCK не используются. |
||||
3. Суперузел сначала durable регистрирует сообщение и media job, затем выбирает M. |
||||
M скачивает ciphertext у A и сообщает S подтверждённое хранение, место и expiry. |
||||
4. Текстовая квитанция и квитанция медиа независимы. Получатель подтверждает медиа |
||||
после проверки, публикации файла и commit. S хранит задачу удаления у M до DELETE_ACK; |
||||
поздний STORED не возрождает завершённую доставку. |
||||
5. TTL медиа `dm_media_ttl_sec` по умолчанию 604800 с; квота `dm_media_storage_mb` |
||||
по умолчанию 1024 МиБ. Настройки на M. Новая custody отклоняется при |
||||
недостатке квоты; уже подтверждённые копии не вытесняются молча раньше срока. |
||||
6. Stop снимает новые задачи и закрывает transfer/CM handles, затем ждёт |
||||
media_async с живыми owner-контекстами. Группы уничтожаются после owners. |
||||
|
||||
Control `ETCP_RT_ID_DM_MEDIA=0x38`: OFFER, STORE, STORED, RECEIPT, DELETE, |
||||
DELETE_ACK, EXPIRED, REJECT; точные форматы заданы в `dm_media.h` и реализациях owners. |
||||
Подпись router обязательна. Media receipt содержит `DMFILE01`, conv, seq, author, |
||||
recipient, UUID и SHA256 полного подписанного сообщения; B подписывает первые 88 байт. |
||||
Отдельные таблицы `dm_files`, `dm_custody`, `dm_custody_done`, `dm_media_jobs`, |
||||
`dm_media_holders` сохраняют состояния и tombstones. Все выданные grants M |
||||
учитываются при удалении; повторный STORE не продлевает TTL. |
||||
|
||||
Qt и Android получают историю через события ядра: 41 — conv + JSON сообщений, |
||||
45 — JSON бесед. Снимки ограничены 200 записями. Отправка вложения вызывает |
||||
`dm_send_file` в uasync; длительная обработка выполняется в `media_async`. |
||||
Android копирует выбранный URI и сохраняет полученный файл через `Dispatchers.IO`; |
||||
Qt сохраняет файл из контекстного меню в отдельном потоке. Выбор «как файл» сохраняет |
||||
оригинал; «как видео» в Qt использует общий FFmpeg worker и метаданные конечного MP4. |
||||
Android записывает MP4 через CameraX; импорт MP4 проходит через платформенный |
||||
MediaMetadataRetriever в IO. Другие контейнеры на Android отправляются как файлы. |
||||
|
||||
Голосовая запись отдаёт worker собственный PCM либо компрессор без копирования |
||||
в GUI/uasync; worker выполняет flush, Opus-кодирование и вычисление waveform. |
||||
Кодирование Qt и Android использует один `voice_file`. Адресат фиксируется до |
||||
записи, выбора URI и подготовки; завершение не читает текущую беседу GUI. |
||||
Подготовленный PM-файл и Android cache удаляются после копирования, ошибки или отмены; |
||||
пользовательский исходник Qt остаётся во владении пользователя. |
||||
|
||||
Событие 46 `[target_len:1][target][job_UUID:16][state:1]` сообщает начало (0), |
||||
готовность (1) и ошибку (2). Target канала — числовой ID, PM — `dm:<conv_id>`. |
||||
Подготовка, доставка и воспроизведение имеют независимые состояния. UI отображает |
||||
только задания текущей беседы. Состояния файла и delivery receipt приходят в JSON |
||||
41; `content_type`, duration, dimensions и waveform преобразуются в общие роли |
||||
сообщения. Канальные скачивания вызываются контроллером только для каналов; |
||||
PM-файлы доставляются автоматически через `dm_media`. |
||||
|
||||
Android использует общие `ConversationComposer`/`ConversationMessages` для обеих |
||||
бесед; Qt — `InputBar` и `MessageDelegate`. Превью Qt создаются в общем пуле |
||||
с двумя workers; декодирование длинного Opus-файла выполняется вне GUI. Оба плеера |
||||
проверяют заголовок, пакеты и end marker; Qt ограничивает PCM буфер 256 МиБ. |
||||
|
||||
## Проверки |
||||
|
||||
`test_dm`: симметрия ключа target, CCM round-trip, пустое тело, пределы длины, |
||||
неверный ключ/tag, разделение nonce; потоковые файлы на границах 32 КиБ, |
||||
неверные author/UUID/hash, обрезание и лишние байты. |
||||
|
||||
`test_dm_e2e`: реальные A–C–B соединения и BGP; нет физического A–B линка. |
||||
Проверяет out-of-order, dedup/conflict, подписи, SQL rollback при приёме и enqueue, |
||||
ACK чужого тела, отказ mailbox без роли, точное удаление и поздний PUT. |
||||
Первый сценарий передаёт 40 offline-сообщений (больше порции pump) и перезапускает источник с pending outbox; второй перезапускает |
||||
суперузел и доставляет первое сообщение при выключенном источнике. |
||||
|
||||
`test_dm_media`: реальные A/B/S/M с раздельными ролями supernode и storage; |
||||
прямая передача, custody, restart S/M при выключенном A, точный файл на B, |
||||
удаление копии и тела S, поздние STORE/STORED, TTL, отказ по квоте, неверная квитанция, |
||||
SQL rollback регистрации, состояния JSON и отмена операции с CM handle. |
||||
Голосовое передаётся напрямую; метаданные видео сохраняются после offline custody |
||||
и restart S/M при выключенном источнике. |
||||
|
||||
`test_dm_worker`: повторное шифрование 8 МиБ не менее 0,5 с и проверка расшифровки; |
||||
work выполняется вне uasync, done в его потоке, heartbeat продолжает работать. |
||||
Destroy отменяет уже posted completion, callback вызывается ровно один раз. |
||||
|
||||
`test_attachment`: строгий формат, повреждения и граничные длины, реальное |
||||
кодирование/декодирование 50 Opus-кадров, длительность и waveform. |
||||
`test_attachment_send`: выбранная беседа после смены текущего target, освобождение |
||||
PCM в worker, остановка до completion без регистрации и удаление отклонённого cache. |
||||
Qt `test_voice_file`: реальные PCM/Opus, повреждённые файлы и отмена async decoder. |
||||
|
||||
Проверка 01.10.2026: чистые сборки Qt и Android APK; полный `check.sh` — |
||||
117 PASS, 0 FAIL, 1 SKIP (auto_socket_dynamic требует root), proxy/burst/load PASS. |
||||
Qt CTest: 7 PASS, 0 FAIL; Android APK собран после `clean`. |
||||
В тесте worker максимальный интервал heartbeat составил 10,3 мс. |
||||
Интерактивная проверка GUI на телефоне и десктопе не выполнялась. |
||||
@ -0,0 +1,10 @@
|
||||
В конфиге явно прописываются сокеты с ip и портом. |
||||
Через эти сокеты идёт весь трафик, случайные порты клиент не использует - только эти сокеты. |
||||
Соответственно, возможен только один линк между двумя пирами по одному каналу. |
||||
|
||||
виртуальные сокеты - плохо т.к. используются ip/port для поиск подключения |
||||
|
||||
|
||||
|
||||
./configure --with-openssl - openssl |
||||
./configure --without-openssl - tinycrypt |
||||
@ -1,178 +0,0 @@
|
||||
# Разбор полного прогона 2026-09-27 |
||||
|
||||
Исходный результат `./check.sh` вне песочницы: 92 passed, 5 failed, 1 skipped (98 тестов). |
||||
Песочница запрещает сетевые сокеты; её результаты не использовались для оценки протокола. |
||||
|
||||
Промежуточный прогон: 96 passed, 1 failed, 1 skipped; оставался `test_chat_join_e2e`. |
||||
После KEY_REGISTER_ACK и исправления невыровненных чтений повторный полный |
||||
`make check -j4`: **97 passed, 0 failed, 1 skipped (98)**. |
||||
Лог: `/tmp/utun-ack-final-check.log`. Исходное падение отдельного root-теста и |
||||
последующая доработка описаны ниже. После исправления autosockets общий набор дал |
||||
97 PASS / 1 SKIP, а `check-proxy`, `check-burst`, `check-load` прошли: |
||||
`/tmp/root-fix-full-check.log`. |
||||
|
||||
`test_etcp_lifecycle`, `test_etcp_link_stress`, `test_conn_mgr_phases` дополнительно |
||||
прошли ASan/UBSan/LeakSanitizer. Инструментированы тесты и изменённые транспортные/ |
||||
маршрутные модули, остальные объекты библиотек взяты из обычной сборки. |
||||
Логи: `/tmp/ncd-late-asan.log`, `/tmp/link-role-asan.log`, `/tmp/cm-phases-asan.log`. |
||||
|
||||
## Исправленные причины |
||||
|
||||
- `test_conn_mgr_phases`: после TIMEOUT фазы DIRECT NCD навсегда подавлял UP, |
||||
хотя conn_mgr сохранял handle для REVERSE. В логе физический ETCP уже UP, |
||||
но conn_mgr продолжал DIRECT_REQ до своего таймаута. TIMEOUT теперь обозначает |
||||
истечение срока первой попытки, CLOSED — окончательное завершение. Удерживаемый |
||||
handle получает поздний UP. Отдельный lifecycle-регрессионный тест воспроизводит |
||||
TIMEOUT → INIT/UP (одно уведомление) → DOWN → UP; до исправления он падал. |
||||
- `test_etcp_link_stress`: ожидал reset всей сессии при коллизии дополнительного |
||||
линка. Проверка заменена на сохранение эпох, отсутствие reset и дублей линков, |
||||
доставку и ACK сообщений через новый линк. Прежний тест забирал сырые фрагменты |
||||
вместо normalizer и мог считать служебные пакеты полезным трафиком. Теперь он |
||||
использует отдельный ETCP service и проверяет каждый байт и порядок сообщений. |
||||
Эта проверка выявила ошибку реализации: при принятии встречного INIT сохранённый |
||||
исходящий линк не менял `is_server`. Роль обновляется при отправке INIT_RESPONSE. |
||||
- `test_bgp_route_exchange`: после разрыва B–C recovery успешно строил C–A, |
||||
нарушая предположение теста о фиксированной цепочке. Теперь A и C принимают |
||||
только ключ B: все прежние проверки withdraw/restore сохранены, обход запрещён |
||||
условиями тестовой сети. Автоматический обход отдельно проверяет `test_topo_recovery`. |
||||
- `test_tcp_io`: адрес 192.0.2.1:81 в окружении устанавливал TCP-соединение, |
||||
поэтому connect timeout закономерно не срабатывал. Теперь используется локальный |
||||
listener с заполненной accept-очередью, проверяется ETIMEDOUT. Перед уничтожением |
||||
uasync тест завершает отложенное освобождение TCP и проверяет баланс таймеров. |
||||
|
||||
Диагностика тестов включается `UTUN_TEST_DEBUG=1`; обычный запуск не включает DEBUG. |
||||
Остаточные DATA после удаления соединения могут быть undecryptable и сами по себе |
||||
не означают ошибку транспорта. |
||||
|
||||
## Исправлено: гонка регистрации приглашения |
||||
|
||||
`test_chat_join_e2e`, happy path A != C, падал из-за отсутствия подтверждения KEY_REGISTER. |
||||
Раньше `chat_invite_build_link()` вызывал `chat_join_register_key()` и сразу возвращал ссылку. |
||||
Успех `etcp_route_send()` означает постановку в очередь. Он не подтверждает обработку |
||||
ключа посредником C. Параллельное подключение J–C может завершиться раньше доставки A–C. |
||||
|
||||
Доказательство: `/tmp/join-e2e-debug.log`, один ключ `90c4b081833450a0`: |
||||
|
||||
- 01:39:02.437851 — A пишет `join-key registered`, фактически ключ только поставлен в отправку. |
||||
- 01:39:02.446527 — C отвергает JOIN_INFO_REQ: ключ ещё неизвестен. |
||||
- 01:39:02.449724 — C сохраняет этот ключ, но J уже получил терминальный ERROR. |
||||
|
||||
Теперь C отвечает KEY_REGISTER_ACK только после сохранения ключа. A сопоставляет |
||||
channel/target/key и выдаёт ссылку после успешного ACK и локального сохранения ключа. |
||||
Отрицательный ACK завершает запрос ошибкой. Чужие, неподписанные, незашифрованные, |
||||
повторные и запоздалые подтверждения не могут завершить другой запрос. |
||||
|
||||
Контракт асинхронных API `chat_join_register_key` / `chat_invite_build_link`: |
||||
NULL означает ошибку запуска без callback; принятый запрос даёт ровно один callback. |
||||
Обычное завершение происходит после возврата API, включая self. Cancel/destroy |
||||
завершают запрос синхронно с CANCELLED. Handle недействителен после callback. |
||||
Для удалённого узла дедлайн 5 секунд; доставку и повтор потерянных пакетов обеспечивает |
||||
надёжный router. Повторная регистрация того же ключа тем же инвайтером обновляет TTL; |
||||
замена инвайтера для существующего ключа запрещена. Отмена может оставить ключ на C |
||||
до истечения TTL, но A не авторизует по нему вход, поскольку локально такой ключ |
||||
сохраняется только при успешном завершении. |
||||
|
||||
GUI получает request_id в событии и игнорирует устаревшие результаты. Новый запрос |
||||
отменяет предыдущее ожидание GUI. Headless связывает ответ с JSON id и отменяет все |
||||
ожидания отключившегося клиента. Ни один из путей не запускает вложенный event loop. |
||||
|
||||
Дополнительные исправления: |
||||
|
||||
- Устранён double-free при ошибке `etcp_route_send`: пакет уже освобождён роутером. |
||||
- Headless прекращает разбор пакета после `quit`; последующая команда не создаёт |
||||
запрос на уже закрытом клиенте. При destroy отменяются cleanup_timer и запросы, |
||||
удаляются клиентские сокеты и освобождается контекст. |
||||
- UBSan обнаружил невыровненное чтение group_id из `d + 1` в chat_sync. Оно и три |
||||
аналогичных диагностических чтения заменены на memcpy. |
||||
- Тест chat_join теперь останавливает вручную запущенный member_sync; |
||||
проверяется баланс выделения/освобождения таймеров. |
||||
|
||||
Проверки: |
||||
|
||||
- `test_chat_join`: 31/31, включая задержанный/отсутствующий ACK, таймаут, |
||||
неверные channel/peer/key/flags/размер/status, дубликат, отказ хранения из-за |
||||
конфликта инвайтера, отмену, shutdown, параллельные ключи и ошибку отправки. |
||||
- `test_chat_join_e2e`: 3/3 (A≠C, A=C, неверный ключ). C принудительно откладывает |
||||
регистрацию на 200 мс; проверяется отсутствие ссылки до обработки регистрации. |
||||
Дополнительно проверяются подавление старого GUI-запроса и реальный headless |
||||
control socket: параллельные ответы, валидность ссылок, quit и команда после него. |
||||
- ASan/UBSan: оба теста проходят. LeakSanitizer и баланс таймеров проверены в |
||||
test_chat_join; E2E использует fork/_exit, поэтому сам по себе не проверяет утечки |
||||
при завершении дочерних процессов. Инструментированы изменённые chat-модули и |
||||
transport/routing, остальные объекты взяты из обычной библиотеки. |
||||
Логи: `/tmp/utun-ack-join-asan.log`, `/tmp/utun-ack-e2e-asan.log`. |
||||
|
||||
## Отдельный root-тест auto_socket_dynamic |
||||
|
||||
Запуск в `sudo unshare --net` с поднятым loopback закончился TIMEOUT через 30 секунд. |
||||
Лог: `/tmp/utun-ack-root-checks.log`. Это не PASS основного набора: в нём тест пропущен. |
||||
|
||||
В конфиге теста включён auto_sockets, но не отключён стандартный фильтр |
||||
`auto_socket_skip_no_default_route=1`. Dummy-интерфейсы теста не имеют default route, |
||||
а `auto_socket.c:reconcile_iface()` исключает такие интерфейсы. Лог показывает |
||||
`no links created`, затем TIMEOUT. Исправленный тест создаёт собственный network |
||||
namespace, поднимает loopback и добавляет default route только клиентским dummy. |
||||
Фильтр остаётся включённым; отдельно проверяется исключение серверного dummy, |
||||
иначе его сокет скрывал бы полный обрыв клиентской сети. |
||||
|
||||
Кроме того, тест сохраняет handle общего дедлайна, traffic_send и traffic_monitor |
||||
в одну переменную timeout_handle; фазовые таймеры вообще не сохраняются. При выходе |
||||
не отменялись все таймеры; зафиксирован остаток 9 таймеров (665/656). В обработчиках |
||||
трафика освобождался entry без queue_dgram_free. Теперь у каждого таймера свой slot, |
||||
waiters отменяются до teardown, payload освобождается, deferred cleanup завершается |
||||
до проверки баланса таймеров, сокетов и `u_get_allocated_count()`. |
||||
|
||||
Новый генератор шлёт 1024-байтовые пакеты независимо в обе стороны, по одному через |
||||
threshold waiter, с бюджетом 64 пакета на tick. Проверяются содержимое, строгий seq, |
||||
ограниченность TX/RX очередей, реальное срабатывание backpressure, остановка |
||||
генераторов при полном обрыве и полное опустошение после восстановления. NCD handle |
||||
удерживает логическую сессию: смена интерфейса не является разрешением терять её данные. |
||||
Остатки после действительно удалённого соединения — отдельный допустимый случай. |
||||
|
||||
Прогон нагрузки обнаружил ошибки, скрытые прежними проверками: |
||||
|
||||
- `tcp_socket_add` не сохранял `netif_index`: монитор принимал interface-bound сокет |
||||
за сокет default route и менял его адрес при событиях другого интерфейса. |
||||
- Удаление TCP модели искало её по имени уже удалённого интерфейса и порту из БД. |
||||
Теперь запись интерфейса хранит имя и прямую ссылку на модель; один путь закрывает |
||||
listener, линки и модель. Портовые записи удаляются с сохранённым именем. |
||||
- `tcp_socket_remove` не закрывал линки, созданные вне NCD. Теперь закрывает все |
||||
связанные линки до освобождения модели; root-тест создаёт такой линк отдельно. |
||||
- Поиск UDP модели не исключал TCP. Добавлена проверка типа сокета. |
||||
- При selective ACK sender мог выдавать новые seq за границей `MAX_INFLIGHT_SIZE` |
||||
относительно cumulative ACK. Теперь генерация seq останавливается на границе, |
||||
cumulative ACK возобновляет очередь даже при отсутствии уже selectively-acked записей. |
||||
Тесты проверяют блокировку, возобновление и uint32 wrap. ACK не может подтвердить |
||||
ещё не назначенный seq; декодирование старшего байта seq/ACK не делает signed shift. |
||||
- Timestamp Q16 умножался как signed int и переполнялся на циклических/отрицательных |
||||
смещениях. Перевод выполняется в uint32. Jitter раньше всегда получал нулевую разницу: |
||||
предыдущий RTT читался после перезаписи; теперь берётся до неё, EWMA использует int64. |
||||
- UBSan выявил невыровненные canary/указатели в `mem.c` и `memory_pool.c`. Метаданные |
||||
читаются/пишутся через memcpy; покрыты размеры блоков 1..17 и повторное использование пула. |
||||
- Повторный общий прогон поймал `test_etcp_connect`: PID-порты пересекались с занятым |
||||
UDP-портом (`EADDRINUSE`). Теперь сервер bind-ится на порт 0, клиент получает фактический |
||||
порт через getsockname; собственного алгоритма поиска свободного порта нет. |
||||
|
||||
Root ASan/UBSan/LSan: PASS, 153335 / 175912 пакетов, reinit=0, все очереди пусты, |
||||
баланс памяти/таймеров/сокетов совпал (`/tmp/root-fix-asan.log`). Инструментированы |
||||
тест, ETCP/session/router/NCD/autosocket/STCP/normalizer, очереди, event loop и аллокаторы; |
||||
прочие объекты архивов обычные. Unit-логи: `/tmp/root-session-asan.log`, |
||||
`/tmp/root-memory-asan.log`. LSan запускается вне sandbox: под ptrace он не работает. |
||||
|
||||
Финальная чистая обычная сборка: `./check.sh` — **97 PASS / 0 FAIL / 1 SKIP**; |
||||
proxy, burst и load — PASS (`/tmp/root-fix-final-check.log`). Отдельный root-тест — |
||||
PASS, 188069 / 191552 пакета, все очереди пусты, reinit=0, баланс ресурсов совпал |
||||
(`/tmp/root-test-final.log`). После добавления drain отложенной очистки |
||||
`test_etcp_connect` повторно прошёл без остаточных таймеров (`/tmp/root-connect-final.log`). |
||||
|
||||
Подробные socket/connection INFO включаются только через `UTUN_TEST_DEBUG=1`. |
||||
`ENETUNREACH` непосредственно при принудительном удалении IP/интерфейса ожидаем до |
||||
обработки netlink; тест требует последующей доставки всех данных. Ошибки выхода |
||||
за receive window недопустимы и после исправления sender gate не наблюдаются. |
||||
|
||||
## Попутная проблема диагностики |
||||
|
||||
`lib/debug_config.c:debug_parse_config()` требует `:`, хотя документация описывает `=`. |
||||
Кроме того, функция всё ещё вычисляет индекс через битовую маску, хотя категории |
||||
теперь являются индексами. Исправление в этот набор не включено; тесты используют |
||||
`debug_set_category_level()` напрямую. |
||||
@ -1,433 +0,0 @@
|
||||
# Linux: диагностика и восстановление аудио |
||||
|
||||
Проверка 2026-10-02: desktop chatgui, miniaudio/PulseAudio, общий голосовой стек, |
||||
звонки, рация и запись сообщений. Исправления обработки PCM, владения устройствами |
||||
и автоматическое восстановление реализованы. Ниже сохранены исходные результаты |
||||
аудита; раздел «Реализация» описывает действующие контракты и проверки. |
||||
|
||||
## Вывод |
||||
|
||||
До исправлений тракт был некорректен при сбоях. Самое существенное: воспроизведение |
||||
звонка зависит от наличия микрофонных кадров в duplex-ring. Его опустошение |
||||
останавливает вызов пользовательского callback, оставляя хвост выходного буфера |
||||
незаполненным. Восстановление jitter-буфера не исправляет остановившееся устройство. |
||||
Отдельного управляющего механизма восстановления устройств не было. |
||||
|
||||
В реализации восстановлены три гарантии: каждый запрос output полностью заполнен; обработка |
||||
одного callback всегда ограничена; остановка/пересоздание устройства выполняются |
||||
вне аудиопотока и не уничтожают сетевую сессию звонка. |
||||
|
||||
## Что установлено по работающим клиентам |
||||
|
||||
В локальном старом логе звонка `132e4a9698f47bdf` с Samsung SM_A525F наблюдались |
||||
паузы callback примерно 0,5–2,6 секунды при зарегистрированной обработке 0–2 мс. |
||||
Входящий буфер desktop достигал 15 секунд, росли потери, включалось ускорение 160%. |
||||
На Samsung захват продолжал давать около 50 кадров/с, playback регистрировал |
||||
underrun и тоны разрыва. RTT порядка 1–4 мс не объясняет эти паузы сам по себе. |
||||
|
||||
Это признаки проблемы обслуживания локального аудио, но ещё не доказательство |
||||
конкретной причины зависания PulseAudio. Старое измерение исключало время |
||||
синхронных DEBUG-вызовов между концом обработки и обновлением timestamp. |
||||
Новая диагностика охватывает весь callback, включая эти логи. |
||||
Работающий GUI не перезапускался, поэтому новый инструмент ещё не записал этот |
||||
сбой на реальном звонке. Лог Samsung сохранён в `/tmp/sm-audio-current.log`. |
||||
|
||||
## Найденные дефекты и ограничения до исправлений |
||||
|
||||
| Место | Поведение и последствия | Необходимая доработка | |
||||
| --- | --- | --- | |
||||
| `lib/miniaudio.h`, `ma_device__handle_duplex_callback_playback()` | При пустом capture-ring `acquire_read` возвращает success с нулём кадров. Выполняется `break`, output остаётся частично незаполненным, call callback не вызывается. PulseAudio затем отправляет весь mapped buffer. | Обеспечить полный output и продолжение playback при отсутствии capture. Нельзя делать commit_read для синтетических кадров. | |
||||
| `ma_device_on_write__pulse()` | Успешная запись с нулём обработанных кадров не уменьшает остаток цикла. Возможно бесконечное вращение с блокированием того же PA mainloop, включая capture. | Немедленно выходить при отсутствии продвижения, возвращать управление mainloop. Логировать состояние и запрос, не пытаться записывать до успеха в этом callback. | |
||||
| `ma_device_on_read__pulse()` | PA hole отбрасывается без подачи соответствующего количества нулевых кадров. Из capture timeline исчезает интервал, duplex-ring может опустеть. | Сохранять длительность hole тишиной либо явно обозначать discontinuity независимому capture-тракту. | |
||||
| `lib/speex_aec.c`, `speex_aec_feed_playback()` | Обрабатывает только остаток одного 960-sample кадра, отбрасывая хвост входного блока. Контракт разрешает произвольный count. | Циклом потреблять весь count, складывать все полные кадры, сохранять остаток. | |
||||
| `CallAudioEngine::setPlaybackDeviceIndex()` | После неудачного пересоздания `m_active` остаётся true при отсутствии устройства; `start` того же звонка возвращает успех. | Разделить желание продолжать звонок и работоспособность устройства. Ошибка запуска переводит устройство в recovering. | |
||||
| `RadioAudioEngine::teardownDevice()` | Если playback init успешен, а start нет, uninit пропускается: он зависит от флага started. Объект удаляется вместе с незавершёнными ресурсами. | Разделить initialized и started; каждый успешный init обязательно завершать uninit. | |
||||
| `MainWindow::~MainWindow()` / `SoundManager::shutdown()` | Общий context освобождается вместе с engine, но звонок и рация здесь явно не останавливаются. Их singleton-объекты могут оставлять живые устройства. `m_context` также не обнуляется. | Сначала остановить все зависимые устройства и дождаться callbacks, затем освобождать общий context. | |
||||
| `AudioRecorder` | Callback выбирает глобальный `g_recorder`, игнорируя `pUserData`. Второй recorder, в том числе тест микрофона, заменяет получателя. Флаги/уровень/вектор waveform используются из GUI и аудиопотока без согласованной синхронизации. | Использовать `pUserData`; передавать уровни атомарно, waveform — безопасным snapshot; буфер записи должен иметь однозначного владельца. | |
||||
|
||||
До исправлений первые два дефекта воспроизводились стендом без аппаратуры: |
||||
duplex оставлял sentinel в output при starvation, а PA-write без продвижения |
||||
требовал внешней остановки. AEC из 1200 + 720 samples сохранял один кадр вместо |
||||
двух. Теперь эти проверки требуют полного output, самостоятельного возврата |
||||
из callback и двух сохранённых AEC-кадров; результаты приведены ниже. |
||||
|
||||
Capture-AEC также обрабатывает не более одного кадра за вызов. В звонке его |
||||
вызывают ровно с 960 samples, поэтому текущий call feed этот случай не нарушает. |
||||
При расширении API нельзя объявлять произвольный count без согласованного размера |
||||
выходного буфера. `speex_aec_reset()` уже существует: новый API для сброса самого |
||||
AEC не нужен, но нужен согласованный сброс всего локального voice-тракта. |
||||
|
||||
## Чистота реализации |
||||
|
||||
Этот раздел фиксирует исходный аудит; изменения владения и ограничение работы |
||||
callback описаны в разделе «Реализация». |
||||
|
||||
Нормальная структура уже есть: GUI владеет устройствами, сервисный слой кодирует |
||||
Opus и обрабатывает jitter, сетевой поток доставляет пакеты. Нет необходимости |
||||
переписывать весь стек или заводить новый backend. |
||||
|
||||
Однако границы владения не завершены: context фактически принадлежит engine |
||||
звуковых уведомлений, а используется независимыми устройствами звонка/рации. |
||||
Флаги active/started/initialized местами смешивают три разных состояния. |
||||
Выбор input применяется к recorder, звонок использует default input, рация — |
||||
default устройства. Output звонка и SoundManager тоже выбираются отдельно. |
||||
Индекс перечисления не является устойчивой идентичностью устройства после hotplug. |
||||
В radio capture fallback `useDefault` фактически повторяет ту же попытку: |
||||
`pDeviceID` и так NULL в обеих ветках. Callbacks рации запускаются раньше |
||||
подготовки capture-буфера и установки VAD; порядок запуска нужно привести к |
||||
«сначала состояние и буферы, затем устройства», чтобы исключить гонки при старте. |
||||
|
||||
Callback не является строго realtime-safe. Capture удерживает `g_mtx` во время |
||||
AEC/AGC/Opus; отправка выделяет память и вызывает `uasync_post`. Playback вызывает |
||||
SoundTouch и код с возможным расширением контейнеров, mutex и синхронными логами. |
||||
Приём пакетов тоже может держать mutex при работе с jitter и логировании. |
||||
Плохие Opus-кадры могут последовательно выбираться из большой очереди в одном |
||||
pull: нужен предел работы на callback. Новая диагностика покажет, где это |
||||
действительно нарушает deadline. Перенос всего DSP в ещё один worker заранее |
||||
увеличит число очередей и точек синхронизации без установленной необходимости. |
||||
|
||||
AEC получает reference только звонка. Звуки отдельного SoundManager в него не |
||||
попадают; delay по умолчанию фиксирован, не вычисляется по фактической задержке |
||||
устройств. Это ограничение качества эхоподавления, а не установленная причина |
||||
текущего зависания. |
||||
|
||||
Jitter умеет повторно накопить резерв после сетевого underrun, но диапазон tempo |
||||
ограничен 1,0–1,6. Ускорение убирает избыток, а замедления для компенсации более |
||||
медленных часов отправителя нет. При устойчивом отрицательном дрейфе резерв будет |
||||
снова опустошаться. Это отдельная задача длительных звонков: после восстановления |
||||
устройств проверить наклон глубины буфера и при необходимости добавить небольшую |
||||
компенсацию дрейфа в обе стороны, не смешивая её с быстрым catch-up после паузы. |
||||
|
||||
Принудительные 480 frames задают период 10 мс при 48 кГц. Собственный PA default |
||||
miniaudio — 25 мс с комментарием о проблемах PipeWire при периодах меньше ~20 мс. |
||||
Период backend и Opus-кадр 20 мс должны оставаться независимыми. Увеличивать |
||||
период до исправления AEC нельзя: 1200-sample блоки уже воспроизводят потерю хвоста. |
||||
После исправления разумно сначала проверить default backend и измерить задержку. |
||||
|
||||
## Предлагаемое восстановление без большого усложнения |
||||
|
||||
### 1. Сначала восстановить инварианты обработки и владения |
||||
|
||||
Исправить полный output, zero-progress, AEC accumulation и uninit при неудачном |
||||
start. Перед освобождением context явно остановить recorder, звонок, рацию и |
||||
прочие его устройства. До остановки отменять recovery timer; после join можно |
||||
сбрасывать voice-ресурсы. Проверять возвраты feed/start/init и логировать причину. |
||||
|
||||
Самый небольшой вариант — оставить один duplex-device: при нехватке capture |
||||
подать в call callback нулевой input, полностью сформировать playback и не |
||||
продвигать реальный capture-ring за синтетические samples. Это чинит непосредственный |
||||
дефект, но сохраняет общий жизненный цикл input/output и синтетический TX по |
||||
playback-clock. |
||||
|
||||
Для устойчивости к исчезновению микрофона предпочтительнее два устройства |
||||
capture/playback внутри существующего `CallAudioEngine`, как уже сделано в рации. |
||||
Capture только накапливает 960 samples и вызывает feed; playback независимо |
||||
вызывает pull и всегда заполняет output. Это два handle с одним владельцем, |
||||
без нового DSP-worker или дополнительной PCM-очереди. Потребуется уточнить |
||||
потоковый контракт voice-слоя: один capture-writer и один playback-writer, |
||||
согласованный доступ к AEC, остановка обоих до release. Текущий `g_mtx` уже |
||||
защищает AEC, но остальные общие поля нужно проверить перед разделением. |
||||
|
||||
### 2. Разделить краткий underrun и неисправность устройства |
||||
|
||||
| Событие | Действие | |
||||
| --- | --- | |
||||
| Нет сетевого PCM, callback продолжает приходить | Тишина/существующий gap tone; jitter автоматически накапливает данные. Устройство не пересоздавать. | |
||||
| Один backend underrun, новые кадры принимаются | Подать следующий полностью заполненный буфер; собрать счётчик. Не сбрасывать AEC и не запускать restart на каждый xrun. | |
||||
| Hole/переполнение capture | Учесть потерянную длительность; краткую потерю пережить, длительную отметить discontinuity для AEC. | |
||||
| Поток failed/stopped или нет продвижения кадров длительное время | Перейти в recovering и пересоздать затронутое устройство вне callback. | |
||||
| Suspend/resume ОС | Учитывать suspend как отдельную причину, восстановление проверять после resume; не запускать частый restart-loop во время сна. | |
||||
| Потерян общий backend/context | Однократно остановить всех его пользователей, восстановить владельца, затем требуемые устройства. | |
||||
|
||||
Один owner timer и состояния `Stopped / Running / Recovering` достаточны. |
||||
`wanted` хранит желание пользователя продолжать; generation меняется при stop, |
||||
новом звонке и смене устройства. Каждая отложенная попытка проверяет generation |
||||
и wanted — законченный звонок никогда не возрождается. |
||||
|
||||
Callbacks только обновляют атомарные счётчики успешно обработанных кадров и |
||||
признак backend failure. Watchdog проверяет фактический прогресс отдельно по |
||||
направлениям. Тишина в PCM не означает остановку устройства. Reporter диагностической |
||||
очереди не должен становиться watchdog: его события могут теряться. |
||||
|
||||
Начальные значения для проверки: timer 200 мс, подозрение при отсутствии прогресса |
||||
500 мс после startup grace; повторные попытки через 250/500/1000 мс с потолком |
||||
2 секунды. Это предлагаемые настройки, не доказанные универсальные пределы. |
||||
Suspend и фактический согласованный backend period должны учитываться отдельно. |
||||
Нельзя обещать bounded recovery, пока init/stop/uninit сами могут зависнуть: |
||||
сначала устранить spin-loop и проверить время backend-операций. Если они реально |
||||
блокируют GUI, вынести только управление устройствами в один owner-thread, |
||||
сохранив тот же автомат состояний. |
||||
|
||||
На пересоздании сбросить capture-accumulator, AEC delay/filter и локальный PCM/ |
||||
stretch, сохранив call ID и сетевую сессию. После длительной аппаратной паузы |
||||
устаревшую речь нужно явно отбросить до актуального резерва jitter, иначе |
||||
15 секунд накопленного звука при скорости 160% разгружаются примерно 25 секунд. |
||||
Этот сброс применять только при аппаратной discontinuity; обычный сетевой jitter |
||||
не должен автоматически приводить к отбрасыванию очереди. |
||||
|
||||
Устройство хранить по backend ID либо как default, не по индексу. При его |
||||
исчезновении попробовать default и явно сообщить о смене; выбранное устройство |
||||
можно вернуть при появлении. Нужны ошибки с ID/именем/направлением и результатом |
||||
попытки. Для PA-ошибок следующий уровень диагностики — stream/context state и |
||||
`pa_context_errno`, потому что один `-1` от API не объясняет причину. |
||||
|
||||
### 3. Проверить сценарии, а не только успешный старт |
||||
|
||||
После реализации добавить проверки: capture starvation 20/500 мс и возврат; |
||||
hole; AEC блоки, пересекающие границу кадра; zero writable; ошибка записи; |
||||
исчезновение выбранного устройства и появление default; suspend/resume; |
||||
перезапуск звукового сервера; stop во время backoff; закрытие GUI при активном |
||||
звонке/рации. Проверять полный output, bounded очереди и работу callback, |
||||
возобновление прогресса, отсутствие callbacks после release и отсутствие |
||||
повторного старта после пользовательского stop. |
||||
|
||||
## Как читать добавленную диагностику |
||||
|
||||
В GUI-категориях включить `call=debug` либо задать отдельную строку в INI: |
||||
|
||||
```ini |
||||
[debug] |
||||
call=debug |
||||
``` |
||||
|
||||
Обычные сводки идут на DEBUG, аномалии — WARN/ERROR; miniaudio info перенесён |
||||
на DEBUG, подробный backend log — TRACE. Все устройства используют категорию |
||||
call, включая recorder, radio и sound-engine; role и ID различают их в строках. |
||||
Не включать глобальный trace для всего проекта при измерении аудио. |
||||
|
||||
Reporter раз в секунду печатает stage, tid, callbacks/s, requested frames/s, |
||||
максимальный интервал между входами, полное wall-time обработки и thread CPU-time, |
||||
незавершённый вход и время с последнего входа. `work_max` и `cpu_max` — отдельные |
||||
максимумы за окно, они не обязательно относятся к одному вызову. |
||||
|
||||
| Stage | Что измеряется | |
||||
| --- | --- | |
||||
| loop | Один `pa_mainloop_iterate`, включая ожидание и вложенные callbacks. Долгое время само по себе не доказывает тяжёлый DSP. | |
||||
| read / write | Полный backend read/write callback. Frames/s здесь — запрошенные, не гарантированно обработанные кадры. | |
||||
| map | `writable_size` и `begin_write`; пустой/ошибочный выход тоже закрывает измерение. | |
||||
| submit | Вызов `pa_stream_write`. | |
||||
| duplex | Перенос capture-ring в duplex-output; detail содержит глубину capture-ring. | |
||||
| callback | Полный miniaudio data callback, включая код приложения и его синхронные логи. | |
||||
| feed / pull | Capture DSP/encode/send и playback jitter/stretch/tone в звонке. | |
||||
|
||||
`unfilled_frames` — незаполненный output, `hole_frames` — потеря capture timeline, |
||||
`capture_drop_frames` — переполнение capture-ring, `no_progress` — PA-write без |
||||
продвижения. `backend_underruns` — отдельный callback самого PA-stream; это не |
||||
то же самое, что пустой сетевой jitter. `backend playback started/resumed` |
||||
показывает фактическое начало/возобновление playback. Notification содержит |
||||
числовые miniaudio type/state. |
||||
|
||||
Первое событие каждого типа за окно имеет tid и CLOCK_MONOTONIC timestamp; |
||||
повторения суммируются. Для задержавшихся потоков reporter читает `/proc` wchan |
||||
и накопленные schedstat CPU/ready-wait/slices. Сравнивать соседние snapshots: |
||||
сами totals не являются длительностью текущей паузы. CPU-time намного меньше |
||||
wall-time означает ожидание/вытеснение; wchan помогает отличить poll от mutex/ |
||||
I/O wait. Это снимок в момент отчёта, не исторический стек зависшего вызова. |
||||
|
||||
Producer не выделяет память, не ждёт mutex reporter и не пишет диагностические |
||||
логи: ограниченная MPSC-очередь 8192 события, не более четырёх CAS-попыток. |
||||
Reporter не разыменовывает ma_device/pa_stream. Переполнение логируется как |
||||
`diagnostic events lost`; такое окно нельзя считать полным. Инструментация |
||||
добавляет получение часов/TID и атомарные операции, поэтому overhead ненулевой. |
||||
Частые события call jitter/AEC/TX теперь публикуются счётчиками и выводятся из |
||||
uasync вне аудиомьютекса. Часть существующих логов рации остаётся синхронной; |
||||
тракт не объявляется строго realtime-safe. |
||||
После close устройство удаляется из reporter: для устройств короче секунды |
||||
доступны lifecycle и первые аномалии, но не полная секундная сводка. |
||||
|
||||
Новый binary собран в `tools/chatgui/build/vibechat`; для нового измерения нужен |
||||
его обычный перезапуск. Изменение файла binary не обновляет уже запущенный процесс. |
||||
|
||||
## Реализация (2026-10-02) |
||||
|
||||
`SoundManager` владеет общим контекстом и конечными выходами. На каждый фактический |
||||
выход приходится один микшер: PCM сообщений/видео, заранее декодированные сигналы, |
||||
готовый голос звонка и рации. После суммирования float PCM ограничивается, и один |
||||
и тот же stereo буфер отправляется устройству и AEC соответствующего микрофона. |
||||
Звук другого выхода в референс не входит. Default/явный backend ID объединяются; |
||||
PulseAudio также публикует идентификатор фактического маршрута. После server move |
||||
совпавшие выходы объединяются в GUI только после подтверждения фактического |
||||
маршрута; предположение о default не используется вместо PA snapshot. При временной коллизии дополнительный выход |
||||
отдаёт тишину до объединения. Изменение списка источников выполняется после stop |
||||
callback, с сохранением backend stream и выбранного сервером маршрута. |
||||
|
||||
Устройства сохраняются как backend ID вместо индекса. При init/start failure |
||||
явного выбора пробуется default, причина логируется. Выбранный ID сохраняется; |
||||
вернуться к нему можно повторным выбором или при следующем recovery. Автоматический |
||||
опрос hotplug для возврата на первоначальный выбор не добавлен. |
||||
|
||||
`AudioDevice` — владелец одного miniaudio device в GUI. Успешный init всегда имеет |
||||
парный uninit, включая ошибку start. Callback и его состояние готовятся до start. |
||||
Close останавливает/join-ит device worker перед освобождением callback. Отдельный |
||||
атомарный счётчик кадров контролирует прогресс. Таймер 200 мс проверяет stopped |
||||
и отсутствие продвижения 1 с; suspend продлевает grace, отдельный xrun не вызывает |
||||
restart. Backoff: 250/500/1000/2000 мс, сброс после продвижения. |
||||
|
||||
Miniaudio не добавляет накопитель фиксированного периода: `noFixedSizedCallback` |
||||
включён, hardware chunks могут иметь любую длину. В PulseAudio физическое начало |
||||
блока определяется по timing snapshot с AUTO_TIMING_UPDATE/INTERPOLATE_TIMING. |
||||
Capture: monotonic now минус latency read index, без повторного вычитания длины |
||||
блока. Playback: now плюс latency write index. Учитываются задержка converter и |
||||
остающийся input cache. Один backend dispatch имеет один snapshot, позиции |
||||
последующих client callbacks продвигаются по числу кадров. В отсутствие snapshot |
||||
используется оценка по callback; источник времени и physical route логируются. |
||||
PA hole отдельно помечен как отсутствующий capture, а не как реальная тишина. |
||||
|
||||
`VoiceAudioIo` создаётся только для звонка и рации. Capture и RX обрабатывают |
||||
независимые потоки. Hardware capture только копирует PCM в фиксированную MPSC-очередь; |
||||
RX worker выполняет decode/SoundTouch/тоны и поддерживает до 40 мс готового голоса. |
||||
В сервисном слое рации вычисление Silero держит только TX mutex: общий mutex |
||||
отпущен, поэтому VAD не блокирует RX; lifetime и порядок PCM/PTT сохранены. |
||||
Hardware output смешивает готовый PCM. Opus, AEC, VAD, AGC и растущие буферы записи |
||||
не исполняются в аппаратных callbacks. Обработка самого backend, PCM/resampler |
||||
узлов miniaudio и атомарные уведомления остаются в callback; строгая hard realtime |
||||
гарантия не заявляется. |
||||
|
||||
Capture worker — единственный владелец AEC и порядка PCM/PTT/mute. Кадр AEC — |
||||
960 отсчётов на канал; 1/2 канала микрофона и 2 независимых канала референса. |
||||
Противофазное стерео не усредняется. История рендера содержит до 1,92 с PCM и |
||||
физические timestamps. Reference выбирается по времени микрофона; небольшое |
||||
различие часов компенсируется интерполяцией только референса. Реальная тишина |
||||
сохраняется как валидный PCM; при отсутствующем reference AEC пропускает микрофон |
||||
и не адаптируется. Потеря reference, смена маршрута/поколения и скачок часов |
||||
сбрасывают фильтр. Возраст capture-очереди ограничен 100 мс от enqueue, отдельно |
||||
от собственной задержки backend. Потери и очередь видны в сводках категории `aec`. |
||||
|
||||
Mute применяется после AEC/AGC: фильтр продолжает получать настоящий микрофон, |
||||
а Opus получает нули только в отмеченной части кадра. Desktop запускает core |
||||
через `call_audio_start_prepared`: повторного core AEC нет. Raw API с core AEC |
||||
сохранён для Android/headless; смешивать политики в активном звонке нельзя. |
||||
|
||||
У рации один автомат передачи: manual PTT или VAD. VAD читает непрерывный очищенный |
||||
сигнал до AGC, включая время ручного PTT. Передача использует отдельный накопитель |
||||
Opus-кадра; границы PTT внутри AEC-кадра сохраняются. При закрытии серии хвост |
||||
дополняется нулями только перед Opus и помещается в очередь перед FIN. Короткий |
||||
capture tail при stop проходит без AEC, а не дополняет непрерывный фильтр. Разрыв |
||||
захвата завершает старый сетевой хвост и сбрасывает VAD, исключая склейку через |
||||
потерянные отсчёты. Capture restart завершает принятую серию; новое прослушивание |
||||
того же канала отменяет старые BEGIN/media. FIN проверяет токен своего capture/burst |
||||
и поколение экземпляра ядра, поэтому не закрывает более новую серию и не обращается |
||||
к освобождённому ядру даже при повторном использовании его адреса. |
||||
|
||||
TX ограничен 8 media-задачами, возраст — 200 мс. У рации дополнительно ограничено |
||||
16 control-задач; FIN выделяется заранее вместе с BEGIN. Если control-очередь |
||||
занята, новый BEGIN откладывается до освобождения; это логируется. Очередь PCM |
||||
резервирует 8 слотов для управляющих сообщений; producer не меняет consumer и |
||||
публикует слот только после копирования. Capture callback никогда не ждёт слот. |
||||
Отказ постановки GUI-команды завершает capture; таймер запускает recovery. |
||||
|
||||
`AudioRecorder` получает raw microphone без AEC. Его worker обрабатывает AGC, |
||||
накапливает запись и публикует атомарный уровень/длительность. Переполнение или |
||||
ошибка обработки отклоняют запись с логом. Meter-only использует тот же capture, |
||||
но не сохраняет PCM и не создаёт голосовое сообщение. Stop: close device, drain |
||||
и join worker, затем передача владельца записи задаче attachment. Настройки |
||||
компрессора в ходе записи применяются к следующей. |
||||
|
||||
PCM воспроизводится через независимые handles. Сообщение, видео и тест динамика |
||||
не останавливают чужой PCM; sample rate принадлежит источнику. Seek пересоздаёт |
||||
узел и очищает его read/resampler cache, сохраняя handle и владельца PCM. Cursor |
||||
вычисляется по реально обработанным графом кадрам, а не по prefetched datasource. |
||||
Pause отсоединяет узел и ждёт начатое чтение графа; resume подключает тот же узел, |
||||
сохраняя его PCM и cache. Соседние проигрыватели продолжают работать. |
||||
У асинхронного открытия видео/голоса проверяется generation. |
||||
|
||||
Потоковый AGC использует фиксированные кольца истории и PCM, нулевой lookahead |
||||
и непосредственный process_frame без растущего output. У записи lookahead явно |
||||
100 мс; растёт только сохраняемый результат. Push/flush/configure возвращают |
||||
ошибки; flush проверяется перед кодированием вложения. |
||||
|
||||
Порядок остановки голосового направления: close capture → join capture worker → |
||||
detach output → join RX worker → release core. При failed context синхронный сигнал |
||||
сначала закрывает всех клиентов, затем освобождает context. Mixer, PCM handles, |
||||
call ID и группа переживают recovery. Stop выключает recovery; таймер завершения |
||||
звонка проверяет generation. MainWindow останавливает клиентов до SoundManager |
||||
и сетевого ядра. Сетевые операции и регистрация audio ops выполняются в uasync. |
||||
|
||||
PulseAudio control-waits ограничены 1 с на операцию; при timeout operation |
||||
отменяется до возврата, исключая callback с просроченным stack pointer. Partial |
||||
init освобождает контекст/mainloop/события. Failed stream/context содержит PA |
||||
errno в логе. Zero writable/zero converter progress возвращают управление. |
||||
Последовательность нескольких операций init/uninit всё ещё может задержать GUI |
||||
дольше секунды; отдельный device owner-thread не добавлен. |
||||
|
||||
## Проверки реализации |
||||
|
||||
`vibechat` и аудиотесты собираются через CMake. Набор проверки: |
||||
|
||||
```sh |
||||
ctest --test-dir tools/chatgui/build --output-on-failure \ |
||||
-R 'test_(linux_audio|audio_timing|audio_diagnostics|audio_recovery|audio_event_queue|voice_audio_io|voice_tx|call_jitter|radio_audio|radio_jitter|voice_file)$' |
||||
make -C tests test_audio_compressor test_speex_aec |
||||
./tests/test_audio_compressor |
||||
./tests/test_speex_aec |
||||
``` |
||||
|
||||
Добавлены отдельные проверки MPSC (80000 сообщений, reserve/control, слот ещё не |
||||
опубликован), точных PCM/PTT/mute границ, короткого хвоста, независимого RX при |
||||
задержке capture worker, противофазного stereo AEC, старого физического timestamp |
||||
при свежем enqueue, совпадения реальных Opus-пакетов с правильно дополненным |
||||
хвостом, rapid PTT, capture restart, отмены старого прослушивания и bounded control. |
||||
Проверяются manual/VAD handover |
||||
без второй серии и продвижение настоящего сервисного RX при заблокированном VAD. |
||||
Timing test проверяет read-index timestamp, несколько converter callbacks одного |
||||
блока и метку hole. Recorder тестирует raw PCM и отсутствие вложения в meter-only. |
||||
|
||||
Speex проверяет подавление эха и сохранение double-talk также для двух микрофонов |
||||
с двумя независимыми каналами reference. В детерминированном синтетическом тесте |
||||
остаточная энергия эха — около 0,009 исходной; корреляция near-end — около 0,95. |
||||
Эти величины относятся только к тесту, а не к реальным акустическим условиям. |
||||
|
||||
В `/tmp/utun-audio-asan` проверяются пять тестов нового тракта с ASAN/UBSAN и |
||||
LeakSanitizer; LeakSanitizer запускается вне песочницы. MPSC отдельно проверяется |
||||
ThreadSanitizer. Изолированный интеграционный запуск: |
||||
|
||||
```sh |
||||
python3 tools/chatgui/tests/run_pulse_recovery.py |
||||
``` |
||||
|
||||
Итоговый прогон 2026-10-02: сборка `vibechat` успешна, CTest — 11/11, |
||||
ASAN/UBSAN/LeakSanitizer — 5/5, компрессор — 479/479, Speex — PASS, |
||||
MPSC под ThreadSanitizer — PASS. Timing-стенд прошёл ещё 100 последовательных |
||||
повторов. Пауза PCM проверяется также серией pause/resume при работающем соседнем |
||||
проигрывателе; она ждёт чтение узла, а не только меняет его флаг. |
||||
|
||||
Тест создаёт собственные PipeWire/PulseAudio/WirePlumber в `/tmp`, отключает |
||||
аппаратные monitors и использует два виртуальных sink. Проверяет объединение |
||||
voice с общим выходом после `move-sink-input`, сохранение desired выхода звонка |
||||
при recovery общего выхода (включая server stream-restore), затем восстановление capture/PCM |
||||
после остановки и запуска своего PulseAudio. Звонок и рация сохраняют идентичность; |
||||
recorder и медиа продолжают работу. Системный аудиосервер не затрагивается. |
||||
|
||||
Исторические проверки исходного recovery: полный `check.sh` — 119 passed, |
||||
0 failed, 1 skipped; proxy/burst/load интеграции прошли. По указанию пользователя |
||||
async-тесты исключены из дальнейших запусков. APK ранее собран offline; установка |
||||
на телефон не выполнялась. |
||||
|
||||
Физический USB/Bluetooth hotplug, сон ОС, внешние звуки других приложений и качество |
||||
AEC в длительном реальном звонке этими проверками не подтверждены. Reference |
||||
охватывает звук этого приложения на том же выходе, согласно выбранному контракту. |
||||
|
||||
## Референсы |
||||
|
||||
Первичные исходники и документация просмотрены 2026-10-01; ссылки на master могут |
||||
изменяться. Используем принципы, а не копируем целиком архитектуру библиотеки. |
||||
|
||||
- [PipeWire audio-src-ring.c](https://docs.pipewire.org/audio-src-ring_8c-example.html): |
||||
callback читает доступные кадры, заполняет остаток тишиной и возвращает полный |
||||
буфер; пополнение инициирует через событие управляющего потока. Хороший минимальный |
||||
пример поведения при пустой PCM-очереди. |
||||
- [PulseAudio stream API](https://github.com/pulseaudio/pulseaudio/blob/master/src/pulse/stream.h): |
||||
отдельные underflow/overflow/started/suspended/moved callbacks; peek различает |
||||
пустую очередь и hole с ненулевой длиной. Нужно разделять эти состояния в логах. |
||||
- [PulseAudio pacat.c](https://github.com/pulseaudio/pulseaudio/blob/master/src/utils/pacat.c): |
||||
underflow callback сообщает событие без пересоздания stream. При failed context |
||||
утилита завершает работу — это пример диагностики, не готовая self-healing система. |
||||
- [Mozilla cubeb PulseAudio backend](https://github.com/mozilla/cubeb/blob/master/src/cubeb_pulse.c): |
||||
явные context/stream states, пересоздание ошибочного context при инициализации, |
||||
согласованное отключение callbacks и доступ под mainloop lock; minimum latency |
||||
25 мс. Это полезный референс владения и границ управления, а не основание |
||||
автоматически заимствовать всю библиотеку. |
||||
- [miniaudio](https://github.com/mackron/miniaudio/blob/master/miniaudio.h): |
||||
init/start/stop/uninit запрещены внутри data callback из-за deadlock. Просмотренный |
||||
upstream тоже содержит break при пустом duplex-ring: обновление библиотеки само |
||||
по себе не решает этот найденный дефект. |
||||
@ -1,66 +0,0 @@
|
||||
# Важные нюансы работы встроенного lwIP TCP-стека |
||||
|
||||
Краткая памятка по тонким местам `src/lwip_tcp/`, критичным для прокси. |
||||
Полное описание модуля — в `src/lwip_tcp/lwip_tcp_doc.md`. |
||||
|
||||
## Ключевые константы (`lwip_tcp_opts.h`) |
||||
|
||||
| Константа | Значение | Смысл | |
||||
|-----------|----------|-------| |
||||
| `TCP_MSS` | 1460 | максимальный размер сегмента | |
||||
| `TCP_WND` | 8×MSS = 11680 | окно приёма lwIP (объявляется пиру) | |
||||
| `TCP_SND_BUF` | 16×MSS = 23360 | буфер отправки | |
||||
| `TCP_TMR_INTERVAL` | 250 мс | базовый интервал таймера | |
||||
| `tmr_interval_ms` | `TCP_TMR_INTERVAL/4` = 62.5 мс | фактический шаг `tcp_fasttmr` | |
||||
| `TCP_WND_UPDATE_THRESHOLD` | `TCP_WND/4` = 2920 | порог немедленного ACK окна | |
||||
|
||||
**Важно:** таймер uasync использует timebase 0.1 мс — `uasync_set_timeout(ua, N, …)` |
||||
задаёт N×0.1 мс. Например, 5000 = 500 мс. |
||||
|
||||
## Контракт recv_cb — главный источник багов |
||||
|
||||
lwIP вызывает `recv_cb(arg, pcb, pbuf, err)` для данных, **уже извлечённых из pcb**. |
||||
Дальше приложение само решает судьбу данных и окна: |
||||
|
||||
| Действие | Результат | |
||||
|----------|-----------| |
||||
| скопировать + `tcp_recved(pcb, len)` | данные приняты, окно восстановлено (норма) | |
||||
| `return != LERR_OK` | lwIP кладёт pbuf в `refused_data` и отдаст позже — **без потери**, окно закрывается | |
||||
| `pbuf_free(p)` без `tcp_recved` | данные потеряны, окно не восстановлено → схлопывание окна | |
||||
|
||||
`refused_data` — штатный backpressure lwIP: одноканальный (один pbuf), ре-доставка |
||||
через `tcp_fasttmr` (62.5 мс) или при следующем входящем сегменте. |
||||
|
||||
**Ошибка, из-за которой падал `tcp_proxy_full`:** при backpressure прокси делал |
||||
`pbuf_free(p)` без `tcp_recved`. Каждый такой сброс уменьшал `rcv_wnd` на MSS; после |
||||
~8 пакетов окно падало в 0, OS-TCP переставал слать, и передача умирала. |
||||
|
||||
## Управление окном и «деградация 62.5 мс» |
||||
|
||||
`tcp_recved(pcb, len)` увеличивает `rcv_wnd` и через `tcp_update_rcv_ann_wnd` решает, |
||||
слать ли ACK окна немедленно. Немедленный ACK идёт только если прирост окна |
||||
`wnd_inflation ≥ TCP_WND_UPDATE_THRESHOLD` (=2920 = 2×MSS); иначе ACK откладывается |
||||
до `tcp_fasttmr` (62.5 мс). |
||||
|
||||
Отсюда классическая ловушка: если после backpressure окно восстанавливать **по одному |
||||
пакету** (`tcp_recved(1460)` за раз), прирост 1460 < 2920 → ACK уходит раз в 62.5 мс → |
||||
пир шлёт 1 пакет за 62.5 мс ≈ 23 КБ/с. Это выглядит как «пропускная способность |
||||
застряла», хотя сеть свободна. |
||||
|
||||
**Правильно:** восстанавливать окно **пачкой** (несколько `tcp_recved` подряд либо один |
||||
`tcp_recved(TCP_WND_MAX - rcv_wnd)`), тогда прирост ≥ 2920 → ACK немедленный → пир |
||||
сразу возобновляет бурст. |
||||
|
||||
## Рекомендуемый паттерн для relay-потребителя (как в `tcp_proxy_client.c`) |
||||
|
||||
1. При получении данных — копировать в ограниченную очередь, **не вызывать** |
||||
`tcp_recved` (окно само плавно закрывается; очередь ограничена `TCP_WND`). |
||||
2. Дрейн очереди по сигналу освобождения нижележащего канала |
||||
(`etcp_router_on_send_ready`) + retry-таймер (force=1) как safety-net. |
||||
3. `tcp_recved(len)` вызывать **только после успешной отправки** — окно |
||||
восстанавливается пачкой по мере дрена, ACK уходит немедленно. |
||||
4. Никогда не сбрасывать pbuf без `tcp_recved`; не держать данные в `refused_data` |
||||
дольше одного пакета (single-slot). |
||||
|
||||
См. реализацию: `tcp_proxy_client_tx_queue_drain_cb` / `tcp_proxy_client_fin_flush` |
||||
в `src/proxy/tcp_proxy_client.c`. |
||||
@ -1,60 +0,0 @@
|
||||
# Подписанная запись узла |
||||
|
||||
`TOPO_NODE.timestamp` — версия всей подписанной записи, а не отдельного адреса. |
||||
Узел-владелец назначает Unix microseconds, строго больше предыдущей сохранённой |
||||
версии. Имя, тип, активность, адреса, метаданные сокетов, REALITY и подпись |
||||
заменяются вместе. Timestamp входит в подпись. Групповые пути и подсети |
||||
остаются в `TOPO_GROUP_NODE` и не являются частью общей записи узла. |
||||
|
||||
Получатель проверяет подпись до изменения реестра или маршрутов. Более свежая |
||||
запись заменяет старую целиком, включая пустые списки адресов. Старые записи |
||||
игнорируются; разные подписанные записи с одинаковым timestamp — конфликт. |
||||
Повторное BGP-объявление может обновить путь, но не содержимое общей записи. |
||||
8-битный `ver` сохраняется как диагностический счётчик; свежесть определяется |
||||
только timestamp. |
||||
|
||||
Таблица `node_snapshots` хранит полную сериализованную запись без группового |
||||
окружения. `node_addresses` остаётся проекцией для поиска и отображения, а не |
||||
источником для восстановления подписанной записи. При наличии сохранённого |
||||
snapshot загрузка использует его. Локальная публикация сохраняется до рассылки; |
||||
ошибка сохранения не разрешает публикацию новой версии. |
||||
|
||||
Timestamp 0 обозначает bootstrap-сведения без подписи (конфиг, приглашение, |
||||
начальные адреса). Они могут помочь установить первое соединение, но не |
||||
заменяют принятую подписанную запись. По BGP принимаются подписанные записи |
||||
с ненулевым timestamp. Формат NODEINFO изменён без обратной совместимости. |
||||
|
||||
После принятия BGP-записи `node_conn_direct_update_node` применяет адреса к |
||||
существующему NCD-соединению. Отсутствующее соединение этот вызов не создаёт. |
||||
Линки дополняются как при UP, так и до установления соединения; повторная |
||||
доставка не создаёт дубликаты UDP/TCP. Работающие линки не закрываются только |
||||
из-за смены списка адресов. NCD удерживает ссылку на общую запись, пока |
||||
обслуживает соединение, и использует её при восстановлении. |
||||
|
||||
`node_conn_direct_open_node` сверяет переданную запись с реестром/БД. Более |
||||
свежая подписанная запись принимается целиком, старая уступает уже известной. |
||||
Bootstrap-адреса не перекрывают подписанную запись даже при DOWN. |
||||
|
||||
Конфигурационные подключения UTUN используют один адаптер |
||||
`init_client_connections` для старта и reload. Все адреса, TCP и REALITY |
||||
передаются NCD; адаптер не создаёт ETCP-линки. Новый набор handles открывается |
||||
до освобождения старого; при ошибке старые handles сохраняются. Конфигурация |
||||
удерживает один handle на клиента, независимо от количества его адресов. |
||||
|
||||
Включение конфигурационного пира в UTUN-группу выполняет callback NCD UP. |
||||
Транспортный UP и наличие callback старого `etcp_connect` больше не включают |
||||
соединение в UTUN-группу автоматически. Входящий JOIN остаётся явным запросом |
||||
участия. При пересоздании UTUN-группы учитываются её конфигурационные handles. |
||||
Удаление клиента отправляет `LEAVE_GROUP` и освобождает удержание UTUN-группы |
||||
с обеих сторон. Другие владельцы NCD сохраняют транспорт. Поздние NODEINFO и |
||||
WITHDRAW от отсоединённого UTUN-пира игнорируются, чтобы не вернуть его маршруты. |
||||
LEAVE ждёт backpressure через queue waiter и временно удерживает собственный |
||||
NCD handle. Закрытие транспорта отменяет ожидание; новое участие в группе |
||||
делает ожидающий LEAVE устаревшим, и он не отправляется. |
||||
Причина удаления пира передаётся явно: только потеря транспорта запускает |
||||
recovery каскадных маршрутов. Локальный/удалённый LEAVE и удаление битой |
||||
member-записи не должны восстанавливать намеренно закрытое участие. |
||||
|
||||
REALITY использует полное SNI до 255 символов; размер поля в записи узла |
||||
согласован с транспортом. Если локального TCP listen-сокета нет, NCD может |
||||
создать исходящий TCP-линк с выбором локального адреса операционной системой. |
||||
@ -1,12 +0,0 @@
|
||||
Работа лички |
||||
|
||||
1. отдели логику лички от логики групповых чатов. не смешивай. |
||||
|
||||
Работа лички. сообщения и медиа доставляется напрямую если узел доступен через bgp. |
||||
если недоступен - то pending сообщения сохраняются на суперузле и pending media сохраняется на каком-либо медиаузле, а суперузлу сообщается где хранится медиа. |
||||
при появлении пира суперузел организует доставку медиа до получателя. после успешной доставки медиа/сообщений они удаляются с промежуточных узлов. |
||||
|
||||
время хранения медиафайлов на промежуточных узлах ограничено - 1 неделя по умолчанию (задачется в конфиге этих узлов). |
||||
для доставки медиа поднимаем линк (conn_mgr). |
||||
|
||||
и посмотри реализацию медиа для групповых чатов. |
||||
@ -1,61 +0,0 @@
|
||||
# Proxy: потоки TCP и датаграммы |
||||
|
||||
Клиентские входы — TUN (lwIP), SOCKS5 CONNECT и HTTP proxy/CONNECT. Exit открывает |
||||
обычный TCP-сокет к назначению. TCP-поток на exit определяется парой `(peer_node_id, |
||||
stream_id)`. Клиент принимает ответы и уведомления restart только от настроенного |
||||
exit в UTUN-группе. UDP/ICMP-контексты принадлежат конкретному `UTUN_INSTANCE`. |
||||
|
||||
## TCP |
||||
|
||||
Общие определения находятся в `src/proxy/proxy_protocol.h`. Передаваемый заголовок: |
||||
`svc_id:1, command:1, stream_id:4`; поля маршрута добавляет ETCP-router. |
||||
|
||||
- CONNECT (1): IPv4:4 и порт:2 в сетевом порядке. |
||||
- CONNECTED (2): exit подтвердил успешный TCP connect. Только после этого клиент |
||||
сообщает SOCKS success / HTTP 200 и начинает передавать накопленные данные. |
||||
- DATA (3): от 1 до 4096 байт. |
||||
- CLOSE (4), ERROR (5): прекращение потока. |
||||
- FIN (6): конец одного направления; все предшествующие DATA должны быть переданы. |
||||
- WINDOW (7): uint32_t, число дополнительно разрешённых байт. |
||||
|
||||
У каждого направления начальное окно 65536 байт. DATA уменьшает окно отправителя |
||||
и получателя. Получатель возвращает кредит после записи в локальный TCP-сокет |
||||
либо принятия данных ограниченным send-buffer lwIP. Очередь ETCP сама по себе |
||||
не является подтверждением потребления данных конечным TCP. |
||||
|
||||
FIN ждёт освобождения исходящей очереди, включая данные, остановленные окном |
||||
или backpressure роутера. Закрытие одного направления не прекращает другое. |
||||
Окончательное освобождение происходит после обоих FIN и передачи оставшихся данных. |
||||
Непереданные управляющие сообщения повторяются таймером. Ошибки и переходы |
||||
состояния диагностируются категорией `proxy`; состояние сокетов — `socket`. |
||||
|
||||
Парсеры сохраняют остаток после greeting/request/HTTP headers. HTTP CONNECT ждёт |
||||
полного блока заголовков. Во время DNS/connect чтение ограничено очередью tcp_io; |
||||
накопленные HTTP headers/body передаются частями размером DATA. |
||||
|
||||
## UDP и ICMP |
||||
|
||||
REQUEST и REPLY различаются явно. Ответ допускается только от настроенного exit. |
||||
UDP-ответ восстанавливается с нулевой UDP checksum (допустимо для IPv4), IPv4 checksum |
||||
пересчитывается. ICMP checksum учитывает нечётный последний байт без чтения за буфером. |
||||
|
||||
Exit заменяет ICMP id/sequence уникальной среди ожидающих запросов парой и сопоставляет |
||||
ответ также с IP назначения. Исходные id/sequence восстанавливаются для клиента. |
||||
Это исключает смешивание ping-запросов разных клиентов с одинаковыми исходными ID. |
||||
|
||||
## Проверки и ограничения |
||||
|
||||
`test_proxy_packets` проверяет IPv4/UDP/ICMP и payload длиной 0..1501. |
||||
`test_tcp_io_flush` проверяет уведомление после последнего синхронного send. |
||||
`test_proxy_regressions` использует реальные локальные сокеты и управляемую доставку |
||||
ETCP: совместные/раздельные handshake, ранние данные, отложенный CONNECTED, HTTP 502, |
||||
большой POST, одинаковые stream ID разных пиров, FIN при закрытом окне, RST и IPv4 options. |
||||
|
||||
На Linux полный `./check.sh`: 109 passed, 0 failed, 1 skipped; дополнительно прошли |
||||
настоящий TUN, full-duplex burst и нагрузка 8 соединений / 128 МиБ. |
||||
Windows/FreeBSD в этом прогоне не проверялись. |
||||
|
||||
SOCKS IPv6 явно отклоняется кодом address type not supported. IPv4-фрагменты |
||||
на входе proxy TUN отклоняются с диагностикой; сборка фрагментов не реализована. |
||||
Формат TCP дополнен CONNECTED/WINDOW, UDP-команды разделены: клиент и exit необходимо |
||||
обновлять вместе; совместимость со старым proxy-протоколом не предусмотрена. |
||||
@ -1,86 +0,0 @@
|
||||
# Жизненный цикл ядра и сервисов |
||||
|
||||
Все операции выполняются в потоке единственного `UASYNC` экземпляра, вне callbacks |
||||
останавливаемых модулей. GUI передаёт команды в этот поток. |
||||
|
||||
```c |
||||
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config); |
||||
if (!inst || utun_core_start(inst) < 0) /* обработать ошибку */; |
||||
if (chat_service_start(inst) < 0) /* обработать ошибку */; |
||||
if (utun_service_start(inst) < 0) /* обработать ошибку */; |
||||
|
||||
chat_service_stop(inst); /* UTUN продолжает работать */ |
||||
chat_service_start(inst); /* загружает сохранённые каналы */ |
||||
utun_service_stop(inst); /* чат продолжает работать */ |
||||
utun_instance_destroy(inst); |
||||
``` |
||||
|
||||
`utun_instance_init()` — сокращение для запуска ядра, UTUN и чата, если чат включён |
||||
в конфигурации. Desktop GUI и Android запускают ядро и чат явно, без UTUN. |
||||
Повторный start работающего сервиса и повторный stop допустимы. Ошибка старта |
||||
сервиса откатывает созданные им ресурсы. `utun_instance_stop()` останавливает цикл |
||||
приложения; освобождение ядра выполняет `utun_instance_destroy()`. |
||||
|
||||
| Владелец | Ресурсы | |
||||
|---|---| |
||||
| Ядро | Идентичность узла, реестр узлов и групп, SQLite, транспортные сокеты, NCD, маршрутизатор, общая репликация, время, мониторинг | |
||||
| UTUN | Группа UTUN, запросы соединений из конфигурации, DATA binding, TUN, системные маршруты, NAT transport | |
||||
| Чат | CHAT-группы, запросы подключения и join, экземпляры синхронизации каналов, chat API, медиа, личные сообщения, звонки и headless API | |
||||
| Группа | Сессии пиров, её маршруты и логические каналы, планировщик подключения и recovery | |
||||
| Сессия пира группы | NCD handle с момента CONNECTING, согласованные эпохи, состояние обмена таблицей, очередь отправки | |
||||
|
||||
SQLite открывается ядром один раз: `db_path/chats.db`, либо `:memory:`, если путь |
||||
не задан. Остановка чата сохраняет каналы и сообщения в этой БД. В варианте |
||||
`:memory:` данные сохраняются только до уничтожения ядра. |
||||
|
||||
Общий транспорт живёт, пока есть его владельцы. Закрытие запроса одной группы |
||||
не разрывает соединение, нужное другой группе. При остановке группы отменяются |
||||
её планировщики, запросы, таймеры и ожидания очередей; отправляется LEAVE, |
||||
закрываются логические каналы маршрутизатора и удаляется её ожидающий транзит |
||||
на общих соединениях. Транзит других групп сохраняется. Отложенный LEAVE может временно |
||||
удерживать транспорт самостоятельно, не обращаясь к уже удалённой группе. |
||||
|
||||
Остановка чата сначала прекращает внешние команды и фоновые задания, затем |
||||
закрывает медиа и сервисы, синхронизацию и группы, после чего освобождает chat core. |
||||
`media_async_destroy()` дожидается завершения native workers и вызывает каждый |
||||
ожидающий callback с `MEDIA_ASYNC_CANCELLED`, пока его контекст ещё существует. |
||||
Worker резервирует completion до запуска и передаёт его через `uasync_post_reserved`; |
||||
периодического polling нет. Destroy выполняется вне callbacks uasync, после join |
||||
отменяет pending post и завершает done ровно один раз. Uasync живёт до конца этой очистки. |
||||
Результаты отменённых задач не записываются в БД. Это синхронное ожидание: длительное |
||||
транскодирование или распознавание речи может задержать stop; принудительного |
||||
прерывания этих библиотек нет. |
||||
|
||||
Статус транспорта UP подтверждает восстановление физического пути. READY сессии |
||||
группы означает согласованный JOIN/ACCEPT и завершённый обмен её таблицей; именно |
||||
его ждёт recovery. REINIT меняет эпохи обмена, сохраняя рабочие маршруты. Эпохи |
||||
защищают от сообщений старой сессии и не задают срок жизни маршрута. |
||||
|
||||
Проверки: `test_services` проверяет независимый stop/start, общий NCD в CONNECTING, |
||||
сохранение идентичности/сокетов/БД, повторную загрузку каналов, отмену workers, |
||||
заблокированного медиа-потока, транзита и ожиданий заменённой очереди. `test_chat_join_e2e` |
||||
проверяет join и распространение membership между узлами без UTUN-сервиса. |
||||
|
||||
В Android звонковой сессией владеет `CallController` уровня приложения; события |
||||
ядра и команды Telecom обрабатываются один раз в main thread, без повторного |
||||
проигрывания событий при создании ViewModel. UI получает `StateFlow` и передаёт |
||||
пользовательские команды. `CallService` владеет `CallAudioEngine` и частичным |
||||
wake lock: перед исходящим вызовом или принятием входящего запускается foreground |
||||
режим `phoneCall|microphone`, затем сигналинг и аудио. Завершение сначала |
||||
останавливает I/O и дожидается аудиопотоков, затем освобождает native-аудио, |
||||
wake lock и сервис. Перед `NativeLib.stop/restart/destroy` выполняется тот же |
||||
порядок; новый звонок разрешается после `SERVICE_STARTED` нового ядра. |
||||
|
||||
Активность ядра задаётся явно: видимая Activity или незавершённый звонок удерживают |
||||
ACTIVE и выключают Android standby. Состояние не истекает по таймеру. После |
||||
завершения звонка в фоне возвращается standby; экран может гаснуть во время |
||||
разговора независимо от частичного wake lock сервиса. |
||||
|
||||
`utun_set_client_activity()` в сборке с `UTUN_HAVE_STANDBY` применяет фактический |
||||
режим duty-cycle до рассылки событий активности; JNI не переключает его отдельно. |
||||
Повторная команда не перезапускает фазу и анонсы, но первый фоновый запуск включает |
||||
standby даже при начальном `client_activity=STANDBY`. Keepalive подписан на режим |
||||
и фазы самого standby; новые UDP/TCP-линки сразу анонсируют текущую фазу после |
||||
handshake. Пробуждение по таймеру и внешней сети сначала сообщает ACTIVE-окно |
||||
пирам, затем запускает ожидающие задачи. При выходе сначала восстанавливаются |
||||
линки; deinit отменяет ожидающие задачи и очищает подписки старого ядра. |
||||
@ -1,442 +0,0 @@
|
||||
# Задачи по проекту |
||||
|
||||
Сюда пишется список задач с короткой аннотацией. Если задача большая и имеет ТЗ - то ТЗ оформляется отдельным файлом, а сюда помещается аннотация и ссылка на ТЗ. |
||||
|
||||
## Текущая задача |
||||
[+] **DM: надёжная доставка сообщений, первый этап pm.txt** — 01.10.2026: |
||||
атомарные история/seq/outbox, подписанные точные квитанции после commit, |
||||
live BGP-маршрут и durable очередь на суперузле с проверкой подписанной роли. |
||||
Повторы, out-of-order, rollback БД, неверные ACK и поздний PUT проверены; |
||||
40 offline-сообщений доставлены после restart источника, первый offline DM — |
||||
после restart суперузла при выключенном источнике. Копии удалены по квитанции. |
||||
Подготовка ciphertext медиа использует ключ target и независимые nonce порций; |
||||
проверены границы 32 КиБ, пустые файлы, неверные ключи/UUID/hash и повреждения. |
||||
Clean-сборка ядра/Qt, test_dm, test_dm_e2e и полный check прошли: |
||||
113 PASS, 0 FAIL, 1 SKIP (auto_socket_dynamic требует root); proxy/burst/load PASS. |
||||
Текущие гарантии и оставшаяся интеграция: `doc/dm_arch.md`. |
||||
|
||||
[+] **DM: сетевая доставка и pending-медиа по pm.txt** — 01.10.2026: |
||||
отдельные owners dm_media/dm_mailbox_media и файловый transfer с CM handle, |
||||
подписанные метаданные и независимая квитанция файла после durable приёма. |
||||
Прямая доставка или S→M custody, restart S/M при выключенном A, доставка B, |
||||
повтор удаления до DELETE_ACK; поздние STORE/STORED не возрождают копии. |
||||
TTL M по умолчанию 7 дней, резервирование квоты, отмена transfer и stop workers. |
||||
Копирование, шифрование, хеширование, проверка и durable файловые операции — |
||||
в media_async; отправка по 8 КиБ через call_soon. Тест 8 МиБ/0,5 с сохраняет |
||||
heartbeat uasync (максимальный интервал 10,3 мс в проверенном прогоне). |
||||
Qt/Android: оригинальные файловые вложения, сохранение и статусы доставки; |
||||
PM-снимки из ядра асинхронные. Чистые сборки Qt и Android APK прошли. |
||||
test_dm_media/test_dm_worker, rollback/quota/cancel и полный check: |
||||
115 PASS, 0 FAIL, 1 SKIP; proxy/burst/load PASS. GUI на устройствах не проверялся. |
||||
Протокол, владение и проверки: `doc/dm_arch.md`. |
||||
|
||||
[+] **DM: голосовые/видео и общий GUI вложений** — 01.10.2026: |
||||
тип FILE/VOICE/VIDEO и метаданные зашифрованы ключом target; в transport остаётся |
||||
подписанный descriptor, без новых ключей и канального протокола в DM. |
||||
Общие attachment/voice_file/attachment_send: Opus, waveform, копирование и видео |
||||
готовятся в workers; completion через reserved post вместо polling. PCM передаётся |
||||
без копии; адресат закреплён до записи/выбора. Отмена освобождает собственные файлы. |
||||
Android: ConversationComposer/ConversationMessages для каналов и PM, CameraX MP4, |
||||
IO для URI/метаданных; Qt: общий InputBar/delegate, FFmpeg, фоновые превью/Opus. |
||||
Подготовка адресована беседе и UUID задачи; статусы доставки показаны для всех типов. |
||||
test_attachment/test_attachment_send проверяют кодек, формат, владение и отмену; |
||||
test_dm_media — voice напрямую и encrypted video metadata после offline restart. |
||||
Чистые сборки ядра/Qt/APK, 117 PASS / 0 FAIL / 1 SKIP в check.sh; proxy/burst/load PASS. |
||||
Qt CTest: 7 PASS, включая повреждения Opus и отмену decoder. APK не устанавливался, |
||||
интерактивная проверка устройств не выполнялась. Подробности: `doc/dm_arch.md`. |
||||
|
||||
[ ] **uasync: ложные ERROR об утечке перед cleanup** — 01.10.2026: |
||||
test_dm_e2e и существующий test_conn_mgr проходят, но uasync_destroy() |
||||
пишет Timer leak до освобождения отменённых heap/immediate таймеров. |
||||
После cleanup счётчики сходятся, финальная проверка проходит. Перенести |
||||
предварительные счётчики в DEBUG; ERROR оставить для реального расхождения |
||||
после cleanup, отдельно показывать активные и отменённые таймеры. |
||||
|
||||
[+] **Статус подключения звонка в Qt и Android** — фактический путь router: |
||||
прямое / reverse / indirect / нет подключения. Учитывается BGP fallback; |
||||
события только при изменении, общий таймер 1с, сброс при завершении. |
||||
Проверены смена путей настоящего звонка, медиа, ASAN, Qt, Android APK и полный check. |
||||
|
||||
[+] **Единицы RTT в окне звонка** — `call_post_stats()` переводит |
||||
`ETCP_CONN.rtt_last` из 0.1мс в миллисекунды до отправки CALL_STATS. |
||||
Исправление общее для Qt и Android. DEBUG-категория `debug` раз в секунду |
||||
показывает исходный RTT, RTT в мс, транспортного соседа и метрики аудио. |
||||
|
||||
[+] **Восстановление путей conn_mgr на протяжении жизни handle** — начальный |
||||
TIMEOUT отделён от срока цикла проб. Поздний обрыв запускает новый цикл; |
||||
рабочий резерв и NCD сохраняются, при отсутствии резерва INDIRECT стартует сразу. |
||||
Циклы ограничены 15с, паузы 1→2→4→8→15с; новая версия узла или локальные сокеты |
||||
обходят паузу. Прямой READY останавливает поиск и получает приоритет. |
||||
Исходящие request_id и входящие сроки независимы; устаревшие ответы игнорируются. |
||||
UP/DOWN отражают общую доступность, TIMEOUT приходит один раз до первого UP. |
||||
Последний close отменяет восстановление, включая закрытие из callback. |
||||
Проверены десять сетевых сценариев с реальными данными: поздний обрыв, |
||||
повторные циклы, смена адресов/сокетов, потеря маршрута посредника, старый ACK, |
||||
начальный TIMEOUT с поздним UP и close. ASAN без ошибок; собраны Qt и Android APK. |
||||
Полный check: 113 PASS, 0 FAIL, 1 SKIP; пропущенный auto_socket_dynamic отдельно |
||||
прошёл с root в network namespace. Интеграционные proxy, burst и load прошли. |
||||
Алгоритм: `src/routing_layer/conn_mgr_doc.md`. |
||||
|
||||
[ ] **Общий лимит check.sh после clean** — 01.10.2026: расчёт по времени предыдущего |
||||
прогона дал 205с вместе с полной сборкой и оборвал проверку после успешных тестов |
||||
до `test_uasync_socket_race`. Повторный запуск на готовой сборке с большим лимитом |
||||
прошёл за 206с, включая proxy/burst/load. Разделить бюджет сборки и запуска тестов |
||||
либо учитывать clean при расчёте. |
||||
|
||||
[ ] **Паузы аудио Qt при звонке с SM** — 01.10.2026, звонок |
||||
`939a3ea36f3eeac3`, прямой LAN TCP `192.168.40.250:44604 ↔ 192.168.40.247:44173`. |
||||
Tcpdump: общие паузы входящего трафика до 645мс; ping до роутера в среднем |
||||
100мс, до SM 128мс. ПК на 2.4ГГц, SM на 5ГГц. Одновременно `CallAudio: cb` |
||||
падает до 20–40/с вместо 100/с, буфер Qt растёт до 2с, на SM повторяется refill. |
||||
В 8с strace аудиопоток продолжает обслуживать PulseAudio без долгих syscall; |
||||
причина редких аудиоколлбэков пока не установлена. Добавить измерения интервалов |
||||
и длительности callback, кадров capture/playback и сводку CALL_STATS; сравнить |
||||
сетевые задержки после подключения ПК к 5ГГц или кабелю. |
||||
Повторный звонок `b2a423ed2d36ed83` (01:25:31–01:27:39): DIRECT сохраняется, |
||||
RTT 2–3мс; в начале на SM gap tone длится 1.8–2.0с примерно каждые 6с. |
||||
На Qt частота callback падает вплоть до 4/с, входящий PCM продолжает приходить: |
||||
буфер 65→465→1465→2079мс за 3с, затем растёт до нескольких секунд, dropped=0. |
||||
Локализовано прекращение обслуживания duplex-аудио Qt; ожидание backend и |
||||
задержка обработки PCM пока не разделены. Часы SM отстают от Qt примерно на 1.24с. |
||||
Добавлена диагностика backend/устройств, времени между callback и длительности |
||||
capture/playback, фактической частоты callback и PCM кадров за интервал. |
||||
Счётчики сбрасываются до старта устройства; нужна повторная запись с новой сборкой. |
||||
|
||||
[+] **Ложный сигнал потери звука при запуске звонка** — 01.10.2026, звонок |
||||
`bc3efadcac629947`: Qt запускает аудиоколлбэки в 01:54:56.933, включает gap tone в |
||||
01:54:57.415 и выключает в 01:54:58.072 при первом воспроизведении PCM. |
||||
На SM между ACCEPT и первым захваченным кадром проходит около 1.15с; |
||||
часы SM отстают примерно на 1.23с. Qt стабильно обслуживает 100 callback/с, |
||||
RTT около 1мс. Исправлено: до первого PCM начальное ожидание проходит в тишине, |
||||
последующий перебой включает тон через 500мс. Стартовый сбой по-прежнему |
||||
ограничен существующим watchdog отсутствия медиа. Регрессия проверяет ожидание |
||||
1.5с, начальное накопление, настоящий перебой, неполный блок, новый звонок и завершение. |
||||
Ускорение запуска Android: router.start занимает около 775мс; система сообщает |
||||
длительное удержание AudioManager.updateAudioPortCache. Ещё около 370мс проходит |
||||
до запуска capture worker. Гудки теперь останавливаются до startAudio; |
||||
router использует снимок устройств с обновлением по событиям и явной переоценке, |
||||
capture запускается до подготовки/старта playback. Ошибка playback останавливает capture. |
||||
Добавлены длительности отдельных стадий, первого capture и PCM. Ядро, Qt и APK |
||||
собраны после clean; три аудиотеста прошли. Выигрыш на SM требует замера в новом звонке. |
||||
В звонке Qt `180f046ef22defc2` первый PCM пришёл через 1245мс без стартового gap tone. |
||||
Полный check вне песочницы: 113 PASS, 0 FAIL, 1 SKIP; auto_socket_dynamic отдельно |
||||
прошёл с root в private netns. Интеграционные proxy, burst и load прошли. |
||||
|
||||
[ ] **Оставшаяся задержка запуска аудио SM** — после обновления 01.10.2026, |
||||
звонок `2b9ad4e21a54d8ba`: стартового gap tone нет на обеих сторонах; Qt |
||||
стабильно работает около 100 callback/с, max_idle=15.3мс, буфер 55мс, RTT=2мс. |
||||
На SM первый capture появляется через 1140мс от startAudio (1164мс от ACCEPT). |
||||
router.start=781мс, получение снимка устройств=0мс, AudioRecord create=24мс, |
||||
startRecording=7мс; от начала startRecording до первого кадра=328мс. |
||||
Capture worker стартует раньше playback, но общий выигрыш пока не виден. |
||||
AudioTrack create=21мс, play=327мс; система сообщает AudioPortCache contention=497мс |
||||
и восстановление AudioTrack при setPreferredDevice. Разделить замеры регистрации |
||||
callback, BT-профиля и применения маршрута; отдельно проверить первый AudioRecord.read. |
||||
На SM входящий буфер за время подготовки вырос до 1149мс и к концу звонка снизился |
||||
до 570мс; ускорение старта playback также сократит начальное накопление. |
||||
Реализован независимый запуск AudioTrack и AudioRecord в play/capture потоках; |
||||
частичный старт отменяется до освобождения устройств/native. AEC измеряется после готовности обоих. |
||||
Добавлены callbackMs/btProfileMs/applyMs/refreshMs, длительность первого read и время первой записи playback. |
||||
Резерв теперь зависит от изменения задержки доставки по timestamps CALL_MEDIA, отдельно от глубины очереди; |
||||
стартовое накопление и догон не повышают запас. Сохранность FIFO и uint32 wrap проверяются регрессиями. |
||||
Сборки ядра, Qt и Android после clean успешны; три аудиотеста прошли. Полный check: |
||||
113 PASS, 0 FAIL, 1 SKIP (auto_socket_dynamic требует root); proxy/burst/load прошли. |
||||
Регрессия refill учитывает остаток SoundTouch: два новых кадра могут уже заполнить запас 60мс. |
||||
SM и Q8 обновлены 01.10.2026 в 03:03. Выигрыш по времени требует пробного звонка с новой версией. |
||||
|
||||
[ ] **Гонка invite-файла в test_chat_join_e2e** — `wait_file()` проверяет только |
||||
существование файла, а `wf()` создаёт его до записи. Читатель иногда получает |
||||
пустую ссылку и ошибку префикса `utun://`. Публиковать готовый файл атомарно. |
||||
|
||||
[ ] **Некорректное время в выводе check-proxy** — успешные `basic_1mb` и |
||||
`stress_sessions` иногда печатают огромные положительные/отрицательные миллисекунды, |
||||
хотя итоговая скорость и длительность передачи выглядят нормально. Проверить расчёт вывода. |
||||
|
||||
[+] **SpeexDSP AEC (эхоподавление) в lib/** — акустическое эхоподавление звонка. |
||||
Вендорен SpeexDSP (mdf/fftwrap/kiss_fft, 3-clause BSD) в `lib/speexdsp/`, |
||||
обёртка `lib/speex_aec.c/h` (C-API, FLOATING_POINT + USE_KISS_FFT, без внешних |
||||
зависимостей). Собственная линия задержки рендера на `delay_frames` кадров + |
||||
синхронный `speex_echo_cancellation` (встроенный буфер SpeexDSP фиксирован в |
||||
2 кадра и для Android не годится); дрейф потоков — через переполнение/недозаполнение |
||||
линии (drop/dup + passthrough). Категория лога `aec`. |
||||
Ядро: `call_audio_set_aec_enabled/delay_frames`, AEC в feed_pcm (захват→encode) |
||||
и pull_pcm (рендер-референс), lifecycle в start/release. Desktop — настройка GUI |
||||
`aec_enabled` (`[chatserver]`, читается в `call_audio_start`). Android — включается |
||||
только на спикерфоне (`route==SPEAKER`, live через JNI `callAudioSetAec`), |
||||
задержка меряется `AudioTrack/AudioRecord.getTimestamp`; `CallAudioEngine.playLoop` |
||||
переведён на absolute-deadline (устранён систематический дрейф). Тест |
||||
`test_speex_aec` (подавление эха 25.5 дБ, сохранность речи 0.956, delay-line, NULL-гарды). |
||||
Сборки: autotools + chatgui CMake + Android CMake/APK. |
||||
|
||||
[+] **Silero VAD в lib/** — добавлен детектор голосовой активности (Silero VAD v5, |
||||
стриминговая ONNX-модель + обёртка `lib/silero_vad.c/h` над ONNX Runtime C API). |
||||
API: `silero_vad_create/create_default/destroy/reset/process/process_pcm16` |
||||
(окно 512 сэмплов @16 кГц, вероятность речи [0..1], рекуррентное состояние). |
||||
Модель вшита в бинарник (`silero_vad_model.inc`, `create_default`), файл не нужен. |
||||
Категория лога `vad`. Модуль под `#ifdef HAVE_SILERO_VAD` — без onnxruntime |
||||
компилируется в стабы, поэтому GLOB-сборки (chatgui/Android) не ломаются. |
||||
Сборка: `--with-silero-vad` (autotools), `-DHAVE_SILERO_VAD` + libonnxruntime |
||||
(chatgui CMake, Android NDK). `make check`: 111/0/1. |
||||
|
||||
[+] **Авто-PTT рации по VAD** — сделано. Отдельный режим `radio_vad_enabled` (ручной |
||||
PTT не меняется, при зажатой кнопке — override). Сервисный слой `radio_audio.c`: |
||||
ресемплер 48k→16k моно (3-tap box + 3:1, стерео микшируется), накопитель окон |
||||
512, фильтр-гистерезис + машина состояний (`src/radio/radio_vad.h`, чистый, |
||||
юнит-тестируемый): старт при подтверждении 2 окон (~64мс) выше порога |
||||
`radio_vad_threshold`, стоп при тишине `radio_vad_hangover_ms` (200мс) или чужом |
||||
разговоре (busy = RX-кадр <200мс). История захвата (pre-roll) удалена; передаётся текущий PCM. |
||||
Компрессор в VAD-режиме байпасится. Desktop `radio_audio_engine.cpp` и Android |
||||
`RadioAudioEngine.kt` фидят PCM постоянно в VAD-режиме; JNI `radioVadMode()`. |
||||
Тест `test_radio_vad` (ресемплер + FSM). Android: onnxruntime AAR 1.23.2 в |
||||
`jniLibs/` + `cpp/onnxruntime/include` + `-DHAVE_SILERO_VAD` в CMake. |
||||
Android: настройки VAD (включение, порог, задержка после тишины); микрофон в PTT |
||||
создаётся только для передачи. PTT запускается при касании, после отпускания |
||||
передаёт ещё 250мс; повторное касание отменяет завершение (двойной тап без FIN). |
||||
VAD работает 15 минут при горящем экране; |
||||
SCREEN_OFF освобождает захват, SCREEN_ON автоматически открывает новое окно. |
||||
RX остаётся активен; TX освобождается отдельно через `radio_audio_capture_stop()`. |
||||
|
||||
[+] **chatgui (Qt) не собирался: C++ void* в utun_node.cpp** — сделано. Причина: |
||||
`proxy_protocol.h` (C-заголовок, включается в C++ `utun_node.cpp`) делал |
||||
`e->dgram = u_malloc(...)` и `struct proxy_flow* f = arg;` без явных приведений — |
||||
в C++ нет неявного `void*`→T*. Добавлены `(uint8_t*)`/`(struct proxy_flow*)` |
||||
(валидны и в C). `vibechat` собирается (47МБ, с вшитой моделью + onnxruntime). |
||||
|
||||
[+] **test_auto_socket_dynamic: root-регрессия и нагрузка** — изолированный namespace, |
||||
корректные маршруты, двусторонний поток через backpressure, строгие seq/payload, |
||||
пределы TX/RX, остановка при обрыве и drain после восстановления, баланс ресурсов. |
||||
Исправлены привязка TCP к интерфейсу, удаление TCP моделей/линков/портов из БД, |
||||
sender receive window при selective ACK, timestamp overflow и нулевой jitter. |
||||
ASan/UBSan/LSan: PASS; дополнительно исправлены невыровненные метки аллокаторов |
||||
и PID-порты в test_etcp_connect. [Разбор и логи](etcp_full_suite_findings.md). |
||||
|
||||
[ ] **debug_parse_config: формат и индексы категорий** — parser ожидает `:`, документация |
||||
описывает `=`; преобразование битовой маски осталось после перехода enum на индексы. |
||||
[Разбор](etcp_full_suite_findings.md). |
||||
|
||||
[+] **Q8/Android не подключается к локальному чатгуи (LAN)** — сделано. Причина: тип сокета |
||||
в Android-конфиге был захардкожен `type=public`, из-за чего `sock_match` ставил |
||||
`has_priv=0` и не создавал линки к приватным (LAN) адресам пиров (`create_links … → 0 links`). |
||||
Исправлено: (1) `ip_is_public()` вынесен в `lib/platform_compat` (единый классификатор |
||||
IPv4 с корректным ntohl); (2) в `auto_socket_reconcile_config` (Android-режим) тип сокета |
||||
выставляется по адресу (public→PUBLIC, приватный/link-local→NAT), конфиг `type` игнорируется; |
||||
(3) `sock_collect_views` больше не смотрит на `type` из конфига: `has_priv` — по классификации |
||||
локального адреса, `has_inet=1` (исходящие считаем доступными), `is_eim` — по NAT-детекции |
||||
(hairpin). `is_local_subnet` упрощён до `!ip_is_public()`. Тест `test_sock_match` обновлён |
||||
(+ `ip_is_public` кейсы). `check.sh`: 86 passed / 1 failed / 1 skipped. |
||||
|
||||
[ ] **DM: прямой p2p чат с любым пользователем группы** — отдельная DM-подсистема. |
||||
ТЗ и архитектура: `/doc/dm_arch.md`. Статус: реализовано (dm_core/dm_mailbox/dm_crypto), |
||||
тесты `test_dm` (крипто) и `test_dm_e2e` (интеграция). Остались задачи ниже. |
||||
|
||||
[ ] **Звонок (P2P аудио)** — голос 1:1, из CHAT-группы или p2p-чата (DM). Отдельный |
||||
каталог `src/call/`. Сигналинг + conn_mgr + аудиопоток (Opus) с backpressure |
||||
(локальный буфер отправки при переполнении роутера) + адаптивный джиттер-буфер |
||||
(SoundTouch, ускорение до 1.6x) + тоны (глитч/завершение) + watchdog 20с. |
||||
Android: переключение источника (трубка/громкая/гарнитура). |
||||
Архитектура: `/doc/chat_call_arch.md`. |
||||
|
||||
[+] **Сигналинг + релей медиа** — сделано. `call_proto.h` (svc `ETCP_RT_ID_CALL |
||||
0x36`, subcmd INVITE/RINGING/ACCEPT/DECLINE/BUSY/CANCEL/HANGUP/MEDIA), |
||||
`call.c` (машина состояний, реестр сессий, conn_mgr, ring-timeout 45с, |
||||
watchdog no-traffic 20с, потеря соединения → end, события |
||||
CHAT_EVT_CALL_* 34–40), `DEBUG_CATEGORY_CALL 32`. `test_call` (2 инстанса, |
||||
happy path + decline + media-релей). Приём медиа — через `call_set_media_recv_cb`. |
||||
[+] **Headless API** — сделано. Команды control-сокета `call_start/accept/decline/ |
||||
hangup` + события `call_incoming/ringing/accepted/ended/declined` (JSON); |
||||
аудио-сокет `call/call_headless.c` (TCP, бинарный фрейм |
||||
`[type:1][call_id:8][len:2][data]`, HELLO/FRAME), конфиг `call_audio_bind`. |
||||
Разрыв conn_mgr — отложенно 500мс (`CALL_TEARDOWN_DELAY_TB`) после ENDED, |
||||
чтобы HANGUP успел долететь; защита от stale-handle через conn-status. |
||||
Тесты: `test_call` (сигналинг+медиа), `test_call_headless` (полный e2e через |
||||
control+audio сокеты). |
||||
[+] **Аудио-контур (time-stretch в сервисном слое)** — сделано. `src/call/audio_jitter.cpp` |
||||
(SoundTouch, C API `ajb_*`: адаптивный джиттер-буфер ≤1000мс, target 60мс, |
||||
tempo 1.0→1.6x — догон live без потери данных при сетевой паузе), |
||||
`src/call/call_tones.c` (глитч при сталле >500мс + тон завершения), |
||||
`src/call/call_audio.c` (Opus 48kHz/20мс + jitter + тоны + pull API финального PCM). |
||||
`call.c`: `call_audio_ops` (on_media c seq/ts, get_stats) + событие |
||||
`CHAT_EVT_CALL_STATS` раз в 1с [rtt_ms, buffer_ms, tempo_x100]. |
||||
Desktop `call_audio_engine.cpp` — только miniaudio I/O (получает финальный PCM). |
||||
Тест `test_audio_jitter` (ёмкость/догон/стационар). Демон остаётся C-only. |
||||
[+] **Android** — сделано. JNI (`utun_bridge_call_*`/`nativeCall*`), AudioRecord/Track |
||||
(CallAudioEngine), роутинг источника (CallAudioRouter: EARPIECE/SPEAKER/HEADSET, |
||||
BT SCO), CallScreen + IncomingCallDialog + RingtonePlayer + IncomingCallManager, |
||||
`libutun_lite/call_audio.c` (собственный аудио-контур Opus 48kHz/20мс). |
||||
[+] **Единый голосовой стек (voice stack) + SoundTouch на Android** — сделано. |
||||
Унификация аудио-контура звонка: `libutun_voice` (статическая C++-либа с |
||||
C-интерфейсом `call_audio.h` = SoundTouch + `voice_jitter.cpp` + `call_audio.c` |
||||
+ `call_tones.c`), собирается через `src/call/voice_sources.cmake` и линкуется |
||||
desktop/Android/headless-CLI. Джиттер-буфер держит КОДИРОВАННЫЕ (Opus) кадры, |
||||
декодирует только на pull (непосредственно перед воспроизведением) — память ~48x |
||||
меньше и uasync-поток разгружен. Android переведён с push-FIFO (Kotlin ArrayDeque) |
||||
на pull-модель (`nativeCallAudioPull`), своего джиттера больше нет → убрана |
||||
асимметрия задержки (desktop↔Android). Headless-аудио-сокет отдаёт готовый PCM |
||||
(декод Opus→PCM, без тактирования; клиент сам задаёт темп). Диагностика: |
||||
`CALL_STATS` расширен [dropped, underruns] (раз в 1с), счётчики headless. |
||||
Удалены `audio_jitter.cpp` и `libutun_lite/call_audio.c`. Тест `test_voice_jitter` |
||||
(ёмкость/догон/стационар). Android — `ANDROID_STL=c++_static`. |
||||
[ ] **DM-звонки** (fallback `group_id==0`) — не сделано: `call_is_online`/`call_start` |
||||
требуют реальную группу (`topo_groups_find`), для DM (group_id==0) маршрута нет. |
||||
|
||||
## Звонок: две фазы исходящего вызова (индикация + гудки) |
||||
|
||||
[+] **Две фазы вызова** — сделано. Исходящий звонок разбит на 2 фазы с индикацией |
||||
в окне вызова и разными гудками: |
||||
- фаза 1 «Пытаемся связаться…» (`OUTGOING` — INVITE/маршрут) — гудок «соединение»; |
||||
- фаза 2 «Звоним…» (`RINGING` — у пира идёт вызов) — длинные гудки (ringback). |
||||
Desktop: `CallWindow::Ringing` + `SoundManager` loop `call_connecting`/`call_ringing` |
||||
(MP3 в qrc), переключение по `onCallRinging`, остановка по accepted/ended/error; |
||||
добавлена обработка `GUI_EVT_CALL_ERROR` (40) — раньше при «пир офлайн» окно/гудок |
||||
зависали навсегда. Android: `CallRingback` (MediaPlayer loop из res/raw), |
||||
подписи `OUTGOING`/`RINGING` в CallScreen + ActiveCallBanner. |
||||
Звуковые события добавлены в SoundSettingsPage (call_connecting/call_ringing). |
||||
|
||||
[+] **Надёжность отмены вызова (кодограмма + watchdog)** — сделано. Отмена вызова |
||||
(HANGUP/CANCEL) шлётся один раз, без ретрансмита, и при флапе маршрута (RST/epoch, |
||||
прямой TCP к мобиле не поднимается — только релей) терялась: вызываемый висел в |
||||
RINGING вечно и отвечал BUSY на все новые INVITE. Исправлено моделью «сердцебиения»: |
||||
- вызывающий шлёт кодограмму INVITE каждые 300мс весь период вызова (OUTGOING+RINGING); |
||||
- у вызываемого watchdog 2с — нет кодограммы → вызов сорван (`CALL_REASON_CANCEL`); |
||||
- завершающие кодограммы без ретрансмиссий (потерю покрывает watchdog); |
||||
- запоздалые кодограммы вызова при разговоре (CONNECTING/ACTIVE) игнорируются. |
||||
`CALL_INVITE_RETRY_TB=3000`, `CALL_RING_DEAD_TB=20000`, `CALL_NO_ROUTE_MAX_STRIKES=20`. |
||||
|
||||
## Рефакторинг: per-instance chat/dm (однопоточный test_dm_e2e) |
||||
|
||||
Сделано (глобальные синглтоны переведены на `UTUN_INSTANCE`): |
||||
- `chat_setting` → `inst->chat_settings` (`struct chat_setting_state`, state-level API для парсера конфига + instance-level для runtime). |
||||
- `chat_event` → `inst->chat_event_handler` (обработчик теперь получает `inst`). |
||||
- `chat_core` (`g_cc`) → `inst->chat_core` (`struct chat_core_ctx*`). |
||||
- `chat_sync` (`g_cs`) → `inst->chat_sync`. |
||||
- `member_sync` callback-списки (`g_props_cbks`/`g_apply_cbks`) → в `chat_core_ctx`. |
||||
- `chat_join` (`g_keys`) → в `chat_core_ctx`. |
||||
- `chat_msg` media-счётчики/backfill → в `chat_core_ctx`. |
||||
- `dm_core` (`g_dm`) → `inst->dm`; `dm_mailbox` (`g_mb`) → `inst->dm_mailbox`. |
||||
- `chat_headless_control` (`g_hc`) → `inst->headless`. |
||||
- `chat_whisper` — сигнатуры `init/available/trigger` принимают `inst`. |
||||
- API протащено через `inst` (chat_core_*/dm_*), обновлены call-сайты в src/ (headless, topo, media_delivery, auto_socket, config_parser). |
||||
- `test_dm_e2e.c` переписан на однопоточный: один `uasync`, три `utun_instance` (A,B,C), master state-machine, без fork. Фаза 2 (B offline) — `utun_instance_destroy(B1)` + пересоздание B2. |
||||
- Попутно исправлен 1-байтовый overflow в `dm_mailbox.c: mb_route_send` (`u_malloc(1+body_len)` → `1+1+body_len`). |
||||
|
||||
Статус сборки: `make -C src` (libutun.a + бинарник utun) и `make -C tests` — OK. |
||||
|
||||
### Осталось (по порядку) |
||||
|
||||
[+] **test_dm_e2e: зависание в utun_instance_destroy(B1)** (фаза P_B_OFFLINE) — сделано. |
||||
Причин было несколько (не одна): |
||||
1. `lib/u_async.c`: если ближайший таймер уже истёк на входе в `uasync_poll`, |
||||
`get_next_timeout` возвращал {0,0}, `timeout_ms=-1` → `epoll_wait(-1)` блокировался |
||||
навсегда, а `process_timeouts` (вызывается только после epoll_wait) не запускался. |
||||
Фикс: `if (timeout_ms == -1 && heap не пуст) timeout_ms = 0`. |
||||
2. `routing_layer/etcp_router.c`: `router_send_conn` с group_id=0 (глобальная |
||||
маршрутизация DM/mailbox) не находил маршрут (`topo_groups_find(0)==NULL`). |
||||
Фикс: fallback на `instance_find_conn(inst, remote)` при group==NULL. |
||||
3. `dm/dm_core.c`: `dm_on_conn_status` PULL-ил только беседы с подключившимся пиром; |
||||
B2 подключается к storage (C), а беседа — с A. Фикс: на подъём любого соединения |
||||
PULL для всех бесед, чей пир не подключён напрямую. |
||||
4. `dm/dm_mailbox.c`: не было ACK после PULL_RESP → storage не чистил dm_mail. |
||||
Фикс: PULL_RESP теперь несёт sender, получатель шлёт MB_SUBCMD_ACK. |
||||
5. `utun_instance.c`: `dm_core_init` ставил deliver-cb до `dm_mailbox_init` → |
||||
mailbox не находился, deliver-cb не регистрировался. Фикс: mailbox init раньше core. |
||||
`test_dm_e2e` проходит (несколько прогонов), `test_dm` тоже. |
||||
|
||||
[+] **chatgui-android: переезд на per-instance API** — сделано. |
||||
`instance_lite.c/h` (+`instance_lite_get_instance()`), `jni_bridge.c`, `standby.c`, |
||||
`headless_control.c`, `voice_recorder.c`, `attachment_sender.c`, `video_sender.c`, |
||||
`photo_sender.c` — все call-сайты переведены на `inst`. `libutun_lite` компилируется, |
||||
`jni_bridge.c` — синтакс-чисто. (headless-линковка сломана ПРЕДЫДУЩЕ — instance_lite.c |
||||
ссылается на jni_bridge-функции, не включённые в headless-сборку; отдельная задача.) |
||||
|
||||
[+] **test_chat_join: segfault** (регресс рефакторинга) — сделано. |
||||
`chat_join` хранил `join_keys` в `chat_core_ctx` (CC(inst)), а тест использует chat_join |
||||
без chat_core → NULL-deref. Фикс: `join_keys` вынесен в `UTUN_INSTANCE.join_keys` |
||||
(у chat_join собственный init/destroy). |
||||
|
||||
[ ] **BGP-гонка: дропнутый REQUEST_TABLE** (отдельная задача, как договорились). |
||||
|
||||
**Место:** `topo_group_new_conn()` в `src/routing_layer/topo_group.c:571`. Дедуп-ветка |
||||
«conn already in senders_list» (строки 579–587) делает `return` БЕЗ повторной отправки |
||||
`topo_group_send_table_request()`. |
||||
|
||||
**Зачем дедуп:** тот же `ETCP_CONN` стреляет `ETCP_CONN_STATUS_UP` дважды (UDP-линк, |
||||
затем TCP-линк). Повторная обработка задваивает `active_conn_count`, из-за чего |
||||
переподключение не стартует. Поэтому `return` в дедупе — правильный. |
||||
|
||||
**В чём гонка:** REQUEST_TABLE — это то, что заставляет пира отдать свою таблицу |
||||
(NODEINFO/узлы группы). Сценарий «разнесённого старта»: |
||||
1. A↔C соединение поднято. A шлёт C `REQUEST_TABLE` для группы G (`topo_group_new_conn` |
||||
→ `topo_group_send_table_request`, `topo_group.c:602`). |
||||
2. У C группа G ещё **не создана** (создаётся позже, по мере загрузки каналов/синка). |
||||
C получает `REQUEST_TABLE`, но `topo_group_handle_request_table` не находит G → |
||||
дропает запрос. |
||||
3. C наконец создаёт G (`topo_groups_create_group` → обход connections → |
||||
`topo_group_new_conn(G, conn_A)` на стороне C) → C шлёт A свой `REQUEST_TABLE`. |
||||
A отвечает своей таблицей → **C узнаёт A**. |
||||
4. Но A больше не шлёт C `REQUEST_TABLE` → **A так и не узнаёт C** в группе G. |
||||
Итог: при разнесённом старте A не видит C в CHAT-группе (односторонняя видимость). |
||||
|
||||
**Фикс (предполагаемый):** в дедуп-ветке не просто `return`, а предварительно |
||||
идемпотентно `topo_group_send_table_request(group, conn)`: |
||||
```c |
||||
if (((struct TOPO_GROUP_CONN_ITEM*)se->data)->conn == conn) { |
||||
topo_group_send_table_request(group, conn); /* повторно, идемпотентно */ |
||||
DEBUG_INFO(..., "conn already in senders_list, re-request table (%s)", conn->log_name); |
||||
return; |
||||
} |
||||
``` |
||||
`topo_group_send_table_request` не ведёт состояния — просто шлёт REQUEST_TABLE, |
||||
пир отвечает снимком таблицы (идемпотентно). Лишний дубль таблицы безвреден. |
||||
|
||||
**Замечание:** в однопоточном `test_dm_e2e` (и `test_chat_join_e2e`) из-за |
||||
детерминированного порядка группа успевает создаться до прихода REQUEST_TABLE — |
||||
поэтому тест проходит. Реальная гонка остаётся для асинхронных сетей. Отдельный |
||||
воспроизводимый тест для этой гонки пока не написан. |
||||
|
||||
[ ] **«packet undecryptable» на линках C** (вторичное). A, узнав B через BGP, через |
||||
`topo_group_connect` пытается поднять прямой A↔B линк (`seskey 4777737b`), но B не |
||||
настроен принимать A → crypto-шум каждые ~1с. Разобраться с авто-подключением |
||||
(не пытаться соединяться с узлами, для которых нет `[client]`/ncd-конфигурации, |
||||
либо обрабатывать неудачу без спама). |
||||
|
||||
[+] **Обновить GUI call-сайты `tools/chatgui` (Qt, desktop)** — сделано. |
||||
- В `chat_core.h` вынесены trampoline-структуры из `.c`: `update_my_name_arg`, |
||||
`save_ui_state_arg`, `chat_setting_arg` (были в chat_profile.c/chat_core.c). |
||||
- `gui_bridge.h/impl`: `gui_bridge_set_inst()`/`gui_bridge_get_inst()` (+`g_inst`). |
||||
- `utun_node.cpp`: `gui_bridge_set_inst(m_instance)`, `chat_event_set_handler(m_instance,…)`, |
||||
`chat_core_sync_my_addresses(m_instance)`. |
||||
- Обновлены ~45 call-сайтов в 16 файлах (`mainwindow`, `messagelist`, `messagedelegate`, |
||||
`memberlistmodel`, `memberpropsdialog`, `accountlist`, `invitedialog`, `inviteby`, |
||||
`joindialog`, `settingsdialog`, `channelsettingsdialog`, `soundsettingspage`, |
||||
`audiodevicesettingspage`, `storagesettingspage`, `statuspage`, `connmonitorwindow`). |
||||
- Сборка `cmake --build . --target vibechat` — OK. |
||||
Попутно: `chat_sync_connect_from_invite` писал `cs->pending_*` на вызывающем (GUI) потоке — |
||||
перенёс в `cm_invite_trampoline` (uasync-поток), убрав гонку; заодно исправил утечку |
||||
`inv->addrs_data` и добавил null-check `cs` в трамплине. |
||||
|
||||
[+] **Прогон всех тестов** — сделано. `make clean && make -j4` OK; все тесты проходят |
||||
(кроме `test_auto_socket_dynamic` — пропуск «requires root»). `test_dm_e2e` стабилен |
||||
в нескольких прогонах. В `test_dm_e2e` при teardown остаётся timer-leak (~68 узлов, |
||||
`router_no_route`), не влияет на результат — стоит разобрать отдельно. |
||||
|
||||
[ ] **lwip/proxy: утечка pcb в FIN_WAIT_2** — `tcp_shutdown(pcb,0,1)` (half-close по FIN от |
||||
exit) не ставит `TF_RXCLOSED`, поэтому таймаут FIN_WAIT_2 в `tcp_slowtmr` не срабатывает: |
||||
если браузер никогда не пришлёт FIN, pcb навсегда остаётся в FIN_WAIT_2 (утечка, не crash). |
||||
Двойной free по TIME_WAIT уже закрыт (detach по локальному закрытию + guard abort). |
||||
|
||||
## Открытые флаки |
||||
[+] **test_chat_join_e2e** — сделано. Причина: ложный критерий успеха джойнера J. |
||||
`wait_group_started()` проверял `senders_list` непуст в предположении «senders_list |
||||
непуст ⟺ J получил JOIN_READY». После коммита b483335c `tgi_to_channel_cb` |
||||
(topo_group_invite.c) при `NCD_EVENT_UP` стал вызывать `topo_group_new_conn` → |
||||
conn попадает в `senders_list` сразу после установки TCP/UDP-соединения, ДО |
||||
завершения хендшейка (JOIN_INFO_REQ→RESP→REQUEST→READY). J выходил «OK» раньше, |
||||
чем A получал JOIN_REQUEST → A не подписывал J → `wait_signed` на A/C падал |
||||
(`get_sign_rc=-1`). Фикс: критерий J — событие `CHAT_EVT_CONNECT_RESULT(result=0)` |
||||
(JOIN_READY), ловится через `chat_event_set_handler`; `wait_group_started` удалён. |
||||
Это также убрало гонку «C вышел раньше, чем J синкает свой рекорд» (C выходит сразу |
||||
после `wait_signed`, не дожидаясь merkle-синка J). 5 прогонов стабильно 3/3. |
||||
|
||||
[ ] **test_etcp_reconnect** — флаки под параллельной нагрузкой `make check -j4`: |
||||
phase 4 (reconnect после server restart) таймаутит (`sent=480 recv=0`), при одиночном |
||||
запуске стабильно `TEST PASSED`. Наш код (chat_join/chat_sync) его не трогает. |
||||
@ -1,301 +0,0 @@
|
||||
#include "silero_vad.h" |
||||
#include "debug_config.h" |
||||
#include "mem.h" |
||||
|
||||
#ifdef HAVE_SILERO_VAD |
||||
|
||||
#include <stdio.h> |
||||
#include <string.h> |
||||
|
||||
#include <onnxruntime/onnxruntime_c_api.h> |
||||
|
||||
#include "silero_vad_model.inc" |
||||
|
||||
/* Число float-элементов рекуррентного состояния модели: 2 * 1 * 128 */ |
||||
#define SILERO_VAD_STATE_COUNT 256 |
||||
|
||||
struct silero_vad { |
||||
const OrtApi* ort; |
||||
OrtEnv* env; |
||||
OrtSession* session; |
||||
OrtMemoryInfo* mem_info; |
||||
float state[SILERO_VAD_STATE_COUNT]; /* рекуррентное состояние между вызовами */ |
||||
uint8_t* model_data; /* байты .onnx, загруженные в память (для file-версии) */ |
||||
size_t model_len; |
||||
}; |
||||
|
||||
/* Освободить OrtStatus и залогировать его сообщение как ошибку */ |
||||
static void vad_report_ort_error(const OrtApi* ort, OrtStatus* status, const char* what) { |
||||
const char* msg = ort->GetErrorMessage(status); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "%s: %s", what, msg ? msg : "unknown onnxruntime error"); |
||||
ort->ReleaseStatus(status); |
||||
} |
||||
|
||||
/* Прочитать файл целиком в буфер. 0 при успехе: буфер пишется в out/out_len (u_malloc). */ |
||||
static int vad_read_file(const char* path, uint8_t** out, size_t* out_len) { |
||||
FILE* f; |
||||
long size; |
||||
uint8_t* buf; |
||||
|
||||
*out = NULL; |
||||
*out_len = 0; |
||||
|
||||
f = fopen(path, "rb"); |
||||
if (!f) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "vad_read_file: cannot open model '%s'", path); |
||||
return -1; |
||||
} |
||||
if (fseek(f, 0, SEEK_END) != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "vad_read_file: fseek failed on '%s'", path); |
||||
fclose(f); |
||||
return -1; |
||||
} |
||||
size = ftell(f); |
||||
if (size <= 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "vad_read_file: empty or unreadable model '%s'", path); |
||||
fclose(f); |
||||
return -1; |
||||
} |
||||
rewind(f); |
||||
|
||||
buf = (uint8_t*)u_malloc((size_t)size); |
||||
if (!buf) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "vad_read_file: OOM for %ld bytes", size); |
||||
fclose(f); |
||||
return -1; |
||||
} |
||||
if (fread(buf, 1, (size_t)size, f) != (size_t)size) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "vad_read_file: short read on '%s'", path); |
||||
u_free(buf); |
||||
fclose(f); |
||||
return -1; |
||||
} |
||||
fclose(f); |
||||
|
||||
*out = buf; |
||||
*out_len = (size_t)size; |
||||
return 0; |
||||
} |
||||
|
||||
/* Общая инициализация детектора из байтов модели (файл или вшитая модель).
|
||||
* data может указывать на внешний буфер (вшитая модель) — тогда не освобождается. */ |
||||
static silero_vad_t* silero_vad_create_from_bytes(const uint8_t* data, size_t len, |
||||
const uint8_t* owned_buf, const char* label) { |
||||
silero_vad_t* vad = NULL; |
||||
const OrtApi* ort; |
||||
OrtStatus* status; |
||||
OrtSessionOptions* opts = NULL; |
||||
|
||||
if (!data || len == 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_create(%s): empty model", label); |
||||
return NULL; |
||||
} |
||||
|
||||
ort = OrtGetApiBase() ? OrtGetApiBase()->GetApi(ORT_API_VERSION) : NULL; |
||||
if (!ort) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_create(%s): onnxruntime C API unavailable", label); |
||||
return NULL; |
||||
} |
||||
|
||||
vad = (silero_vad_t*)u_calloc(1, sizeof(*vad)); |
||||
if (!vad) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_create(%s): OOM", label); |
||||
return NULL; |
||||
} |
||||
vad->ort = ort; |
||||
vad->model_data = (uint8_t*)owned_buf; |
||||
vad->model_len = len; |
||||
|
||||
status = ort->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "utun_silero_vad", &vad->env); |
||||
if (status) { vad_report_ort_error(ort, status, "CreateEnv"); goto fail; } |
||||
|
||||
status = ort->CreateSessionOptions(&opts); |
||||
if (status) { vad_report_ort_error(ort, status, "CreateSessionOptions"); goto fail; } |
||||
status = ort->SetIntraOpNumThreads(opts, 1); |
||||
if (status) { vad_report_ort_error(ort, status, "SetIntraOpNumThreads"); } |
||||
status = ort->SetSessionGraphOptimizationLevel(opts, ORT_ENABLE_ALL); |
||||
if (status) { vad_report_ort_error(ort, status, "SetSessionGraphOptimizationLevel"); } |
||||
|
||||
status = ort->CreateSessionFromArray(vad->env, data, len, opts, &vad->session); |
||||
if (status) { vad_report_ort_error(ort, status, "CreateSessionFromArray"); goto fail; } |
||||
|
||||
status = ort->CreateCpuMemoryInfo(OrtArenaAllocator, OrtMemTypeDefault, &vad->mem_info); |
||||
if (status) { vad_report_ort_error(ort, status, "CreateCpuMemoryInfo"); goto fail; } |
||||
|
||||
ort->ReleaseSessionOptions(opts); |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_VAD, "silero_vad_create(%s): ok bytes=%zu", label, len); |
||||
return vad; |
||||
|
||||
fail: |
||||
if (opts) ort->ReleaseSessionOptions(opts); |
||||
silero_vad_destroy(vad); |
||||
return NULL; |
||||
} |
||||
|
||||
silero_vad_t* silero_vad_create(const char* model_path) { |
||||
uint8_t* buf = NULL; |
||||
size_t len = 0; |
||||
silero_vad_t* vad; |
||||
|
||||
if (!model_path || !*model_path) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_create: empty model path"); |
||||
return NULL; |
||||
} |
||||
if (vad_read_file(model_path, &buf, &len) != 0) return NULL; |
||||
vad = silero_vad_create_from_bytes(buf, len, buf, model_path); |
||||
if (!vad) u_free(buf); |
||||
return vad; |
||||
} |
||||
|
||||
silero_vad_t* silero_vad_create_default(void) { |
||||
return silero_vad_create_from_bytes(silero_vad_model_data, silero_vad_model_len, NULL, "embedded"); |
||||
} |
||||
|
||||
void silero_vad_destroy(silero_vad_t* vad) { |
||||
if (!vad) return; |
||||
const OrtApi* ort = vad->ort; |
||||
if (ort) { |
||||
if (vad->mem_info) ort->ReleaseMemoryInfo(vad->mem_info); |
||||
if (vad->session) ort->ReleaseSession(vad->session); |
||||
if (vad->env) ort->ReleaseEnv(vad->env); |
||||
} |
||||
if (vad->model_data) u_free(vad->model_data); |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_VAD, "silero_vad_destroy"); |
||||
u_free(vad); |
||||
} |
||||
|
||||
void silero_vad_reset(silero_vad_t* vad) { |
||||
if (!vad) return; |
||||
memset(vad->state, 0, sizeof(vad->state)); |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_VAD, "silero_vad_reset"); |
||||
} |
||||
|
||||
int silero_vad_process(silero_vad_t* vad, const float* samples, float* prob) { |
||||
const OrtApi* ort; |
||||
OrtStatus* status; |
||||
OrtValue* in_input = NULL; |
||||
OrtValue* in_state = NULL; |
||||
OrtValue* in_sr = NULL; |
||||
OrtValue* out_prob = NULL; |
||||
OrtValue* out_state = NULL; |
||||
const char* input_names[3]; |
||||
const char* output_names[2]; |
||||
const OrtValue* inputs[3]; |
||||
OrtValue* outputs[2]; |
||||
int64_t shape_input[2] = {1, SILERO_VAD_WINDOW_SAMPLES}; |
||||
int64_t shape_state[3] = {2, 1, 128}; |
||||
int64_t shape_prob[2] = {1, 1}; |
||||
int64_t sr_value = SILERO_VAD_SAMPLE_RATE; |
||||
float prob_local = 0.0f; |
||||
float next_state[SILERO_VAD_STATE_COUNT]; |
||||
int rc = -1; |
||||
|
||||
if (!vad || !samples || !prob) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_process: invalid args"); |
||||
return -1; |
||||
} |
||||
ort = vad->ort; |
||||
|
||||
status = ort->CreateTensorWithDataAsOrtValue(vad->mem_info, (void*)samples, |
||||
SILERO_VAD_WINDOW_SAMPLES * sizeof(float), shape_input, 2, |
||||
ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, &in_input); |
||||
if (status) { vad_report_ort_error(ort, status, "input tensor"); return -1; } |
||||
|
||||
status = ort->CreateTensorWithDataAsOrtValue(vad->mem_info, vad->state, |
||||
SILERO_VAD_STATE_COUNT * sizeof(float), shape_state, 3, |
||||
ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, &in_state); |
||||
if (status) { vad_report_ort_error(ort, status, "state tensor"); goto done; } |
||||
|
||||
status = ort->CreateTensorWithDataAsOrtValue(vad->mem_info, &sr_value, |
||||
sizeof(sr_value), NULL, 0, |
||||
ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64, &in_sr); |
||||
if (status) { vad_report_ort_error(ort, status, "sr tensor"); goto done; } |
||||
|
||||
status = ort->CreateTensorWithDataAsOrtValue(vad->mem_info, &prob_local, |
||||
sizeof(float), shape_prob, 2, |
||||
ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, &out_prob); |
||||
if (status) { vad_report_ort_error(ort, status, "output tensor"); goto done; } |
||||
|
||||
status = ort->CreateTensorWithDataAsOrtValue(vad->mem_info, next_state, |
||||
SILERO_VAD_STATE_COUNT * sizeof(float), shape_state, 3, |
||||
ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, &out_state); |
||||
if (status) { vad_report_ort_error(ort, status, "stateN tensor"); goto done; } |
||||
|
||||
input_names[0] = "input"; |
||||
input_names[1] = "state"; |
||||
input_names[2] = "sr"; |
||||
inputs[0] = in_input; |
||||
inputs[1] = in_state; |
||||
inputs[2] = in_sr; |
||||
|
||||
output_names[0] = "output"; |
||||
output_names[1] = "stateN"; |
||||
outputs[0] = out_prob; |
||||
outputs[1] = out_state; |
||||
|
||||
status = ort->Run(vad->session, NULL, input_names, inputs, 3, output_names, 2, outputs); |
||||
if (status) { |
||||
vad_report_ort_error(ort, status, "Run"); |
||||
goto done; |
||||
} |
||||
|
||||
memcpy(vad->state, next_state, sizeof(next_state)); |
||||
*prob = prob_local; |
||||
rc = 0; |
||||
|
||||
done: |
||||
if (in_input) ort->ReleaseValue(in_input); |
||||
if (in_state) ort->ReleaseValue(in_state); |
||||
if (in_sr) ort->ReleaseValue(in_sr); |
||||
if (out_prob) ort->ReleaseValue(out_prob); |
||||
if (out_state) ort->ReleaseValue(out_state); |
||||
return rc; |
||||
} |
||||
|
||||
int silero_vad_process_pcm16(silero_vad_t* vad, const int16_t* pcm, float* prob) { |
||||
float samples[SILERO_VAD_WINDOW_SAMPLES]; |
||||
int i; |
||||
|
||||
if (!pcm) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_process_pcm16: pcm == NULL"); |
||||
return -1; |
||||
} |
||||
for (i = 0; i < SILERO_VAD_WINDOW_SAMPLES; i++) { |
||||
samples[i] = (float)pcm[i] / 32768.0f; |
||||
} |
||||
return silero_vad_process(vad, samples, prob); |
||||
} |
||||
|
||||
#else /* !HAVE_SILERO_VAD — пустые стабы (сборки без onnxruntime) */ |
||||
|
||||
silero_vad_t* silero_vad_create(const char* model_path) { |
||||
(void)model_path; |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_create: built without onnxruntime (HAVE_SILERO_VAD not defined)"); |
||||
return NULL; |
||||
} |
||||
|
||||
silero_vad_t* silero_vad_create_default(void) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_VAD, "silero_vad_create_default: built without onnxruntime (HAVE_SILERO_VAD not defined)"); |
||||
return NULL; |
||||
} |
||||
|
||||
void silero_vad_destroy(silero_vad_t* vad) { |
||||
(void)vad; |
||||
} |
||||
|
||||
void silero_vad_reset(silero_vad_t* vad) { |
||||
(void)vad; |
||||
} |
||||
|
||||
int silero_vad_process(silero_vad_t* vad, const float* samples, float* prob) { |
||||
(void)vad; (void)samples; (void)prob; |
||||
return -1; |
||||
} |
||||
|
||||
int silero_vad_process_pcm16(silero_vad_t* vad, const int16_t* pcm, float* prob) { |
||||
(void)vad; (void)pcm; (void)prob; |
||||
return -1; |
||||
} |
||||
|
||||
#endif /* HAVE_SILERO_VAD */ |
||||
@ -1,76 +0,0 @@
|
||||
/**
|
||||
* Silero VAD (Voice Activity Detector) — C-обёртка над ONNX Runtime. |
||||
* |
||||
* Запускает официальную стриминговую модель silero_vad.onnx (v5, ~2.3 МБ), |
||||
* которая по окну аудио возвращает вероятность наличия речи [0..1]. |
||||
* Рекуррентное состояние (GRU) хранится внутри объекта и переносится между |
||||
* вызовами, поэтому детектор работает в потоковом (real-time) режиме. |
||||
* |
||||
* Требования к входному аудио: |
||||
* - частота дискретизации 16 кГц (модель поддерживает только 16k); |
||||
* - моно; |
||||
* - окно ровно SILERO_VAD_WINDOW_SAMPLES = 512 сэмплов (32 мс); |
||||
* - float-сэмплы в диапазоне примерно [-1;1] (для int16 есть |
||||
* silero_vad_process_pcm16, который сам делит на 32768). |
||||
* |
||||
* Порог и гистерезис (задержки начала/конца речи) намеренно оставлены |
||||
* вызывающему — радио/звонок сами решают, как интерпретировать вероятность. |
||||
* |
||||
* Зависимость: libonnxruntime (C API). Сборка включается опцией |
||||
* `--with-silero-vad` (см. configure.ac) или макросом HAVE_SILERO_VAD в |
||||
* CMake-сборках. Без HAVE_SILERO_VAD модуль компилируется в пустые стабы |
||||
* (все create-функции возвращают NULL, process — ошибку), чтобы исходники lib/ |
||||
* можно было GLOB-ить во всех сборках (Android/chatgui) без onnxruntime. |
||||
* |
||||
* Лицензия модели: MIT (Silero Team, https://github.com/snakers4/silero-vad).
|
||||
*/ |
||||
#ifndef SILERO_VAD_H |
||||
#define SILERO_VAD_H |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/* Фиксированное окно стриминговой модели: 512 сэмплов @ 16 кГц = 32 мс */ |
||||
#define SILERO_VAD_WINDOW_SAMPLES 512 |
||||
#define SILERO_VAD_SAMPLE_RATE 16000 |
||||
|
||||
typedef struct silero_vad silero_vad_t; |
||||
|
||||
/**
|
||||
* Создать детектор, загрузив ONNX-модель из файла model_path. |
||||
* Возвращает NULL при ошибке (подробности — в лог категории "vad"). |
||||
*/ |
||||
silero_vad_t* silero_vad_create(const char* model_path); |
||||
|
||||
/**
|
||||
* Создать детектор из модели, вшитой в бинарник (silero_vad_model_data в |
||||
* silero_vad.c). Не требует файла на диске. NULL при ошибке. |
||||
*/ |
||||
silero_vad_t* silero_vad_create_default(void); |
||||
|
||||
/* Освободить детектор. NULL безопасен. */ |
||||
void silero_vad_destroy(silero_vad_t* vad); |
||||
|
||||
/* Сбросить рекуррентное состояние (начало нового аудиопотока / после длительной тишины). */ |
||||
void silero_vad_reset(silero_vad_t* vad); |
||||
|
||||
/**
|
||||
* Обработать окно из 512 float-сэмплов @ 16 кГц (примерно [-1;1]). |
||||
* При успехе возвращает 0 и заполняет *prob вероятностью речи [0..1]; |
||||
* при ошибке — отрицательное значение (проб не трогается). |
||||
*/ |
||||
int silero_vad_process(silero_vad_t* vad, const float* samples, float* prob); |
||||
|
||||
/**
|
||||
* То же, но вход — int16 PCM: сэмплы преобразуются к float делением на 32768. |
||||
*/ |
||||
int silero_vad_process_pcm16(silero_vad_t* vad, const int16_t* pcm, float* prob); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif /* SILERO_VAD_H */ |
||||
Binary file not shown.
@ -1,55 +0,0 @@
|
||||
bin |
||||
lib |
||||
|
||||
# Win build files |
||||
*.dll |
||||
*.exe |
||||
*.lib |
||||
|
||||
# GNU build files |
||||
*.o |
||||
*.so* |
||||
*.lo |
||||
*.P* |
||||
*.la* |
||||
*.a |
||||
*.pc |
||||
*config* |
||||
Makefile |
||||
Makefile.in |
||||
.libs |
||||
aclocal.m4 |
||||
autom4te.cache |
||||
stamp-* |
||||
libtool |
||||
soundstretch |
||||
|
||||
# Files generated by MSVC |
||||
*.bsc |
||||
*.suo |
||||
*.sdf |
||||
*.filters |
||||
*.user |
||||
source/SoundTouch/Win32/ |
||||
source/SoundTouch/x64/ |
||||
source/SoundStretch/Win32/ |
||||
source/SoundStretch/x64/ |
||||
source/SoundTouchDll/Win32/ |
||||
source/SoundTouchDll/x64/ |
||||
source/SoundTouchDll/DllTest/Win32/ |
||||
source/SoundTouchDll/DllTest/x64/ |
||||
.vs |
||||
|
||||
# Files generated by Android Studio |
||||
source/android-lib/.gradle |
||||
source/android-lib/.idea |
||||
**/*.iml |
||||
source/android-lib/local.properties |
||||
source/android-lib/build |
||||
source/android-lib/.externalNativeBuild |
||||
|
||||
# CMake build directory |
||||
build* |
||||
CMakeFiles |
||||
CMakeCache.txt |
||||
*.cmake |
||||
@ -1,131 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.16) |
||||
project(SoundTouch VERSION 2.3.3 LANGUAGES CXX) |
||||
set(CMAKE_CXX_STANDARD 17) |
||||
|
||||
include(GNUInstallDirs) |
||||
|
||||
set(COMPILE_OPTIONS) |
||||
|
||||
if(MSVC) |
||||
list(APPEND COMPILE_OPTIONS /O2 /fp:fast) |
||||
else() |
||||
list(APPEND COMPILE_OPTIONS -Wall -Wextra -Wzero-as-null-pointer-constant -Wno-unknown-pragmas) |
||||
if(EMSCRIPTEN) |
||||
list(APPEND COMPILE_OPTIONS -O3) |
||||
else() |
||||
list(APPEND COMPILE_OPTIONS -Ofast) |
||||
endif() |
||||
endif() |
||||
|
||||
set(CMAKE_C_VISIBILITY_PRESET hidden) |
||||
set(CMAKE_CXX_VISIBILITY_PRESET hidden) |
||||
|
||||
##################### |
||||
# SoundTouch library |
||||
|
||||
add_library(SoundTouch STATIC |
||||
source/SoundTouch/AAFilter.cpp |
||||
source/SoundTouch/BPMDetect.cpp |
||||
source/SoundTouch/cpu_detect_x86.cpp |
||||
source/SoundTouch/FIFOSampleBuffer.cpp |
||||
source/SoundTouch/FIRFilter.cpp |
||||
source/SoundTouch/InterpolateCubic.cpp |
||||
source/SoundTouch/InterpolateLinear.cpp |
||||
source/SoundTouch/InterpolateShannon.cpp |
||||
source/SoundTouch/mmx_optimized.cpp |
||||
source/SoundTouch/PeakFinder.cpp |
||||
source/SoundTouch/RateTransposer.cpp |
||||
source/SoundTouch/SoundTouch.cpp |
||||
source/SoundTouch/sse_optimized.cpp |
||||
source/SoundTouch/TDStretch.cpp |
||||
) |
||||
target_include_directories(SoundTouch PUBLIC |
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> |
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> |
||||
) |
||||
target_include_directories(SoundTouch PRIVATE |
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/soundtouch |
||||
) |
||||
|
||||
target_compile_definitions(SoundTouch PRIVATE ${COMPILE_DEFINITIONS}) |
||||
target_compile_options(SoundTouch PRIVATE ${COMPILE_OPTIONS}) |
||||
set_target_properties(SoundTouch PROPERTIES OUTPUT_NAME "soundtouch-static") |
||||
|
||||
option(INTEGER_SAMPLES "Use integers instead of floats for samples" OFF) |
||||
if(INTEGER_SAMPLES) |
||||
target_compile_definitions(SoundTouch PRIVATE SOUNDTOUCH_INTEGER_SAMPLES) |
||||
set(SOUNDTOUCH_INTEGER_SAMPLES TRUE) |
||||
else() |
||||
target_compile_definitions(SoundTouch PRIVATE SOUNDTOUCH_FLOAT_SAMPLES) |
||||
set(SOUNDTOUCH_FLOAT_SAMPLES TRUE) |
||||
endif() |
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(armv7.*)$") |
||||
target_compile_options(SoundTouch PRIVATE -mfpu=neon) |
||||
endif() |
||||
|
||||
install( |
||||
FILES |
||||
include/soundtouch/BPMDetect.h |
||||
include/soundtouch/FIFOSampleBuffer.h |
||||
include/soundtouch/FIFOSamplePipe.h |
||||
include/soundtouch/STTypes.h |
||||
include/soundtouch/SoundTouch.h |
||||
DESTINATION |
||||
"${CMAKE_INSTALL_INCLUDEDIR}/soundtouch" |
||||
) |
||||
|
||||
|
||||
######################## |
||||
# SoundTouchDll library |
||||
|
||||
option(SOUNDTOUCH_DLL "Build SoundTouchDLL C wrapper library" ON) |
||||
if(SOUNDTOUCH_DLL) |
||||
add_library(SoundTouchDLL SHARED |
||||
source/SoundTouchDLL/SoundTouchDLL.cpp |
||||
source/SoundTouchDLL/SoundTouchDLL.rc |
||||
) |
||||
set_target_properties(SoundTouch PROPERTIES POSITION_INDEPENDENT_CODE TRUE) |
||||
target_compile_options(SoundTouchDLL PRIVATE ${COMPILE_OPTIONS}) |
||||
set_target_properties(SoundTouchDLL PROPERTIES CXX_VISIBILITY_PRESET hidden) |
||||
target_compile_definitions(SoundTouchDLL PRIVATE DLL_EXPORTS) |
||||
target_include_directories(SoundTouchDLL INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>) |
||||
target_include_directories(SoundTouchDLL PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include/soundtouch") |
||||
target_link_libraries(SoundTouchDLL PRIVATE SoundTouch) |
||||
set_target_properties(SoundTouchDLL PROPERTIES OUTPUT_NAME "soundtouch") |
||||
if(NOT ANDROID) |
||||
set_target_properties(SoundTouchDLL PROPERTIES VERSION ${CMAKE_PROJECT_VERSION}) |
||||
if(NOT WIN32) |
||||
set_target_properties(SoundTouchDLL PROPERTIES SOVERSION ${PROJECT_VERSION_MAJOR}) |
||||
endif() |
||||
endif() |
||||
install(FILES include/soundtouch/SoundTouchDLL.h DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/soundtouch") |
||||
install(TARGETS SoundTouchDLL EXPORT SoundTouchTargets) |
||||
endif() |
||||
|
||||
######################## |
||||
|
||||
# CMake config |
||||
include(CMakePackageConfigHelpers) |
||||
set(SOUNDTOUCH_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/SoundTouch") |
||||
install( |
||||
EXPORT SoundTouchTargets |
||||
FILE SoundTouchTargets.cmake |
||||
NAMESPACE SoundTouch:: |
||||
DESTINATION "${SOUNDTOUCH_INSTALL_CMAKEDIR}" |
||||
) |
||||
configure_package_config_file(SoundTouchConfig.cmake.in |
||||
"${CMAKE_CURRENT_BINARY_DIR}/SoundTouchConfig.cmake" |
||||
INSTALL_DESTINATION "${SOUNDTOUCH_INSTALL_CMAKEDIR}" |
||||
) |
||||
write_basic_package_version_file( |
||||
"${CMAKE_CURRENT_BINARY_DIR}/SoundTouchConfigVersion.cmake" |
||||
VERSION "${CMAKE_PROJECT_VERSION}" |
||||
COMPATIBILITY SameMajorVersion |
||||
) |
||||
install( |
||||
FILES |
||||
"${CMAKE_CURRENT_BINARY_DIR}/SoundTouchConfig.cmake" |
||||
"${CMAKE_CURRENT_BINARY_DIR}/SoundTouchConfigVersion.cmake" |
||||
DESTINATION "${SOUNDTOUCH_INSTALL_CMAKEDIR}" |
||||
) |
||||
File diff suppressed because it is too large
Load Diff
@ -1,11 +0,0 @@
|
||||
@PACKAGE_INIT@ |
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/SoundTouchTargets.cmake") |
||||
|
||||
check_required_components(SoundTouch) |
||||
|
||||
if(@SOUNDTOUCH_DLL@) |
||||
check_required_components(SoundTouchDLL) |
||||
get_target_property(SoundTouchDLL_LOCATION SoundTouch::SoundTouchDLL LOCATION) |
||||
message(STATUS "Found SoundTouchDLL: ${SoundTouchDLL_LOCATION}") |
||||
endif() |
||||
@ -1,205 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Beats-per-minute (BPM) detection routine.
|
||||
///
|
||||
/// The beat detection algorithm works as follows:
|
||||
/// - Use function 'inputSamples' to input a chunks of samples to the class for
|
||||
/// analysis. It's a good idea to enter a large sound file or stream in smallish
|
||||
/// chunks of around few kilosamples in order not to extinguish too much RAM memory.
|
||||
/// - Input sound data is decimated to approx 500 Hz to reduce calculation burden,
|
||||
/// which is basically ok as low (bass) frequencies mostly determine the beat rate.
|
||||
/// Simple averaging is used for anti-alias filtering because the resulting signal
|
||||
/// quality isn't of that high importance.
|
||||
/// - Decimated sound data is enveloped, i.e. the amplitude shape is detected by
|
||||
/// taking absolute value that's smoothed by sliding average. Signal levels that
|
||||
/// are below a couple of times the general RMS amplitude level are cut away to
|
||||
/// leave only notable peaks there.
|
||||
/// - Repeating sound patterns (e.g. beats) are detected by calculating short-term
|
||||
/// autocorrelation function of the enveloped signal.
|
||||
/// - After whole sound data file has been analyzed as above, the bpm level is
|
||||
/// detected by function 'getBpm' that finds the highest peak of the autocorrelation
|
||||
/// function, calculates it's precise location and converts this reading to bpm's.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _BPMDetect_H_ |
||||
#define _BPMDetect_H_ |
||||
|
||||
#include <vector> |
||||
#include "STTypes.h" |
||||
#include "FIFOSampleBuffer.h" |
||||
|
||||
namespace soundtouch |
||||
{ |
||||
|
||||
/// Minimum allowed BPM rate. Used to restrict accepted result above a reasonable limit.
|
||||
#define MIN_BPM 45 |
||||
|
||||
/// Maximum allowed BPM rate range. Used for calculating algorithm parametrs
|
||||
#define MAX_BPM_RANGE 200 |
||||
|
||||
/// Maximum allowed BPM rate range. Used to restrict accepted result below a reasonable limit.
|
||||
#define MAX_BPM_VALID 190 |
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef struct |
||||
{ |
||||
float pos; |
||||
float strength; |
||||
} BEAT; |
||||
|
||||
|
||||
class IIR2_filter |
||||
{ |
||||
double coeffs[5]; |
||||
double prev[5]; |
||||
|
||||
public: |
||||
IIR2_filter(const double *lpf_coeffs); |
||||
float update(float x); |
||||
}; |
||||
|
||||
|
||||
/// Class for calculating BPM rate for audio data.
|
||||
class BPMDetect |
||||
{ |
||||
protected: |
||||
/// Auto-correlation accumulator bins.
|
||||
float *xcorr; |
||||
|
||||
/// Sample average counter.
|
||||
int decimateCount; |
||||
|
||||
/// Sample average accumulator for FIFO-like decimation.
|
||||
soundtouch::LONG_SAMPLETYPE decimateSum; |
||||
|
||||
/// Decimate sound by this coefficient to reach approx. 500 Hz.
|
||||
int decimateBy; |
||||
|
||||
/// Auto-correlation window length
|
||||
int windowLen; |
||||
|
||||
/// Number of channels (1 = mono, 2 = stereo)
|
||||
int channels; |
||||
|
||||
/// sample rate
|
||||
int sampleRate; |
||||
|
||||
/// Beginning of auto-correlation window: Autocorrelation isn't being updated for
|
||||
/// the first these many correlation bins.
|
||||
int windowStart; |
||||
|
||||
/// window functions for data preconditioning
|
||||
float *hamw; |
||||
float *hamw2; |
||||
|
||||
// beat detection variables
|
||||
int pos; |
||||
int peakPos; |
||||
int beatcorr_ringbuffpos; |
||||
int init_scaler; |
||||
float peakVal; |
||||
float *beatcorr_ringbuff; |
||||
|
||||
/// FIFO-buffer for decimated processing samples.
|
||||
soundtouch::FIFOSampleBuffer *buffer; |
||||
|
||||
/// Collection of detected beat positions
|
||||
//BeatCollection beats;
|
||||
std::vector<BEAT> beats; |
||||
|
||||
// 2nd order low-pass-filter
|
||||
IIR2_filter beat_lpf; |
||||
|
||||
/// Updates auto-correlation function for given number of decimated samples that
|
||||
/// are read from the internal 'buffer' pipe (samples aren't removed from the pipe
|
||||
/// though).
|
||||
void updateXCorr(int process_samples /// How many samples are processed.
|
||||
); |
||||
|
||||
/// Decimates samples to approx. 500 Hz.
|
||||
///
|
||||
/// \return Number of output samples.
|
||||
int decimate(soundtouch::SAMPLETYPE *dest, ///< Destination buffer
|
||||
const soundtouch::SAMPLETYPE *src, ///< Source sample buffer
|
||||
int numsamples ///< Number of source samples.
|
||||
); |
||||
|
||||
/// Calculates amplitude envelope for the buffer of samples.
|
||||
/// Result is output to 'samples'.
|
||||
void calcEnvelope(soundtouch::SAMPLETYPE *samples, ///< Pointer to input/output data buffer
|
||||
int numsamples ///< Number of samples in buffer
|
||||
); |
||||
|
||||
/// remove constant bias from xcorr data
|
||||
void removeBias(); |
||||
|
||||
// Detect individual beat positions
|
||||
void updateBeatPos(int process_samples); |
||||
|
||||
|
||||
public: |
||||
/// Constructor.
|
||||
BPMDetect(int numChannels, ///< Number of channels in sample data.
|
||||
int sampleRate ///< Sample rate in Hz.
|
||||
); |
||||
|
||||
/// Destructor.
|
||||
virtual ~BPMDetect(); |
||||
|
||||
/// Inputs a block of samples for analyzing: Envelopes the samples and then
|
||||
/// updates the autocorrelation estimation. When whole song data has been input
|
||||
/// in smaller blocks using this function, read the resulting bpm with 'getBpm'
|
||||
/// function.
|
||||
///
|
||||
/// Notice that data in 'samples' array can be disrupted in processing.
|
||||
void inputSamples(const soundtouch::SAMPLETYPE *samples, ///< Pointer to input/working data buffer
|
||||
int numSamples ///< Number of samples in buffer
|
||||
); |
||||
|
||||
/// Analyzes the results and returns the BPM rate. Use this function to read result
|
||||
/// after whole song data has been input to the class by consecutive calls of
|
||||
/// 'inputSamples' function.
|
||||
///
|
||||
/// \return Beats-per-minute rate, or zero if detection failed.
|
||||
float getBpm(); |
||||
|
||||
/// Get beat position arrays. Note: The array includes also really low beat detection values
|
||||
/// in absence of clear strong beats. Consumer may wish to filter low values away.
|
||||
/// - "pos" receive array of beat positions
|
||||
/// - "values" receive array of beat detection strengths
|
||||
/// - max_num indicates max.size of "pos" and "values" array.
|
||||
///
|
||||
/// You can query a suitable array sized by calling this with nullptr in "pos" & "values".
|
||||
///
|
||||
/// \return number of beats in the arrays.
|
||||
int getBeats(float *pos, float *strength, int max_num); |
||||
}; |
||||
} |
||||
#endif // _BPMDetect_H_
|
||||
@ -1,180 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// A buffer class for temporarily storaging sound samples, operates as a
|
||||
/// first-in-first-out pipe.
|
||||
///
|
||||
/// Samples are added to the end of the sample buffer with the 'putSamples'
|
||||
/// function, and are received from the beginning of the buffer by calling
|
||||
/// the 'receiveSamples' function. The class automatically removes the
|
||||
/// output samples from the buffer as well as grows the storage size
|
||||
/// whenever necessary.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef FIFOSampleBuffer_H |
||||
#define FIFOSampleBuffer_H |
||||
|
||||
#include "FIFOSamplePipe.h" |
||||
|
||||
namespace soundtouch |
||||
{ |
||||
|
||||
/// Sample buffer working in FIFO (first-in-first-out) principle. The class takes
|
||||
/// care of storage size adjustment and data moving during input/output operations.
|
||||
///
|
||||
/// Notice that in case of stereo audio, one sample is considered to consist of
|
||||
/// both channel data.
|
||||
class FIFOSampleBuffer : public FIFOSamplePipe |
||||
{ |
||||
private: |
||||
/// Sample buffer.
|
||||
SAMPLETYPE *buffer; |
||||
|
||||
// Raw unaligned buffer memory. 'buffer' is made aligned by pointing it to first
|
||||
// 16-byte aligned location of this buffer
|
||||
SAMPLETYPE *bufferUnaligned; |
||||
|
||||
/// Sample buffer size in bytes
|
||||
uint sizeInBytes; |
||||
|
||||
/// How many samples are currently in buffer.
|
||||
uint samplesInBuffer; |
||||
|
||||
/// Channels, 1=mono, 2=stereo.
|
||||
uint channels; |
||||
|
||||
/// Current position pointer to the buffer. This pointer is increased when samples are
|
||||
/// removed from the pipe so that it's necessary to actually rewind buffer (move data)
|
||||
/// only new data when is put to the pipe.
|
||||
uint bufferPos; |
||||
|
||||
/// Rewind the buffer by moving data from position pointed by 'bufferPos' to real
|
||||
/// beginning of the buffer.
|
||||
void rewind(); |
||||
|
||||
/// Ensures that the buffer has capacity for at least this many samples.
|
||||
void ensureCapacity(uint capacityRequirement); |
||||
|
||||
/// Returns current capacity.
|
||||
uint getCapacity() const; |
||||
|
||||
public: |
||||
|
||||
/// Constructor
|
||||
FIFOSampleBuffer(int numChannels = 2 ///< Number of channels, 1=mono, 2=stereo.
|
||||
///< Default is stereo.
|
||||
); |
||||
|
||||
/// destructor
|
||||
~FIFOSampleBuffer() override; |
||||
|
||||
/// Returns a pointer to the beginning of the output samples.
|
||||
/// This function is provided for accessing the output samples directly.
|
||||
/// Please be careful for not to corrupt the book-keeping!
|
||||
///
|
||||
/// When using this function to output samples, also remember to 'remove' the
|
||||
/// output samples from the buffer by calling the
|
||||
/// 'receiveSamples(numSamples)' function
|
||||
virtual SAMPLETYPE *ptrBegin() override; |
||||
|
||||
/// Returns a pointer to the end of the used part of the sample buffer (i.e.
|
||||
/// where the new samples are to be inserted). This function may be used for
|
||||
/// inserting new samples into the sample buffer directly. Please be careful
|
||||
/// not corrupt the book-keeping!
|
||||
///
|
||||
/// When using this function as means for inserting new samples, also remember
|
||||
/// to increase the sample count afterwards, by calling the
|
||||
/// 'putSamples(numSamples)' function.
|
||||
SAMPLETYPE *ptrEnd( |
||||
uint slackCapacity ///< How much free capacity (in samples) there _at least_
|
||||
///< should be so that the caller can successfully insert the
|
||||
///< desired samples to the buffer. If necessary, the function
|
||||
///< grows the buffer size to comply with this requirement.
|
||||
); |
||||
|
||||
/// Adds 'numSamples' pcs of samples from the 'samples' memory position to
|
||||
/// the sample buffer.
|
||||
virtual void putSamples(const SAMPLETYPE *samples, ///< Pointer to samples.
|
||||
uint numSamples ///< Number of samples to insert.
|
||||
) override; |
||||
|
||||
/// Adjusts the book-keeping to increase number of samples in the buffer without
|
||||
/// copying any actual samples.
|
||||
///
|
||||
/// This function is used to update the number of samples in the sample buffer
|
||||
/// when accessing the buffer directly with 'ptrEnd' function. Please be
|
||||
/// careful though!
|
||||
virtual void putSamples(uint numSamples ///< Number of samples been inserted.
|
||||
); |
||||
|
||||
/// Output samples from beginning of the sample buffer. Copies requested samples to
|
||||
/// output buffer and removes them from the sample buffer. If there are less than
|
||||
/// 'numsample' samples in the buffer, returns all that available.
|
||||
///
|
||||
/// \return Number of samples returned.
|
||||
virtual uint receiveSamples(SAMPLETYPE *output, ///< Buffer where to copy output samples.
|
||||
uint maxSamples ///< How many samples to receive at max.
|
||||
) override; |
||||
|
||||
/// Adjusts book-keeping so that given number of samples are removed from beginning of the
|
||||
/// sample buffer without copying them anywhere.
|
||||
///
|
||||
/// Used to reduce the number of samples in the buffer when accessing the sample buffer directly
|
||||
/// with 'ptrBegin' function.
|
||||
virtual uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe.
|
||||
) override; |
||||
|
||||
/// Returns number of samples currently available.
|
||||
virtual uint numSamples() const override; |
||||
|
||||
/// Sets number of channels, 1 = mono, 2 = stereo.
|
||||
void setChannels(int numChannels); |
||||
|
||||
/// Get number of channels
|
||||
int getChannels() |
||||
{ |
||||
return channels; |
||||
} |
||||
|
||||
/// Returns nonzero if there aren't any samples available for outputting.
|
||||
virtual int isEmpty() const override; |
||||
|
||||
/// Clears all the samples.
|
||||
virtual void clear() override; |
||||
|
||||
/// allow trimming (downwards) amount of samples in pipeline.
|
||||
/// Returns adjusted amount of samples
|
||||
uint adjustAmountOfSamples(uint numSamples) override; |
||||
|
||||
/// Add silence to end of buffer
|
||||
void addSilent(uint nSamples); |
||||
}; |
||||
|
||||
} |
||||
|
||||
#endif |
||||
@ -1,230 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// 'FIFOSamplePipe' : An abstract base class for classes that manipulate sound
|
||||
/// samples by operating like a first-in-first-out pipe: New samples are fed
|
||||
/// into one end of the pipe with the 'putSamples' function, and the processed
|
||||
/// samples are received from the other end with the 'receiveSamples' function.
|
||||
///
|
||||
/// 'FIFOProcessor' : A base class for classes the do signal processing with
|
||||
/// the samples while operating like a first-in-first-out pipe. When samples
|
||||
/// are input with the 'putSamples' function, the class processes them
|
||||
/// and moves the processed samples to the given 'output' pipe object, which
|
||||
/// may be either another processing stage, or a fifo sample buffer object.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef FIFOSamplePipe_H |
||||
#define FIFOSamplePipe_H |
||||
|
||||
#include <assert.h> |
||||
#include <stdlib.h> |
||||
#include "STTypes.h" |
||||
|
||||
namespace soundtouch |
||||
{ |
||||
|
||||
/// Abstract base class for FIFO (first-in-first-out) sample processing classes.
|
||||
class FIFOSamplePipe |
||||
{ |
||||
protected: |
||||
|
||||
bool verifyNumberOfChannels(int nChannels) const |
||||
{ |
||||
if ((nChannels > 0) && (nChannels <= SOUNDTOUCH_MAX_CHANNELS)) |
||||
{ |
||||
return true; |
||||
} |
||||
ST_THROW_RT_ERROR("Error: Illegal number of channels"); |
||||
return false; |
||||
} |
||||
|
||||
public: |
||||
// virtual default destructor
|
||||
virtual ~FIFOSamplePipe() {} |
||||
|
||||
|
||||
/// Returns a pointer to the beginning of the output samples.
|
||||
/// This function is provided for accessing the output samples directly.
|
||||
/// Please be careful for not to corrupt the book-keeping!
|
||||
///
|
||||
/// When using this function to output samples, also remember to 'remove' the
|
||||
/// output samples from the buffer by calling the
|
||||
/// 'receiveSamples(numSamples)' function
|
||||
virtual SAMPLETYPE *ptrBegin() = 0; |
||||
|
||||
/// Adds 'numSamples' pcs of samples from the 'samples' memory position to
|
||||
/// the sample buffer.
|
||||
virtual void putSamples(const SAMPLETYPE *samples, ///< Pointer to samples.
|
||||
uint numSamples ///< Number of samples to insert.
|
||||
) = 0; |
||||
|
||||
|
||||
// Moves samples from the 'other' pipe instance to this instance.
|
||||
void moveSamples(FIFOSamplePipe &other ///< Other pipe instance where from the receive the data.
|
||||
) |
||||
{ |
||||
const uint oNumSamples = other.numSamples(); |
||||
|
||||
putSamples(other.ptrBegin(), oNumSamples); |
||||
other.receiveSamples(oNumSamples); |
||||
} |
||||
|
||||
/// Output samples from beginning of the sample buffer. Copies requested samples to
|
||||
/// output buffer and removes them from the sample buffer. If there are less than
|
||||
/// 'numsample' samples in the buffer, returns all that available.
|
||||
///
|
||||
/// \return Number of samples returned.
|
||||
virtual uint receiveSamples(SAMPLETYPE *output, ///< Buffer where to copy output samples.
|
||||
uint maxSamples ///< How many samples to receive at max.
|
||||
) = 0; |
||||
|
||||
/// Adjusts book-keeping so that given number of samples are removed from beginning of the
|
||||
/// sample buffer without copying them anywhere.
|
||||
///
|
||||
/// Used to reduce the number of samples in the buffer when accessing the sample buffer directly
|
||||
/// with 'ptrBegin' function.
|
||||
virtual uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe.
|
||||
) = 0; |
||||
|
||||
/// Returns number of samples currently available.
|
||||
virtual uint numSamples() const = 0; |
||||
|
||||
// Returns nonzero if there aren't any samples available for outputting.
|
||||
virtual int isEmpty() const = 0; |
||||
|
||||
/// Clears all the samples.
|
||||
virtual void clear() = 0; |
||||
|
||||
/// allow trimming (downwards) amount of samples in pipeline.
|
||||
/// Returns adjusted amount of samples
|
||||
virtual uint adjustAmountOfSamples(uint numSamples) = 0; |
||||
|
||||
}; |
||||
|
||||
|
||||
/// Base-class for sound processing routines working in FIFO principle. With this base
|
||||
/// class it's easy to implement sound processing stages that can be chained together,
|
||||
/// so that samples that are fed into beginning of the pipe automatically go through
|
||||
/// all the processing stages.
|
||||
///
|
||||
/// When samples are input to this class, they're first processed and then put to
|
||||
/// the FIFO pipe that's defined as output of this class. This output pipe can be
|
||||
/// either other processing stage or a FIFO sample buffer.
|
||||
class FIFOProcessor :public FIFOSamplePipe |
||||
{ |
||||
protected: |
||||
/// Internal pipe where processed samples are put.
|
||||
FIFOSamplePipe *output; |
||||
|
||||
/// Sets output pipe.
|
||||
void setOutPipe(FIFOSamplePipe *pOutput) |
||||
{ |
||||
assert(output == nullptr); |
||||
assert(pOutput != nullptr); |
||||
output = pOutput; |
||||
} |
||||
|
||||
/// Constructor. Doesn't define output pipe; it has to be set be
|
||||
/// 'setOutPipe' function.
|
||||
FIFOProcessor() |
||||
{ |
||||
output = nullptr; |
||||
} |
||||
|
||||
/// Constructor. Configures output pipe.
|
||||
FIFOProcessor(FIFOSamplePipe *pOutput ///< Output pipe.
|
||||
) |
||||
{ |
||||
output = pOutput; |
||||
} |
||||
|
||||
/// Destructor.
|
||||
virtual ~FIFOProcessor() override |
||||
{ |
||||
} |
||||
|
||||
/// Returns a pointer to the beginning of the output samples.
|
||||
/// This function is provided for accessing the output samples directly.
|
||||
/// Please be careful for not to corrupt the book-keeping!
|
||||
///
|
||||
/// When using this function to output samples, also remember to 'remove' the
|
||||
/// output samples from the buffer by calling the
|
||||
/// 'receiveSamples(numSamples)' function
|
||||
virtual SAMPLETYPE *ptrBegin() override |
||||
{ |
||||
return output->ptrBegin(); |
||||
} |
||||
|
||||
public: |
||||
|
||||
/// Output samples from beginning of the sample buffer. Copies requested samples to
|
||||
/// output buffer and removes them from the sample buffer. If there are less than
|
||||
/// 'numsample' samples in the buffer, returns all that available.
|
||||
///
|
||||
/// \return Number of samples returned.
|
||||
virtual uint receiveSamples(SAMPLETYPE *outBuffer, ///< Buffer where to copy output samples.
|
||||
uint maxSamples ///< How many samples to receive at max.
|
||||
) override |
||||
{ |
||||
return output->receiveSamples(outBuffer, maxSamples); |
||||
} |
||||
|
||||
/// Adjusts book-keeping so that given number of samples are removed from beginning of the
|
||||
/// sample buffer without copying them anywhere.
|
||||
///
|
||||
/// Used to reduce the number of samples in the buffer when accessing the sample buffer directly
|
||||
/// with 'ptrBegin' function.
|
||||
virtual uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe.
|
||||
) override |
||||
{ |
||||
return output->receiveSamples(maxSamples); |
||||
} |
||||
|
||||
/// Returns number of samples currently available.
|
||||
virtual uint numSamples() const override |
||||
{ |
||||
return output->numSamples(); |
||||
} |
||||
|
||||
/// Returns nonzero if there aren't any samples available for outputting.
|
||||
virtual int isEmpty() const override |
||||
{ |
||||
return output->isEmpty(); |
||||
} |
||||
|
||||
/// allow trimming (downwards) amount of samples in pipeline.
|
||||
/// Returns adjusted amount of samples
|
||||
virtual uint adjustAmountOfSamples(uint numSamples) override |
||||
{ |
||||
return output->adjustAmountOfSamples(numSamples); |
||||
} |
||||
}; |
||||
|
||||
} |
||||
|
||||
#endif |
||||
@ -1,176 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Common type definitions for SoundTouch audio processing library.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef STTypes_H |
||||
#define STTypes_H |
||||
|
||||
typedef unsigned int uint; |
||||
typedef unsigned long ulong; |
||||
|
||||
// Patch for MinGW: on Win64 long is 32-bit
|
||||
#ifdef _WIN64 |
||||
typedef unsigned long long ulongptr; |
||||
#else |
||||
typedef ulong ulongptr; |
||||
#endif |
||||
|
||||
|
||||
// Helper macro for aligning pointer up to next 16-byte boundary
|
||||
#define SOUNDTOUCH_ALIGN_POINTER_16(x) ( ( (ulongptr)(x) + 15 ) & ~(ulongptr)15 ) |
||||
|
||||
|
||||
namespace soundtouch |
||||
{ |
||||
/// Max allowed number of channels
|
||||
#define SOUNDTOUCH_MAX_CHANNELS 16 |
||||
|
||||
/// Activate these undef's to overrule the possible sampletype
|
||||
/// setting inherited from some other header file:
|
||||
//#undef SOUNDTOUCH_INTEGER_SAMPLES
|
||||
//#undef SOUNDTOUCH_FLOAT_SAMPLES
|
||||
|
||||
/// If following flag is defined, always uses multichannel processing
|
||||
/// routines also for mono and stero sound. This is for routine testing
|
||||
/// purposes; output should be same with either routines, yet disabling
|
||||
/// the dedicated mono/stereo processing routines will result in slower
|
||||
/// runtime performance so recommendation is to keep this off.
|
||||
// #define USE_MULTICH_ALWAYS
|
||||
|
||||
#if !(SOUNDTOUCH_INTEGER_SAMPLES || SOUNDTOUCH_FLOAT_SAMPLES) |
||||
|
||||
/// Choose either 32bit floating point or 16bit integer sampletype
|
||||
/// by choosing one of the following defines, unless this selection
|
||||
/// has already been done in some other file.
|
||||
////
|
||||
/// Notes:
|
||||
/// - In Windows environment, choose the sample format with the
|
||||
/// following defines.
|
||||
/// - In GNU environment, the floating point samples are used by
|
||||
/// default, but integer samples can be chosen by giving the
|
||||
/// following switch to the configure script:
|
||||
/// ./configure --enable-integer-samples
|
||||
/// However, if you still prefer to select the sample format here
|
||||
/// also in GNU environment, then please #undef the INTEGER_SAMPLE
|
||||
/// and FLOAT_SAMPLE defines first as in comments above.
|
||||
//#define SOUNDTOUCH_INTEGER_SAMPLES 1 //< 16bit integer samples
|
||||
#define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples
|
||||
|
||||
#endif |
||||
|
||||
#if (_M_IX86 || __i386__ || __x86_64__ || _M_X64) |
||||
/// Define this to allow X86-specific assembler/intrinsic optimizations.
|
||||
/// Notice that library contains also usual C++ versions of each of these
|
||||
/// these routines, so if you're having difficulties getting the optimized
|
||||
/// routines compiled for whatever reason, you may disable these optimizations
|
||||
/// to make the library compile.
|
||||
|
||||
#define SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS 1 |
||||
#undef SOUNDTOUCH_ALLOW_NEON_OPTIMIZATIONS |
||||
#elif defined(__arm__) || defined(__aarch64__) |
||||
#undef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS |
||||
#define SOUNDTOUCH_ALLOW_NEON_OPTIMIZATIONS 1 |
||||
#else |
||||
/// Always disable optimizations when not using a x86 systems.
|
||||
#undef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS |
||||
#undef SOUNDTOUCH_ALLOW_NEON_OPTIMIZATIONS |
||||
|
||||
#endif |
||||
|
||||
// If defined, allows the SIMD-optimized routines to skip unevenly aligned
|
||||
// memory offsets that can cause performance penalty in some SIMD implementations.
|
||||
// Causes slight compromise in sound quality.
|
||||
// #define SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION 1
|
||||
|
||||
|
||||
#ifdef SOUNDTOUCH_INTEGER_SAMPLES |
||||
// 16bit integer sample type
|
||||
typedef short SAMPLETYPE; |
||||
// data type for sample accumulation: Use 32bit integer to prevent overflows
|
||||
typedef long LONG_SAMPLETYPE; |
||||
|
||||
#ifdef SOUNDTOUCH_FLOAT_SAMPLES |
||||
// check that only one sample type is defined
|
||||
#error "conflicting sample types defined" |
||||
#endif // SOUNDTOUCH_FLOAT_SAMPLES
|
||||
|
||||
#ifdef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS |
||||
// Allow MMX optimizations (not available in X64 mode)
|
||||
#if (!_M_X64) |
||||
#define SOUNDTOUCH_ALLOW_MMX 1 |
||||
#endif |
||||
#endif |
||||
|
||||
#else |
||||
|
||||
// floating point samples
|
||||
typedef float SAMPLETYPE; |
||||
// data type for sample accumulation: Use float also here to enable
|
||||
// efficient autovectorization
|
||||
typedef float LONG_SAMPLETYPE; |
||||
|
||||
#ifdef SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS |
||||
// Allow SSE optimizations
|
||||
#define SOUNDTOUCH_ALLOW_SSE 1 |
||||
#endif |
||||
#ifdef SOUNDTOUCH_ALLOW_NEON_OPTIMIZATIONS |
||||
#define SOUNDTOUCH_USE_NEON 1 |
||||
#endif |
||||
|
||||
#endif // SOUNDTOUCH_INTEGER_SAMPLES
|
||||
|
||||
#if ((SOUNDTOUCH_ALLOW_SSE) || (__SSE__) || (SOUNDTOUCH_USE_NEON)) |
||||
#if SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION |
||||
#define ST_SIMD_AVOID_UNALIGNED |
||||
#endif |
||||
#endif |
||||
|
||||
} |
||||
|
||||
// define ST_NO_EXCEPTION_HANDLING switch to disable throwing std exceptions:
|
||||
#define ST_NO_EXCEPTION_HANDLING 1 |
||||
#ifdef ST_NO_EXCEPTION_HANDLING |
||||
// Exceptions disabled. Throw asserts instead if enabled.
|
||||
#include <assert.h> |
||||
#define ST_THROW_RT_ERROR(x) {assert((const char *)x);} |
||||
#else |
||||
// use c++ standard exceptions
|
||||
#include <stdexcept> |
||||
#include <string> |
||||
#define ST_THROW_RT_ERROR(x) {throw std::runtime_error(x);} |
||||
#endif |
||||
|
||||
// When this #define is active, eliminates a clicking sound when the "rate" or "pitch"
|
||||
// parameter setting crosses from value <1 to >=1 or vice versa during processing.
|
||||
// Default is off as such crossover is untypical case and involves a slight sound
|
||||
// quality compromise.
|
||||
//#define SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER 1
|
||||
|
||||
#endif |
||||
@ -1,348 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// SoundTouch - main class for tempo/pitch/rate adjusting routines.
|
||||
///
|
||||
/// Notes:
|
||||
/// - Initialize the SoundTouch object instance by setting up the sound stream
|
||||
/// parameters with functions 'setSampleRate' and 'setChannels', then set
|
||||
/// desired tempo/pitch/rate settings with the corresponding functions.
|
||||
///
|
||||
/// - The SoundTouch class behaves like a first-in-first-out pipeline: The
|
||||
/// samples that are to be processed are fed into one of the pipe by calling
|
||||
/// function 'putSamples', while the ready processed samples can be read
|
||||
/// from the other end of the pipeline with function 'receiveSamples'.
|
||||
///
|
||||
/// - The SoundTouch processing classes require certain sized 'batches' of
|
||||
/// samples in order to process the sound. For this reason the classes buffer
|
||||
/// incoming samples until there are enough of samples available for
|
||||
/// processing, then they carry out the processing step and consequently
|
||||
/// make the processed samples available for outputting.
|
||||
///
|
||||
/// - For the above reason, the processing routines introduce a certain
|
||||
/// 'latency' between the input and output, so that the samples input to
|
||||
/// SoundTouch may not be immediately available in the output, and neither
|
||||
/// the amount of outputtable samples may not immediately be in direct
|
||||
/// relationship with the amount of previously input samples.
|
||||
///
|
||||
/// - The tempo/pitch/rate control parameters can be altered during processing.
|
||||
/// Please notice though that they aren't currently protected by semaphores,
|
||||
/// so in multi-thread application external semaphore protection may be
|
||||
/// required.
|
||||
///
|
||||
/// - This class utilizes classes 'TDStretch' for tempo change (without modifying
|
||||
/// pitch) and 'RateTransposer' for changing the playback rate (that is, both
|
||||
/// tempo and pitch in the same ratio) of the sound. The third available control
|
||||
/// 'pitch' (change pitch but maintain tempo) is produced by a combination of
|
||||
/// combining the two other controls.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SoundTouch_H |
||||
#define SoundTouch_H |
||||
|
||||
#include "FIFOSamplePipe.h" |
||||
#include "STTypes.h" |
||||
|
||||
namespace soundtouch |
||||
{ |
||||
|
||||
/// Soundtouch library version string
|
||||
#define SOUNDTOUCH_VERSION "2.3.3" |
||||
|
||||
/// SoundTouch library version id
|
||||
#define SOUNDTOUCH_VERSION_ID (20303) |
||||
|
||||
//
|
||||
// Available setting IDs for the 'setSetting' & 'get_setting' functions:
|
||||
|
||||
/// Enable/disable anti-alias filter in pitch transposer (0 = disable)
|
||||
#define SETTING_USE_AA_FILTER 0 |
||||
|
||||
/// Pitch transposer anti-alias filter length (8 .. 128 taps, default = 32)
|
||||
#define SETTING_AA_FILTER_LENGTH 1 |
||||
|
||||
/// Enable/disable quick seeking algorithm in tempo changer routine
|
||||
/// (enabling quick seeking lowers CPU utilization but causes a minor sound
|
||||
/// quality compromising)
|
||||
#define SETTING_USE_QUICKSEEK 2 |
||||
|
||||
/// Time-stretch algorithm single processing sequence length in milliseconds. This determines
|
||||
/// to how long sequences the original sound is chopped in the time-stretch algorithm.
|
||||
/// See "STTypes.h" or README for more information.
|
||||
#define SETTING_SEQUENCE_MS 3 |
||||
|
||||
/// Time-stretch algorithm seeking window length in milliseconds for algorithm that finds the
|
||||
/// best possible overlapping location. This determines from how wide window the algorithm
|
||||
/// may look for an optimal joining location when mixing the sound sequences back together.
|
||||
/// See "STTypes.h" or README for more information.
|
||||
#define SETTING_SEEKWINDOW_MS 4 |
||||
|
||||
/// Time-stretch algorithm overlap length in milliseconds. When the chopped sound sequences
|
||||
/// are mixed back together, to form a continuous sound stream, this parameter defines over
|
||||
/// how long period the two consecutive sequences are let to overlap each other.
|
||||
/// See "STTypes.h" or README for more information.
|
||||
#define SETTING_OVERLAP_MS 5 |
||||
|
||||
|
||||
/// Call "getSetting" with this ID to query processing sequence size in samples.
|
||||
/// This value gives approximate value of how many input samples you'll need to
|
||||
/// feed into SoundTouch after initial buffering to get out a new batch of
|
||||
/// output samples.
|
||||
///
|
||||
/// This value does not include initial buffering at beginning of a new processing
|
||||
/// stream, use SETTING_INITIAL_LATENCY to get the initial buffering size.
|
||||
///
|
||||
/// Notices:
|
||||
/// - This is read-only parameter, i.e. setSetting ignores this parameter
|
||||
/// - This parameter value is not constant but change depending on
|
||||
/// tempo/pitch/rate/samplerate settings.
|
||||
#define SETTING_NOMINAL_INPUT_SEQUENCE 6 |
||||
|
||||
|
||||
/// Call "getSetting" with this ID to query nominal average processing output
|
||||
/// size in samples. This value tells approcimate value how many output samples
|
||||
/// SoundTouch outputs once it does DSP processing run for a batch of input samples.
|
||||
///
|
||||
/// Notices:
|
||||
/// - This is read-only parameter, i.e. setSetting ignores this parameter
|
||||
/// - This parameter value is not constant but change depending on
|
||||
/// tempo/pitch/rate/samplerate settings.
|
||||
#define SETTING_NOMINAL_OUTPUT_SEQUENCE 7 |
||||
|
||||
|
||||
/// Call "getSetting" with this ID to query initial processing latency, i.e.
|
||||
/// approx. how many samples you'll need to enter to SoundTouch pipeline before
|
||||
/// you can expect to get first batch of ready output samples out.
|
||||
///
|
||||
/// After the first output batch, you can then expect to get approx.
|
||||
/// SETTING_NOMINAL_OUTPUT_SEQUENCE ready samples out for every
|
||||
/// SETTING_NOMINAL_INPUT_SEQUENCE samples that you enter into SoundTouch.
|
||||
///
|
||||
/// Example:
|
||||
/// processing with parameter -tempo=5
|
||||
/// => initial latency = 5509 samples
|
||||
/// input sequence = 4167 samples
|
||||
/// output sequence = 3969 samples
|
||||
///
|
||||
/// Accordingly, you can expect to feed in approx. 5509 samples at beginning of
|
||||
/// the stream, and then you'll get out the first 3969 samples. After that, for
|
||||
/// every approx. 4167 samples that you'll put in, you'll receive again approx.
|
||||
/// 3969 samples out.
|
||||
///
|
||||
/// This also means that average latency during stream processing is
|
||||
/// INITIAL_LATENCY-OUTPUT_SEQUENCE/2, in the above example case 5509-3969/2
|
||||
/// = 3524 samples
|
||||
///
|
||||
/// Notices:
|
||||
/// - This is read-only parameter, i.e. setSetting ignores this parameter
|
||||
/// - This parameter value is not constant but change depending on
|
||||
/// tempo/pitch/rate/samplerate settings.
|
||||
#define SETTING_INITIAL_LATENCY 8 |
||||
|
||||
|
||||
class SoundTouch final : public FIFOProcessor |
||||
{ |
||||
private: |
||||
/// Rate transposer class instance
|
||||
class RateTransposer *pRateTransposer; |
||||
|
||||
/// Time-stretch class instance
|
||||
class TDStretch *pTDStretch; |
||||
|
||||
/// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters.
|
||||
double virtualRate; |
||||
|
||||
/// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters.
|
||||
double virtualTempo; |
||||
|
||||
/// Virtual pitch parameter. Effective rate & tempo are calculated from these parameters.
|
||||
double virtualPitch; |
||||
|
||||
/// Flag: Has sample rate been set?
|
||||
bool bSrateSet; |
||||
|
||||
/// Accumulator for how many samples in total will be expected as output vs. samples put in,
|
||||
/// considering current processing settings.
|
||||
double samplesExpectedOut; |
||||
|
||||
/// Accumulator for how many samples in total have been read out from the processing so far
|
||||
long samplesOutput; |
||||
|
||||
/// Calculates effective rate & tempo valuescfrom 'virtualRate', 'virtualTempo' and
|
||||
/// 'virtualPitch' parameters.
|
||||
void calcEffectiveRateAndTempo(); |
||||
|
||||
protected : |
||||
/// Number of channels
|
||||
uint channels; |
||||
|
||||
/// Effective 'rate' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch'
|
||||
double rate; |
||||
|
||||
/// Effective 'tempo' value calculated from 'virtualRate', 'virtualTempo' and 'virtualPitch'
|
||||
double tempo; |
||||
|
||||
public: |
||||
SoundTouch(); |
||||
~SoundTouch() override; |
||||
|
||||
/// Get SoundTouch library version string
|
||||
static const char *getVersionString(); |
||||
|
||||
/// Get SoundTouch library version Id
|
||||
static uint getVersionId(); |
||||
|
||||
/// Sets new rate control value. Normal rate = 1.0, smaller values
|
||||
/// represent slower rate, larger faster rates.
|
||||
void setRate(double newRate); |
||||
|
||||
/// Sets new tempo control value. Normal tempo = 1.0, smaller values
|
||||
/// represent slower tempo, larger faster tempo.
|
||||
void setTempo(double newTempo); |
||||
|
||||
/// Sets new rate control value as a difference in percents compared
|
||||
/// to the original rate (-50 .. +100 %)
|
||||
void setRateChange(double newRate); |
||||
|
||||
/// Sets new tempo control value as a difference in percents compared
|
||||
/// to the original tempo (-50 .. +100 %)
|
||||
void setTempoChange(double newTempo); |
||||
|
||||
/// Sets new pitch control value. Original pitch = 1.0, smaller values
|
||||
/// represent lower pitches, larger values higher pitch.
|
||||
void setPitch(double newPitch); |
||||
|
||||
/// Sets pitch change in octaves compared to the original pitch
|
||||
/// (-1.00 .. +1.00)
|
||||
void setPitchOctaves(double newPitch); |
||||
|
||||
/// Sets pitch change in semi-tones compared to the original pitch
|
||||
/// (-12 .. +12)
|
||||
void setPitchSemiTones(int newPitch); |
||||
void setPitchSemiTones(double newPitch); |
||||
|
||||
/// Sets the number of channels, 1 = mono, 2 = stereo
|
||||
void setChannels(uint numChannels); |
||||
|
||||
/// Sets sample rate.
|
||||
void setSampleRate(uint srate); |
||||
|
||||
/// Get ratio between input and output audio durations, useful for calculating
|
||||
/// processed output duration: if you'll process a stream of N samples, then
|
||||
/// you can expect to get out N * getInputOutputSampleRatio() samples.
|
||||
///
|
||||
/// This ratio will give accurate target duration ratio for a full audio track,
|
||||
/// given that the the whole track is processed with same processing parameters.
|
||||
///
|
||||
/// If this ratio is applied to calculate intermediate offsets inside a processing
|
||||
/// stream, then this ratio is approximate and can deviate +- some tens of milliseconds
|
||||
/// from ideal offset, yet by end of the audio stream the duration ratio will become
|
||||
/// exact.
|
||||
///
|
||||
/// Example: if processing with parameters "-tempo=15 -pitch=-3", the function
|
||||
/// will return value 0.8695652... Now, if processing an audio stream whose duration
|
||||
/// is exactly one million audio samples, then you can expect the processed
|
||||
/// output duration be 0.869565 * 1000000 = 869565 samples.
|
||||
double getInputOutputSampleRatio(); |
||||
|
||||
/// Flushes the last samples from the processing pipeline to the output.
|
||||
/// Clears also the internal processing buffers.
|
||||
//
|
||||
/// Note: This function is meant for extracting the last samples of a sound
|
||||
/// stream. This function may introduce additional blank samples in the end
|
||||
/// of the sound stream, and thus it's not recommended to call this function
|
||||
/// in the middle of a sound stream.
|
||||
void flush(); |
||||
|
||||
/// Adds 'numSamples' pcs of samples from the 'samples' memory position into
|
||||
/// the input of the object. Notice that sample rate _has_to_ be set before
|
||||
/// calling this function, otherwise throws a runtime_error exception.
|
||||
void putSamples( |
||||
const SAMPLETYPE *samples, ///< Pointer to sample buffer.
|
||||
uint numSamples ///< Number of samples in buffer. Notice
|
||||
///< that in case of stereo-sound a single sample
|
||||
///< contains data for both channels.
|
||||
) override; |
||||
|
||||
/// Output samples from beginning of the sample buffer. Copies requested samples to
|
||||
/// output buffer and removes them from the sample buffer. If there are less than
|
||||
/// 'numsample' samples in the buffer, returns all that available.
|
||||
///
|
||||
/// \return Number of samples returned.
|
||||
uint receiveSamples(SAMPLETYPE *output, ///< Buffer where to copy output samples.
|
||||
uint maxSamples ///< How many samples to receive at max.
|
||||
) override; |
||||
|
||||
/// Adjusts book-keeping so that given number of samples are removed from beginning of the
|
||||
/// sample buffer without copying them anywhere.
|
||||
///
|
||||
/// Used to reduce the number of samples in the buffer when accessing the sample buffer directly
|
||||
/// with 'ptrBegin' function.
|
||||
uint receiveSamples(uint maxSamples ///< Remove this many samples from the beginning of pipe.
|
||||
) override; |
||||
|
||||
/// Clears all the samples in the object's output and internal processing
|
||||
/// buffers.
|
||||
void clear() override; |
||||
|
||||
/// Changes a setting controlling the processing system behaviour. See the
|
||||
/// 'SETTING_...' defines for available setting ID's.
|
||||
///
|
||||
/// \return 'true' if the setting was successfully changed
|
||||
bool setSetting(int settingId, ///< Setting ID number. see SETTING_... defines.
|
||||
int value ///< New setting value.
|
||||
); |
||||
|
||||
/// Reads a setting controlling the processing system behaviour. See the
|
||||
/// 'SETTING_...' defines for available setting ID's.
|
||||
///
|
||||
/// \return the setting value.
|
||||
int getSetting(int settingId ///< Setting ID number, see SETTING_... defines.
|
||||
) const; |
||||
|
||||
/// Returns number of samples currently unprocessed.
|
||||
uint numUnprocessedSamples() const; |
||||
|
||||
/// Return number of channels
|
||||
uint numChannels() const |
||||
{ |
||||
return channels; |
||||
} |
||||
|
||||
/// Other handy functions that are implemented in the ancestor classes (see
|
||||
/// classes 'FIFOProcessor' and 'FIFOSamplePipe')
|
||||
///
|
||||
/// - receiveSamples() : Use this function to receive 'ready' processed samples from SoundTouch.
|
||||
/// - numSamples() : Get number of 'ready' samples that can be received with
|
||||
/// function 'receiveSamples()'
|
||||
/// - isEmpty() : Returns nonzero if there aren't any 'ready' samples.
|
||||
/// - clear() : Clears all samples from ready/processing buffers.
|
||||
}; |
||||
|
||||
} |
||||
#endif |
||||
@ -1,177 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// SoundTouch DLL wrapper - wraps SoundTouch routines into a Dynamic Load
|
||||
/// Library interface.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _SoundTouchDLL_h_ |
||||
#define _SoundTouchDLL_h_ |
||||
|
||||
#if defined(_WIN32) || defined(WIN32) |
||||
// Windows
|
||||
#ifndef __cplusplus |
||||
#error "Expected g++" |
||||
#endif |
||||
|
||||
#ifdef DLL_EXPORTS |
||||
#define SOUNDTOUCHDLL_API extern "C" __declspec(dllexport) |
||||
#else |
||||
#define SOUNDTOUCHDLL_API extern "C" __declspec(dllimport) |
||||
#endif |
||||
|
||||
#else |
||||
// GNU version
|
||||
|
||||
#if defined(DLL_EXPORTS) || defined(SoundTouchDLL_EXPORTS) |
||||
// GCC declaration for exporting functions
|
||||
#define SOUNDTOUCHDLL_API extern "C" __attribute__((__visibility__("default"))) |
||||
#else |
||||
// import function
|
||||
#define SOUNDTOUCHDLL_API extern "C" |
||||
#endif |
||||
|
||||
// Linux-replacements for Windows declarations:
|
||||
#define __cdecl |
||||
typedef unsigned int DWORD; |
||||
#define FALSE 0 |
||||
#define TRUE 1 |
||||
|
||||
#endif |
||||
|
||||
typedef void * ST_HANDLE; |
||||
|
||||
/// Create a new instance of SoundTouch processor.
|
||||
SOUNDTOUCHDLL_API ST_HANDLE __cdecl soundtouch_createInstance(); |
||||
|
||||
/// Destroys a SoundTouch processor instance.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_destroyInstance(ST_HANDLE h); |
||||
|
||||
/// Get SoundTouch library version string
|
||||
SOUNDTOUCHDLL_API const char *__cdecl soundtouch_getVersionString(); |
||||
|
||||
/// Get SoundTouch library version Id
|
||||
SOUNDTOUCHDLL_API unsigned int __cdecl soundtouch_getVersionId(); |
||||
|
||||
/// Sets new rate control value. Normal rate = 1.0, smaller values
|
||||
/// represent slower rate, larger faster rates.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setRate(ST_HANDLE h, float newRate); |
||||
|
||||
/// Sets new tempo control value. Normal tempo = 1.0, smaller values
|
||||
/// represent slower tempo, larger faster tempo.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setTempo(ST_HANDLE h, float newTempo); |
||||
|
||||
/// Sets new rate control value as a difference in percents compared
|
||||
/// to the original rate (-50 .. +100 %);
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setRateChange(ST_HANDLE h, float newRate); |
||||
|
||||
/// Sets new tempo control value as a difference in percents compared
|
||||
/// to the original tempo (-50 .. +100 %);
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setTempoChange(ST_HANDLE h, float newTempo); |
||||
|
||||
/// Sets new pitch control value. Original pitch = 1.0, smaller values
|
||||
/// represent lower pitches, larger values higher pitch.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setPitch(ST_HANDLE h, float newPitch); |
||||
|
||||
/// Sets pitch change in octaves compared to the original pitch
|
||||
/// (-1.00 .. +1.00);
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setPitchOctaves(ST_HANDLE h, float newPitch); |
||||
|
||||
/// Sets pitch change in semi-tones compared to the original pitch
|
||||
/// (-12 .. +12);
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setPitchSemiTones(ST_HANDLE h, float newPitch); |
||||
|
||||
|
||||
/// Sets the number of channels, 1 = mono, 2 = stereo, n = multichannel
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setChannels(ST_HANDLE h, unsigned int numChannels); |
||||
|
||||
/// Sets sample rate.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setSampleRate(ST_HANDLE h, unsigned int srate); |
||||
|
||||
/// Flushes the last samples from the processing pipeline to the output.
|
||||
/// Clears also the internal processing buffers.
|
||||
//
|
||||
/// Note: This function is meant for extracting the last samples of a sound
|
||||
/// stream. This function may introduce additional blank samples in the end
|
||||
/// of the sound stream, and thus it's not recommended to call this function
|
||||
/// in the middle of a sound stream.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_flush(ST_HANDLE h); |
||||
|
||||
/// Adds 'numSamples' pcs of samples from the 'samples' memory position into
|
||||
/// the input of the object. Notice that sample rate _has_to_ be set before
|
||||
/// calling this function, otherwise throws a runtime_error exception.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_putSamples(ST_HANDLE h, |
||||
const float *samples, ///< Pointer to sample buffer.
|
||||
unsigned int numSamples ///< Number of sample frames in buffer. Notice
|
||||
///< that in case of multi-channel sound a single
|
||||
///< sample frame contains data for all channels.
|
||||
); |
||||
|
||||
/// Clears all the samples in the object's output and internal processing
|
||||
/// buffers.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_clear(ST_HANDLE h); |
||||
|
||||
/// Changes a setting controlling the processing system behaviour. See the
|
||||
/// 'SETTING_...' defines for available setting ID's.
|
||||
///
|
||||
/// \return 'nonzero' if the setting was successfully changed, otherwise zero
|
||||
SOUNDTOUCHDLL_API int __cdecl soundtouch_setSetting(ST_HANDLE h, |
||||
int settingId, ///< Setting ID number. see SETTING_... defines.
|
||||
int value ///< New setting value.
|
||||
); |
||||
|
||||
/// Reads a setting controlling the processing system behaviour. See the
|
||||
/// 'SETTING_...' defines for available setting ID's.
|
||||
///
|
||||
/// \return the setting value.
|
||||
SOUNDTOUCHDLL_API int __cdecl soundtouch_getSetting(ST_HANDLE h, |
||||
int settingId ///< Setting ID number, see SETTING_... defines.
|
||||
); |
||||
|
||||
|
||||
/// Returns number of samples currently unprocessed.
|
||||
SOUNDTOUCHDLL_API unsigned int __cdecl soundtouch_numUnprocessedSamples(ST_HANDLE h); |
||||
|
||||
/// Adjusts book-keeping so that given number of samples are removed from beginning of the
|
||||
/// sample buffer without copying them anywhere.
|
||||
///
|
||||
/// Used to reduce the number of samples in the buffer when accessing the sample buffer directly
|
||||
/// with 'ptrBegin' function.
|
||||
SOUNDTOUCHDLL_API unsigned int __cdecl soundtouch_receiveSamples(ST_HANDLE h, |
||||
float *outBuffer, ///< Buffer where to copy output samples.
|
||||
unsigned int maxSamples ///< How many samples to receive at max.
|
||||
); |
||||
|
||||
/// Returns number of samples currently available.
|
||||
SOUNDTOUCHDLL_API unsigned int __cdecl soundtouch_numSamples(ST_HANDLE h); |
||||
|
||||
/// Returns nonzero if there aren't any samples available for outputting.
|
||||
SOUNDTOUCHDLL_API int __cdecl soundtouch_isEmpty(ST_HANDLE h); |
||||
|
||||
#endif // _SoundTouchDLL_h_
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
# SoundTouch library |
||||
|
||||
## About |
||||
|
||||
SoundTouch is an open-source audio processing library that allows changing the sound tempo, pitch and playback rate parameters independently from each other: |
||||
* Change **tempo** while maintaining the original pitch |
||||
* Change **pitch** while maintaining the original tempo |
||||
* Change **playback rate** that affects both tempo and pitch at the |
||||
same time |
||||
* Change any combination of tempo/pitch/rate |
||||
|
||||
Visit [SoundTouch website](https://www.surina.net/soundtouch) and see the [README file](https://www.surina.net/soundtouch/readme.html) for more information and audio examples. |
||||
|
||||
### The latest stable release is 2.3.3 |
||||
|
||||
## Example |
||||
|
||||
Use SoundStretch example app for modifying wav audio files, for example as follows: |
||||
|
||||
``` |
||||
soundstretch my_original_file.wav output_file.wav -tempo=+15 -pitch=-3 |
||||
``` |
||||
|
||||
See the [README file](http://soundtouch.surina.net/README.html) for more usage examples and instructions how to build SoundTouch + SoundStretch. |
||||
|
||||
Ready [SoundStretch application executables](https://www.surina.net/soundtouch/download.html) are available for download for Windows and Mac OS. |
||||
|
||||
## Language & Platforms |
||||
|
||||
SoundTouch is written in C++ and compiles in virtually any platform: |
||||
* Windows |
||||
* Mac OS |
||||
* Linux & Unices (including also Raspberry, Beaglebone, Yocto etc embedded Linux flavors) |
||||
* Android |
||||
* iOS |
||||
* embedded systems |
||||
|
||||
The source code package includes dynamic library import modules for C#, Java and Pascal/Delphi languages. |
||||
|
||||
## Tarballs |
||||
|
||||
Source code release tarballs: |
||||
* https://www.surina.net/soundtouch/soundtouch-2.3.3.tar.gz |
||||
* https://www.surina.net/soundtouch/soundtouch-2.3.2.tar.gz |
||||
* https://www.surina.net/soundtouch/soundtouch-2.3.1.tar.gz |
||||
* https://www.surina.net/soundtouch/soundtouch-2.3.0.tar.gz |
||||
* https://www.surina.net/soundtouch/soundtouch-2.2.0.tar.gz |
||||
* https://www.surina.net/soundtouch/soundtouch-2.1.2.tar.gz |
||||
* https://www.surina.net/soundtouch/soundtouch-2.1.1.tar.gz |
||||
* https://www.surina.net/soundtouch/soundtouch-2.0.0.tar.gz |
||||
|
||||
## License |
||||
|
||||
SoundTouch is released under LGPL v2.1: |
||||
|
||||
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation. |
||||
|
||||
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
||||
|
||||
See [LGPL v2.1 full license text ](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) for details. |
||||
|
||||
-- |
||||
|
||||
Also commercial license free of GPL limitations available upon request |
||||
@ -1,222 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// FIR low-pass (anti-alias) filter with filter coefficient design routine and
|
||||
/// MMX optimization.
|
||||
///
|
||||
/// Anti-alias filter is used to prevent folding of high frequencies when
|
||||
/// transposing the sample rate with interpolation.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <memory.h> |
||||
#include <assert.h> |
||||
#include <math.h> |
||||
#include <stdlib.h> |
||||
#include "AAFilter.h" |
||||
#include "FIRFilter.h" |
||||
|
||||
using namespace soundtouch; |
||||
|
||||
#define PI 3.14159265358979323846 |
||||
#define TWOPI (2 * PI) |
||||
|
||||
// define this to save AA filter coefficients to a file
|
||||
// #define _DEBUG_SAVE_AAFILTER_COEFFICIENTS 1
|
||||
|
||||
#ifdef _DEBUG_SAVE_AAFILTER_COEFFICIENTS |
||||
#include <stdio.h> |
||||
|
||||
static void _DEBUG_SAVE_AAFIR_COEFFS(SAMPLETYPE *coeffs, int len) |
||||
{ |
||||
FILE *fptr = fopen("aa_filter_coeffs.txt", "wt"); |
||||
if (fptr == nullptr) return; |
||||
|
||||
for (int i = 0; i < len; i ++) |
||||
{ |
||||
double temp = coeffs[i]; |
||||
fprintf(fptr, "%lf\n", temp); |
||||
} |
||||
fclose(fptr); |
||||
} |
||||
|
||||
#else |
||||
#define _DEBUG_SAVE_AAFIR_COEFFS(x, y) |
||||
#endif |
||||
|
||||
/*****************************************************************************
|
||||
* |
||||
* Implementation of the class 'AAFilter' |
||||
* |
||||
*****************************************************************************/ |
||||
|
||||
AAFilter::AAFilter(uint len) |
||||
{ |
||||
pFIR = FIRFilter::newInstance(); |
||||
cutoffFreq = 0.5; |
||||
setLength(len); |
||||
} |
||||
|
||||
|
||||
AAFilter::~AAFilter() |
||||
{ |
||||
delete pFIR; |
||||
} |
||||
|
||||
|
||||
// Sets new anti-alias filter cut-off edge frequency, scaled to
|
||||
// sampling frequency (nyquist frequency = 0.5).
|
||||
// The filter will cut frequencies higher than the given frequency.
|
||||
void AAFilter::setCutoffFreq(double newCutoffFreq) |
||||
{ |
||||
cutoffFreq = newCutoffFreq; |
||||
calculateCoeffs(); |
||||
} |
||||
|
||||
|
||||
// Sets number of FIR filter taps
|
||||
void AAFilter::setLength(uint newLength) |
||||
{ |
||||
length = newLength; |
||||
calculateCoeffs(); |
||||
} |
||||
|
||||
|
||||
// Calculates coefficients for a low-pass FIR filter using Hamming window
|
||||
void AAFilter::calculateCoeffs() |
||||
{ |
||||
uint i; |
||||
double cntTemp, temp, tempCoeff,h, w; |
||||
double wc; |
||||
double scaleCoeff, sum; |
||||
double *work; |
||||
SAMPLETYPE *coeffs; |
||||
|
||||
assert(length >= 2); |
||||
assert(length % 4 == 0); |
||||
assert(cutoffFreq >= 0); |
||||
assert(cutoffFreq <= 0.5); |
||||
|
||||
work = new double[length]; |
||||
coeffs = new SAMPLETYPE[length]; |
||||
|
||||
wc = 2.0 * PI * cutoffFreq; |
||||
tempCoeff = TWOPI / (double)length; |
||||
|
||||
sum = 0; |
||||
for (i = 0; i < length; i ++) |
||||
{ |
||||
cntTemp = (double)i - (double)(length / 2); |
||||
|
||||
temp = cntTemp * wc; |
||||
if (temp != 0) |
||||
{ |
||||
h = sin(temp) / temp; // sinc function
|
||||
} |
||||
else |
||||
{ |
||||
h = 1.0; |
||||
} |
||||
w = 0.54 + 0.46 * cos(tempCoeff * cntTemp); // hamming window
|
||||
|
||||
temp = w * h; |
||||
work[i] = temp; |
||||
|
||||
// calc net sum of coefficients
|
||||
sum += temp; |
||||
} |
||||
|
||||
// ensure the sum of coefficients is larger than zero
|
||||
assert(sum > 0); |
||||
|
||||
// ensure we've really designed a lowpass filter...
|
||||
assert(work[length/2] > 0); |
||||
assert(work[length/2 + 1] > -1e-6); |
||||
assert(work[length/2 - 1] > -1e-6); |
||||
|
||||
// Calculate a scaling coefficient in such a way that the result can be
|
||||
// divided by 16384
|
||||
scaleCoeff = 16384.0f / sum; |
||||
|
||||
for (i = 0; i < length; i ++) |
||||
{ |
||||
temp = work[i] * scaleCoeff; |
||||
// scale & round to nearest integer
|
||||
temp += (temp >= 0) ? 0.5 : -0.5; |
||||
// ensure no overfloods
|
||||
assert(temp >= -32768 && temp <= 32767); |
||||
coeffs[i] = (SAMPLETYPE)temp; |
||||
} |
||||
|
||||
// Set coefficients. Use divide factor 14 => divide result by 2^14 = 16384
|
||||
pFIR->setCoefficients(coeffs, length, 14); |
||||
|
||||
_DEBUG_SAVE_AAFIR_COEFFS(coeffs, length); |
||||
|
||||
delete[] work; |
||||
delete[] coeffs; |
||||
} |
||||
|
||||
|
||||
// Applies the filter to the given sequence of samples.
|
||||
// Note : The amount of outputted samples is by value of 'filter length'
|
||||
// smaller than the amount of input samples.
|
||||
uint AAFilter::evaluate(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples, uint numChannels) const |
||||
{ |
||||
return pFIR->evaluate(dest, src, numSamples, numChannels); |
||||
} |
||||
|
||||
|
||||
/// Applies the filter to the given src & dest pipes, so that processed amount of
|
||||
/// samples get removed from src, and produced amount added to dest
|
||||
/// Note : The amount of outputted samples is by value of 'filter length'
|
||||
/// smaller than the amount of input samples.
|
||||
uint AAFilter::evaluate(FIFOSampleBuffer &dest, FIFOSampleBuffer &src) const |
||||
{ |
||||
SAMPLETYPE *pdest; |
||||
const SAMPLETYPE *psrc; |
||||
uint numSrcSamples; |
||||
uint result; |
||||
int numChannels = src.getChannels(); |
||||
|
||||
assert(numChannels == dest.getChannels()); |
||||
|
||||
numSrcSamples = src.numSamples(); |
||||
psrc = src.ptrBegin(); |
||||
pdest = dest.ptrEnd(numSrcSamples); |
||||
result = pFIR->evaluate(pdest, psrc, numSrcSamples, numChannels); |
||||
src.receiveSamples(result); |
||||
dest.putSamples(result); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
|
||||
uint AAFilter::getLength() const |
||||
{ |
||||
return pFIR->getLength(); |
||||
} |
||||
@ -1,93 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo
|
||||
/// while maintaining the original pitch by using a time domain WSOLA-like method
|
||||
/// with several performance-increasing tweaks.
|
||||
///
|
||||
/// Anti-alias filter is used to prevent folding of high frequencies when
|
||||
/// transposing the sample rate with interpolation.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef AAFilter_H |
||||
#define AAFilter_H |
||||
|
||||
#include "STTypes.h" |
||||
#include "FIFOSampleBuffer.h" |
||||
|
||||
namespace soundtouch |
||||
{ |
||||
|
||||
class AAFilter |
||||
{ |
||||
protected: |
||||
class FIRFilter *pFIR; |
||||
|
||||
/// Low-pass filter cut-off frequency, negative = invalid
|
||||
double cutoffFreq; |
||||
|
||||
/// num of filter taps
|
||||
uint length; |
||||
|
||||
/// Calculate the FIR coefficients realizing the given cutoff-frequency
|
||||
void calculateCoeffs(); |
||||
public: |
||||
AAFilter(uint length); |
||||
|
||||
~AAFilter(); |
||||
|
||||
/// Sets new anti-alias filter cut-off edge frequency, scaled to sampling
|
||||
/// frequency (nyquist frequency = 0.5). The filter will cut off the
|
||||
/// frequencies than that.
|
||||
void setCutoffFreq(double newCutoffFreq); |
||||
|
||||
/// Sets number of FIR filter taps, i.e. ~filter complexity
|
||||
void setLength(uint newLength); |
||||
|
||||
uint getLength() const; |
||||
|
||||
/// Applies the filter to the given sequence of samples.
|
||||
/// Note : The amount of outputted samples is by value of 'filter length'
|
||||
/// smaller than the amount of input samples.
|
||||
uint evaluate(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
uint numSamples, |
||||
uint numChannels) const; |
||||
|
||||
/// Applies the filter to the given src & dest pipes, so that processed amount of
|
||||
/// samples get removed from src, and produced amount added to dest
|
||||
/// Note : The amount of outputted samples is by value of 'filter length'
|
||||
/// smaller than the amount of input samples.
|
||||
uint evaluate(FIFOSampleBuffer &dest, |
||||
FIFOSampleBuffer &src) const; |
||||
|
||||
}; |
||||
|
||||
} |
||||
|
||||
#endif |
||||
@ -1,569 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Beats-per-minute (BPM) detection routine.
|
||||
///
|
||||
/// The beat detection algorithm works as follows:
|
||||
/// - Use function 'inputSamples' to input a chunks of samples to the class for
|
||||
/// analysis. It's a good idea to enter a large sound file or stream in smallish
|
||||
/// chunks of around few kilosamples in order not to extinguish too much RAM memory.
|
||||
/// - Inputted sound data is decimated to approx 500 Hz to reduce calculation burden,
|
||||
/// which is basically ok as low (bass) frequencies mostly determine the beat rate.
|
||||
/// Simple averaging is used for anti-alias filtering because the resulting signal
|
||||
/// quality isn't of that high importance.
|
||||
/// - Decimated sound data is enveloped, i.e. the amplitude shape is detected by
|
||||
/// taking absolute value that's smoothed by sliding average. Signal levels that
|
||||
/// are below a couple of times the general RMS amplitude level are cut away to
|
||||
/// leave only notable peaks there.
|
||||
/// - Repeating sound patterns (e.g. beats) are detected by calculating short-term
|
||||
/// autocorrelation function of the enveloped signal.
|
||||
/// - After whole sound data file has been analyzed as above, the bpm level is
|
||||
/// detected by function 'getBpm' that finds the highest peak of the autocorrelation
|
||||
/// function, calculates it's precise location and converts this reading to bpm's.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define _USE_MATH_DEFINES |
||||
|
||||
#include <math.h> |
||||
#include <assert.h> |
||||
#include <string.h> |
||||
#include <stdio.h> |
||||
#include <cfloat> |
||||
#include "FIFOSampleBuffer.h" |
||||
#include "PeakFinder.h" |
||||
#include "BPMDetect.h" |
||||
|
||||
using namespace soundtouch; |
||||
|
||||
// algorithm input sample block size
|
||||
static const int INPUT_BLOCK_SIZE = 2048; |
||||
|
||||
// decimated sample block size
|
||||
static const int DECIMATED_BLOCK_SIZE = 256; |
||||
|
||||
/// Target sample rate after decimation
|
||||
static const int TARGET_SRATE = 1000; |
||||
|
||||
/// XCorr update sequence size, update in about 200msec chunks
|
||||
static const int XCORR_UPDATE_SEQUENCE = (int)(TARGET_SRATE / 5); |
||||
|
||||
/// Moving average N size
|
||||
static const int MOVING_AVERAGE_N = 15; |
||||
|
||||
/// XCorr decay time constant, decay to half in 30 seconds
|
||||
/// If it's desired to have the system adapt quicker to beat rate
|
||||
/// changes within a continuing music stream, then the
|
||||
/// 'xcorr_decay_time_constant' value can be reduced, yet that
|
||||
/// can increase possibility of glitches in bpm detection.
|
||||
static const double XCORR_DECAY_TIME_CONSTANT = 30.0; |
||||
|
||||
/// Data overlap factor for beat detection algorithm
|
||||
static const int OVERLAP_FACTOR = 4; |
||||
|
||||
static const double TWOPI = (2 * M_PI); |
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Enable following define to create bpm analysis file:
|
||||
|
||||
//#define _CREATE_BPM_DEBUG_FILE
|
||||
|
||||
#ifdef _CREATE_BPM_DEBUG_FILE |
||||
|
||||
static void _SaveDebugData(const char *name, const float *data, int minpos, int maxpos, double coeff) |
||||
{ |
||||
FILE *fptr = fopen(name, "wt"); |
||||
int i; |
||||
|
||||
if (fptr) |
||||
{ |
||||
printf("\nWriting BPM debug data into file %s\n", name); |
||||
for (i = minpos; i < maxpos; i ++) |
||||
{ |
||||
fprintf(fptr, "%d\t%.1lf\t%f\n", i, coeff / (double)i, data[i]); |
||||
} |
||||
fclose(fptr); |
||||
} |
||||
} |
||||
|
||||
void _SaveDebugBeatPos(const char *name, const std::vector<BEAT> &beats) |
||||
{ |
||||
printf("\nWriting beat detections data into file %s\n", name); |
||||
|
||||
FILE *fptr = fopen(name, "wt"); |
||||
if (fptr) |
||||
{ |
||||
for (uint i = 0; i < beats.size(); i++) |
||||
{ |
||||
BEAT b = beats[i]; |
||||
fprintf(fptr, "%lf\t%lf\n", b.pos, b.strength); |
||||
} |
||||
fclose(fptr); |
||||
} |
||||
} |
||||
#else |
||||
#define _SaveDebugData(name, a,b,c,d) |
||||
#define _SaveDebugBeatPos(name, b) |
||||
#endif |
||||
|
||||
// Hamming window
|
||||
void hamming(float *w, int N) |
||||
{ |
||||
for (int i = 0; i < N; i++) |
||||
{ |
||||
w[i] = (float)(0.54 - 0.46 * cos(TWOPI * i / (N - 1))); |
||||
} |
||||
|
||||
} |
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IIR2_filter - 2nd order IIR filter
|
||||
|
||||
IIR2_filter::IIR2_filter(const double *lpf_coeffs) |
||||
{ |
||||
memcpy(coeffs, lpf_coeffs, 5 * sizeof(double)); |
||||
memset(prev, 0, sizeof(prev)); |
||||
} |
||||
|
||||
|
||||
float IIR2_filter::update(float x) |
||||
{ |
||||
prev[0] = x; |
||||
double y = x * coeffs[0]; |
||||
|
||||
for (int i = 4; i >= 1; i--) |
||||
{ |
||||
y += coeffs[i] * prev[i]; |
||||
prev[i] = prev[i - 1]; |
||||
} |
||||
|
||||
prev[3] = y; |
||||
return (float)y; |
||||
} |
||||
|
||||
|
||||
// IIR low-pass filter coefficients, calculated with matlab/octave cheby2(2,40,0.05)
|
||||
const double _LPF_coeffs[5] = { 0.00996655391939, -0.01944529148401, 0.00996655391939, 1.96867605796247, -0.96916387431724 }; |
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
BPMDetect::BPMDetect(int numChannels, int aSampleRate) : |
||||
beat_lpf(_LPF_coeffs) |
||||
{ |
||||
beats.reserve(250); // initial reservation to prevent frequent reallocation
|
||||
|
||||
this->sampleRate = aSampleRate; |
||||
this->channels = numChannels; |
||||
|
||||
decimateSum = 0; |
||||
decimateCount = 0; |
||||
|
||||
// choose decimation factor so that result is approx. 1000 Hz
|
||||
decimateBy = sampleRate / TARGET_SRATE; |
||||
if ((decimateBy <= 0) || (decimateBy * DECIMATED_BLOCK_SIZE < INPUT_BLOCK_SIZE)) |
||||
{ |
||||
ST_THROW_RT_ERROR("Too small samplerate"); |
||||
} |
||||
|
||||
// Calculate window length & starting item according to desired min & max bpms
|
||||
windowLen = (60 * sampleRate) / (decimateBy * MIN_BPM); |
||||
windowStart = (60 * sampleRate) / (decimateBy * MAX_BPM_RANGE); |
||||
|
||||
assert(windowLen > windowStart); |
||||
|
||||
// allocate new working objects
|
||||
xcorr = new float[windowLen]; |
||||
memset(xcorr, 0, windowLen * sizeof(float)); |
||||
|
||||
pos = 0; |
||||
peakPos = 0; |
||||
peakVal = 0; |
||||
init_scaler = 1; |
||||
beatcorr_ringbuffpos = 0; |
||||
beatcorr_ringbuff = new float[windowLen]; |
||||
memset(beatcorr_ringbuff, 0, windowLen * sizeof(float)); |
||||
|
||||
// allocate processing buffer
|
||||
buffer = new FIFOSampleBuffer(); |
||||
// we do processing in mono mode
|
||||
buffer->setChannels(1); |
||||
buffer->clear(); |
||||
|
||||
// calculate hamming windows
|
||||
hamw = new float[XCORR_UPDATE_SEQUENCE]; |
||||
hamming(hamw, XCORR_UPDATE_SEQUENCE); |
||||
hamw2 = new float[XCORR_UPDATE_SEQUENCE / 2]; |
||||
hamming(hamw2, XCORR_UPDATE_SEQUENCE / 2); |
||||
} |
||||
|
||||
|
||||
BPMDetect::~BPMDetect() |
||||
{ |
||||
delete[] xcorr; |
||||
delete[] beatcorr_ringbuff; |
||||
delete[] hamw; |
||||
delete[] hamw2; |
||||
delete buffer; |
||||
} |
||||
|
||||
|
||||
/// convert to mono, low-pass filter & decimate to about 500 Hz.
|
||||
/// return number of outputted samples.
|
||||
///
|
||||
/// Decimation is used to remove the unnecessary frequencies and thus to reduce
|
||||
/// the amount of data needed to be processed as calculating autocorrelation
|
||||
/// function is a very-very heavy operation.
|
||||
///
|
||||
/// Anti-alias filtering is done simply by averaging the samples. This is really a
|
||||
/// poor-man's anti-alias filtering, but it's not so critical in this kind of application
|
||||
/// (it'd also be difficult to design a high-quality filter with steep cut-off at very
|
||||
/// narrow band)
|
||||
int BPMDetect::decimate(SAMPLETYPE *dest, const SAMPLETYPE *src, int numsamples) |
||||
{ |
||||
int count, outcount; |
||||
LONG_SAMPLETYPE out; |
||||
|
||||
assert(channels > 0); |
||||
assert(decimateBy > 0); |
||||
outcount = 0; |
||||
for (count = 0; count < numsamples; count ++) |
||||
{ |
||||
int j; |
||||
|
||||
// convert to mono and accumulate
|
||||
for (j = 0; j < channels; j ++) |
||||
{ |
||||
decimateSum += src[j]; |
||||
} |
||||
src += j; |
||||
|
||||
decimateCount ++; |
||||
if (decimateCount >= decimateBy) |
||||
{ |
||||
// Store every Nth sample only
|
||||
out = (LONG_SAMPLETYPE)(decimateSum / (decimateBy * channels)); |
||||
decimateSum = 0; |
||||
decimateCount = 0; |
||||
#ifdef SOUNDTOUCH_INTEGER_SAMPLES |
||||
// check ranges for sure (shouldn't actually be necessary)
|
||||
if (out > 32767) |
||||
{ |
||||
out = 32767; |
||||
} |
||||
else if (out < -32768) |
||||
{ |
||||
out = -32768; |
||||
} |
||||
#endif // SOUNDTOUCH_INTEGER_SAMPLES
|
||||
dest[outcount] = (SAMPLETYPE)out; |
||||
outcount ++; |
||||
} |
||||
} |
||||
return outcount; |
||||
} |
||||
|
||||
|
||||
// Calculates autocorrelation function of the sample history buffer
|
||||
void BPMDetect::updateXCorr(int process_samples) |
||||
{ |
||||
int offs; |
||||
SAMPLETYPE *pBuffer; |
||||
|
||||
assert(buffer->numSamples() >= (uint)(process_samples + windowLen)); |
||||
assert(process_samples == XCORR_UPDATE_SEQUENCE); |
||||
|
||||
pBuffer = buffer->ptrBegin(); |
||||
|
||||
// calculate decay factor for xcorr filtering
|
||||
float xcorr_decay = (float)pow(0.5, 1.0 / (XCORR_DECAY_TIME_CONSTANT * TARGET_SRATE / process_samples)); |
||||
|
||||
// prescale pbuffer
|
||||
float tmp[XCORR_UPDATE_SEQUENCE]; |
||||
for (int i = 0; i < process_samples; i++) |
||||
{ |
||||
tmp[i] = hamw[i] * hamw[i] * pBuffer[i]; |
||||
} |
||||
|
||||
for (offs = windowStart; offs < windowLen; offs ++) |
||||
{ |
||||
float sum; |
||||
int i; |
||||
|
||||
sum = 0; |
||||
for (i = 0; i < process_samples; i ++) |
||||
{ |
||||
sum += tmp[i] * pBuffer[i + offs]; // scaling the sub-result shouldn't be necessary
|
||||
} |
||||
xcorr[offs] *= xcorr_decay; // decay 'xcorr' here with suitable time constant.
|
||||
|
||||
xcorr[offs] += (float)fabs(sum); |
||||
} |
||||
} |
||||
|
||||
|
||||
// Detect individual beat positions
|
||||
void BPMDetect::updateBeatPos(int process_samples) |
||||
{ |
||||
SAMPLETYPE *pBuffer; |
||||
|
||||
assert(buffer->numSamples() >= (uint)(process_samples + windowLen)); |
||||
|
||||
pBuffer = buffer->ptrBegin(); |
||||
assert(process_samples == XCORR_UPDATE_SEQUENCE / 2); |
||||
|
||||
// static double thr = 0.0003;
|
||||
double posScale = (double)this->decimateBy / (double)this->sampleRate; |
||||
int resetDur = (int)(0.12 / posScale + 0.5); |
||||
|
||||
// prescale pbuffer
|
||||
float tmp[XCORR_UPDATE_SEQUENCE / 2]; |
||||
for (int i = 0; i < process_samples; i++) |
||||
{ |
||||
tmp[i] = hamw2[i] * hamw2[i] * pBuffer[i]; |
||||
} |
||||
|
||||
for (int offs = windowStart; offs < windowLen; offs++) |
||||
{ |
||||
float sum = 0; |
||||
for (int i = 0; i < process_samples; i++) |
||||
{ |
||||
sum += tmp[i] * pBuffer[offs + i]; |
||||
} |
||||
beatcorr_ringbuff[(beatcorr_ringbuffpos + offs) % windowLen] += (float)((sum > 0) ? sum : 0); // accumulate only positive correlations
|
||||
} |
||||
|
||||
int skipstep = XCORR_UPDATE_SEQUENCE / OVERLAP_FACTOR; |
||||
|
||||
// compensate empty buffer at beginning by scaling coefficient
|
||||
float scale = (float)windowLen / (float)(skipstep * init_scaler); |
||||
if (scale > 1.0f) |
||||
{ |
||||
init_scaler++; |
||||
} |
||||
else |
||||
{ |
||||
scale = 1.0f; |
||||
} |
||||
|
||||
// detect beats
|
||||
for (int i = 0; i < skipstep; i++) |
||||
{ |
||||
float sum = beatcorr_ringbuff[beatcorr_ringbuffpos]; |
||||
sum -= beat_lpf.update(sum); |
||||
|
||||
if (sum > peakVal) |
||||
{ |
||||
// found new local largest value
|
||||
peakVal = sum; |
||||
peakPos = pos; |
||||
} |
||||
if (pos > peakPos + resetDur) |
||||
{ |
||||
// largest value not updated for 200msec => accept as beat
|
||||
peakPos += skipstep; |
||||
if (peakVal > 0) |
||||
{ |
||||
// add detected beat to end of "beats" vector
|
||||
BEAT temp = { (float)(peakPos * posScale), (float)(peakVal * scale) }; |
||||
beats.push_back(temp); |
||||
} |
||||
|
||||
peakVal = 0; |
||||
peakPos = pos; |
||||
} |
||||
|
||||
beatcorr_ringbuff[beatcorr_ringbuffpos] = 0; |
||||
pos++; |
||||
beatcorr_ringbuffpos = (beatcorr_ringbuffpos + 1) % windowLen; |
||||
} |
||||
} |
||||
|
||||
|
||||
#define max(x,y) ((x) > (y) ? (x) : (y)) |
||||
|
||||
void BPMDetect::inputSamples(const SAMPLETYPE *samples, int numSamples) |
||||
{ |
||||
SAMPLETYPE decimated[DECIMATED_BLOCK_SIZE]; |
||||
|
||||
// iterate so that max INPUT_BLOCK_SAMPLES processed per iteration
|
||||
while (numSamples > 0) |
||||
{ |
||||
int block; |
||||
int decSamples; |
||||
|
||||
block = (numSamples > INPUT_BLOCK_SIZE) ? INPUT_BLOCK_SIZE : numSamples; |
||||
|
||||
// decimate. note that converts to mono at the same time
|
||||
decSamples = decimate(decimated, samples, block); |
||||
samples += block * channels; |
||||
numSamples -= block; |
||||
|
||||
buffer->putSamples(decimated, decSamples); |
||||
} |
||||
|
||||
// when the buffer has enough samples for processing...
|
||||
int req = max(windowLen + XCORR_UPDATE_SEQUENCE, 2 * XCORR_UPDATE_SEQUENCE); |
||||
while ((int)buffer->numSamples() >= req) |
||||
{ |
||||
// ... update autocorrelations...
|
||||
updateXCorr(XCORR_UPDATE_SEQUENCE); |
||||
// ...update beat position calculation...
|
||||
updateBeatPos(XCORR_UPDATE_SEQUENCE / 2); |
||||
// ... and remove proceessed samples from the buffer
|
||||
int n = XCORR_UPDATE_SEQUENCE / OVERLAP_FACTOR; |
||||
buffer->receiveSamples(n); |
||||
} |
||||
} |
||||
|
||||
|
||||
void BPMDetect::removeBias() |
||||
{ |
||||
int i; |
||||
|
||||
// Remove linear bias: calculate linear regression coefficient
|
||||
// 1. calc mean of 'xcorr' and 'i'
|
||||
double mean_i = 0; |
||||
double mean_x = 0; |
||||
for (i = windowStart; i < windowLen; i++) |
||||
{ |
||||
mean_x += xcorr[i]; |
||||
} |
||||
mean_x /= (windowLen - windowStart); |
||||
mean_i = 0.5 * (windowLen - 1 + windowStart); |
||||
|
||||
// 2. calculate linear regression coefficient
|
||||
double b = 0; |
||||
double div = 0; |
||||
for (i = windowStart; i < windowLen; i++) |
||||
{ |
||||
double xt = xcorr[i] - mean_x; |
||||
double xi = i - mean_i; |
||||
b += xt * xi; |
||||
div += xi * xi; |
||||
} |
||||
b /= div; |
||||
|
||||
// subtract linear regression and resolve min. value bias
|
||||
float minval = FLT_MAX; // arbitrary large number
|
||||
for (i = windowStart; i < windowLen; i ++) |
||||
{ |
||||
xcorr[i] -= (float)(b * i); |
||||
if (xcorr[i] < minval) |
||||
{ |
||||
minval = xcorr[i]; |
||||
} |
||||
} |
||||
|
||||
// subtract min.value
|
||||
for (i = windowStart; i < windowLen; i ++) |
||||
{ |
||||
xcorr[i] -= minval; |
||||
} |
||||
} |
||||
|
||||
|
||||
// Calculate N-point moving average for "source" values
|
||||
void MAFilter(float *dest, const float *source, int start, int end, int N) |
||||
{ |
||||
for (int i = start; i < end; i++) |
||||
{ |
||||
int i1 = i - N / 2; |
||||
int i2 = i + N / 2 + 1; |
||||
if (i1 < start) i1 = start; |
||||
if (i2 > end) i2 = end; |
||||
|
||||
double sum = 0; |
||||
for (int j = i1; j < i2; j ++) |
||||
{ |
||||
sum += source[j]; |
||||
} |
||||
dest[i] = (float)(sum / (i2 - i1)); |
||||
} |
||||
} |
||||
|
||||
|
||||
float BPMDetect::getBpm() |
||||
{ |
||||
double peakPos; |
||||
double coeff; |
||||
PeakFinder peakFinder; |
||||
|
||||
// remove bias from xcorr data
|
||||
removeBias(); |
||||
|
||||
coeff = 60.0 * ((double)sampleRate / (double)decimateBy); |
||||
|
||||
// save bpm debug data if debug data writing enabled
|
||||
_SaveDebugData("soundtouch-bpm-xcorr.txt", xcorr, windowStart, windowLen, coeff); |
||||
|
||||
// Smoothen by N-point moving-average
|
||||
float *data = new float[windowLen]; |
||||
memset(data, 0, sizeof(float) * windowLen); |
||||
MAFilter(data, xcorr, windowStart, windowLen, MOVING_AVERAGE_N); |
||||
|
||||
// find peak position
|
||||
peakPos = peakFinder.detectPeak(data, windowStart, windowLen); |
||||
|
||||
// save bpm debug data if debug data writing enabled
|
||||
_SaveDebugData("soundtouch-bpm-smoothed.txt", data, windowStart, windowLen, coeff); |
||||
|
||||
delete[] data; |
||||
|
||||
assert(decimateBy != 0); |
||||
if (peakPos < 1e-9) return 0.0; // detection failed.
|
||||
|
||||
_SaveDebugBeatPos("soundtouch-detected-beats.txt", beats); |
||||
|
||||
// calculate BPM
|
||||
float bpm = (float)(coeff / peakPos); |
||||
return (bpm >= MIN_BPM && bpm <= MAX_BPM_VALID) ? bpm : 0; |
||||
} |
||||
|
||||
|
||||
/// Get beat position arrays. Note: The array includes also really low beat detection values
|
||||
/// in absence of clear strong beats. Consumer may wish to filter low values away.
|
||||
/// - "pos" receive array of beat positions
|
||||
/// - "values" receive array of beat detection strengths
|
||||
/// - max_num indicates max.size of "pos" and "values" array.
|
||||
///
|
||||
/// You can query a suitable array sized by calling this with nullptr in "pos" & "values".
|
||||
///
|
||||
/// \return number of beats in the arrays.
|
||||
int BPMDetect::getBeats(float *pos, float *values, int max_num) |
||||
{ |
||||
int num = (int)beats.size(); |
||||
if ((!pos) || (!values)) return num; // pos or values nullptr, return just size
|
||||
|
||||
for (int i = 0; (i < num) && (i < max_num); i++) |
||||
{ |
||||
pos[i] = beats[i].pos; |
||||
values[i] = beats[i].strength; |
||||
} |
||||
return num; |
||||
} |
||||
@ -1,275 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// A buffer class for temporarily storaging sound samples, operates as a
|
||||
/// first-in-first-out pipe.
|
||||
///
|
||||
/// Samples are added to the end of the sample buffer with the 'putSamples'
|
||||
/// function, and are received from the beginning of the buffer by calling
|
||||
/// the 'receiveSamples' function. The class automatically removes the
|
||||
/// outputted samples from the buffer, as well as grows the buffer size
|
||||
/// whenever necessary.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <stdlib.h> |
||||
#include <memory.h> |
||||
#include <string.h> |
||||
#include <assert.h> |
||||
|
||||
#include "FIFOSampleBuffer.h" |
||||
|
||||
using namespace soundtouch; |
||||
|
||||
// Constructor
|
||||
FIFOSampleBuffer::FIFOSampleBuffer(int numChannels) |
||||
{ |
||||
assert(numChannels > 0); |
||||
sizeInBytes = 0; // reasonable initial value
|
||||
buffer = nullptr; |
||||
bufferUnaligned = nullptr; |
||||
samplesInBuffer = 0; |
||||
bufferPos = 0; |
||||
channels = (uint)numChannels; |
||||
ensureCapacity(32); // allocate initial capacity
|
||||
} |
||||
|
||||
|
||||
// destructor
|
||||
FIFOSampleBuffer::~FIFOSampleBuffer() |
||||
{ |
||||
delete[] bufferUnaligned; |
||||
bufferUnaligned = nullptr; |
||||
buffer = nullptr; |
||||
} |
||||
|
||||
|
||||
// Sets number of channels, 1 = mono, 2 = stereo
|
||||
void FIFOSampleBuffer::setChannels(int numChannels) |
||||
{ |
||||
uint usedBytes; |
||||
|
||||
if (!verifyNumberOfChannels(numChannels)) return; |
||||
|
||||
usedBytes = channels * samplesInBuffer; |
||||
channels = (uint)numChannels; |
||||
samplesInBuffer = usedBytes / channels; |
||||
} |
||||
|
||||
|
||||
// if output location pointer 'bufferPos' isn't zero, 'rewinds' the buffer and
|
||||
// zeroes this pointer by copying samples from the 'bufferPos' pointer
|
||||
// location on to the beginning of the buffer.
|
||||
void FIFOSampleBuffer::rewind() |
||||
{ |
||||
if (buffer && bufferPos) |
||||
{ |
||||
memmove(buffer, ptrBegin(), sizeof(SAMPLETYPE) * channels * samplesInBuffer); |
||||
bufferPos = 0; |
||||
} |
||||
} |
||||
|
||||
|
||||
// Adds 'numSamples' pcs of samples from the 'samples' memory position to
|
||||
// the sample buffer.
|
||||
void FIFOSampleBuffer::putSamples(const SAMPLETYPE *samples, uint nSamples) |
||||
{ |
||||
memcpy(ptrEnd(nSamples), samples, sizeof(SAMPLETYPE) * nSamples * channels); |
||||
samplesInBuffer += nSamples; |
||||
} |
||||
|
||||
|
||||
// Increases the number of samples in the buffer without copying any actual
|
||||
// samples.
|
||||
//
|
||||
// This function is used to update the number of samples in the sample buffer
|
||||
// when accessing the buffer directly with 'ptrEnd' function. Please be
|
||||
// careful though!
|
||||
void FIFOSampleBuffer::putSamples(uint nSamples) |
||||
{ |
||||
uint req; |
||||
|
||||
req = samplesInBuffer + nSamples; |
||||
ensureCapacity(req); |
||||
samplesInBuffer += nSamples; |
||||
} |
||||
|
||||
|
||||
// Returns a pointer to the end of the used part of the sample buffer (i.e.
|
||||
// where the new samples are to be inserted). This function may be used for
|
||||
// inserting new samples into the sample buffer directly. Please be careful!
|
||||
//
|
||||
// Parameter 'slackCapacity' tells the function how much free capacity (in
|
||||
// terms of samples) there _at least_ should be, in order to the caller to
|
||||
// successfully insert all the required samples to the buffer. When necessary,
|
||||
// the function grows the buffer size to comply with this requirement.
|
||||
//
|
||||
// When using this function as means for inserting new samples, also remember
|
||||
// to increase the sample count afterwards, by calling the
|
||||
// 'putSamples(numSamples)' function.
|
||||
SAMPLETYPE *FIFOSampleBuffer::ptrEnd(uint slackCapacity) |
||||
{ |
||||
ensureCapacity(samplesInBuffer + slackCapacity); |
||||
return buffer + samplesInBuffer * channels; |
||||
} |
||||
|
||||
|
||||
// Returns a pointer to the beginning of the currently non-outputted samples.
|
||||
// This function is provided for accessing the output samples directly.
|
||||
// Please be careful!
|
||||
//
|
||||
// When using this function to output samples, also remember to 'remove' the
|
||||
// outputted samples from the buffer by calling the
|
||||
// 'receiveSamples(numSamples)' function
|
||||
SAMPLETYPE *FIFOSampleBuffer::ptrBegin() |
||||
{ |
||||
assert(buffer); |
||||
return buffer + bufferPos * channels; |
||||
} |
||||
|
||||
|
||||
// Ensures that the buffer has enough capacity, i.e. space for _at least_
|
||||
// 'capacityRequirement' number of samples. The buffer is grown in steps of
|
||||
// 4 kilobytes to eliminate the need for frequently growing up the buffer,
|
||||
// as well as to round the buffer size up to the virtual memory page size.
|
||||
void FIFOSampleBuffer::ensureCapacity(uint capacityRequirement) |
||||
{ |
||||
SAMPLETYPE *tempUnaligned, *temp; |
||||
|
||||
if (capacityRequirement > getCapacity()) |
||||
{ |
||||
// enlarge the buffer in 4kbyte steps (round up to next 4k boundary)
|
||||
sizeInBytes = (capacityRequirement * channels * sizeof(SAMPLETYPE) + 4095) & (uint)-4096; |
||||
assert(sizeInBytes % 2 == 0); |
||||
tempUnaligned = new SAMPLETYPE[sizeInBytes / sizeof(SAMPLETYPE) + 16 / sizeof(SAMPLETYPE)]; |
||||
if (tempUnaligned == nullptr) |
||||
{ |
||||
ST_THROW_RT_ERROR("Couldn't allocate memory!\n"); |
||||
} |
||||
// Align the buffer to begin at 16byte cache line boundary for optimal performance
|
||||
temp = (SAMPLETYPE *)SOUNDTOUCH_ALIGN_POINTER_16(tempUnaligned); |
||||
if (samplesInBuffer) |
||||
{ |
||||
memcpy(temp, ptrBegin(), samplesInBuffer * channels * sizeof(SAMPLETYPE)); |
||||
} |
||||
delete[] bufferUnaligned; |
||||
buffer = temp; |
||||
bufferUnaligned = tempUnaligned; |
||||
bufferPos = 0; |
||||
} |
||||
else |
||||
{ |
||||
// simply rewind the buffer (if necessary)
|
||||
rewind(); |
||||
} |
||||
} |
||||
|
||||
|
||||
// Returns the current buffer capacity in terms of samples
|
||||
uint FIFOSampleBuffer::getCapacity() const |
||||
{ |
||||
return sizeInBytes / (channels * sizeof(SAMPLETYPE)); |
||||
} |
||||
|
||||
|
||||
// Returns the number of samples currently in the buffer
|
||||
uint FIFOSampleBuffer::numSamples() const |
||||
{ |
||||
return samplesInBuffer; |
||||
} |
||||
|
||||
|
||||
// Output samples from beginning of the sample buffer. Copies demanded number
|
||||
// of samples to output and removes them from the sample buffer. If there
|
||||
// are less than 'numsample' samples in the buffer, returns all available.
|
||||
//
|
||||
// Returns number of samples copied.
|
||||
uint FIFOSampleBuffer::receiveSamples(SAMPLETYPE *output, uint maxSamples) |
||||
{ |
||||
uint num; |
||||
|
||||
num = (maxSamples > samplesInBuffer) ? samplesInBuffer : maxSamples; |
||||
|
||||
memcpy(output, ptrBegin(), channels * sizeof(SAMPLETYPE) * num); |
||||
return receiveSamples(num); |
||||
} |
||||
|
||||
|
||||
// Removes samples from the beginning of the sample buffer without copying them
|
||||
// anywhere. Used to reduce the number of samples in the buffer, when accessing
|
||||
// the sample buffer with the 'ptrBegin' function.
|
||||
uint FIFOSampleBuffer::receiveSamples(uint maxSamples) |
||||
{ |
||||
if (maxSamples >= samplesInBuffer) |
||||
{ |
||||
uint temp; |
||||
|
||||
temp = samplesInBuffer; |
||||
samplesInBuffer = 0; |
||||
return temp; |
||||
} |
||||
|
||||
samplesInBuffer -= maxSamples; |
||||
bufferPos += maxSamples; |
||||
|
||||
return maxSamples; |
||||
} |
||||
|
||||
|
||||
// Returns nonzero if the sample buffer is empty
|
||||
int FIFOSampleBuffer::isEmpty() const |
||||
{ |
||||
return (samplesInBuffer == 0) ? 1 : 0; |
||||
} |
||||
|
||||
|
||||
// Clears the sample buffer
|
||||
void FIFOSampleBuffer::clear() |
||||
{ |
||||
samplesInBuffer = 0; |
||||
bufferPos = 0; |
||||
} |
||||
|
||||
|
||||
/// allow trimming (downwards) amount of samples in pipeline.
|
||||
/// Returns adjusted amount of samples
|
||||
uint FIFOSampleBuffer::adjustAmountOfSamples(uint numSamples) |
||||
{ |
||||
if (numSamples < samplesInBuffer) |
||||
{ |
||||
samplesInBuffer = numSamples; |
||||
} |
||||
return samplesInBuffer; |
||||
} |
||||
|
||||
|
||||
/// Add silence to end of buffer
|
||||
void FIFOSampleBuffer::addSilent(uint nSamples) |
||||
{ |
||||
memset(ptrEnd(nSamples), 0, sizeof(SAMPLETYPE) * nSamples * channels); |
||||
samplesInBuffer += nSamples; |
||||
} |
||||
@ -1,312 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// General FIR digital filter routines with MMX optimization.
|
||||
///
|
||||
/// Notes : MMX optimized functions reside in a separate, platform-specific file,
|
||||
/// e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp'
|
||||
///
|
||||
/// This source file contains OpenMP optimizations that allow speeding up the
|
||||
/// corss-correlation algorithm by executing it in several threads / CPU cores
|
||||
/// in parallel. See the following article link for more detailed discussion
|
||||
/// about SoundTouch OpenMP optimizations:
|
||||
/// http://www.softwarecoven.com/parallel-computing-in-embedded-mobile-devices
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <memory.h> |
||||
#include <assert.h> |
||||
#include <math.h> |
||||
#include <stdlib.h> |
||||
#include "FIRFilter.h" |
||||
#include "cpu_detect.h" |
||||
|
||||
using namespace soundtouch; |
||||
|
||||
/*****************************************************************************
|
||||
* |
||||
* Implementation of the class 'FIRFilter' |
||||
* |
||||
*****************************************************************************/ |
||||
|
||||
FIRFilter::FIRFilter() |
||||
{ |
||||
resultDivFactor = 0; |
||||
resultDivider = 0; |
||||
length = 0; |
||||
lengthDiv8 = 0; |
||||
filterCoeffs = nullptr; |
||||
filterCoeffsStereo = nullptr; |
||||
} |
||||
|
||||
|
||||
FIRFilter::~FIRFilter() |
||||
{ |
||||
delete[] filterCoeffs; |
||||
delete[] filterCoeffsStereo; |
||||
} |
||||
|
||||
|
||||
// Usual C-version of the filter routine for stereo sound
|
||||
uint FIRFilter::evaluateFilterStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples) const |
||||
{ |
||||
int j, end; |
||||
// hint compiler autovectorization that loop length is divisible by 8
|
||||
uint ilength = length & -8; |
||||
|
||||
assert((length != 0) && (length == ilength) && (src != nullptr) && (dest != nullptr) && (filterCoeffs != nullptr)); |
||||
assert(numSamples > ilength); |
||||
|
||||
end = 2 * (numSamples - ilength); |
||||
|
||||
for (j = 0; j < end; j += 2) |
||||
{ |
||||
const SAMPLETYPE *ptr; |
||||
LONG_SAMPLETYPE suml, sumr; |
||||
|
||||
suml = sumr = 0; |
||||
ptr = src + j; |
||||
|
||||
for (uint i = 0; i < ilength; i ++) |
||||
{ |
||||
suml += ptr[2 * i] * filterCoeffsStereo[2 * i]; |
||||
sumr += ptr[2 * i + 1] * filterCoeffsStereo[2 * i + 1]; |
||||
} |
||||
|
||||
#ifdef SOUNDTOUCH_INTEGER_SAMPLES |
||||
suml >>= resultDivFactor; |
||||
sumr >>= resultDivFactor; |
||||
// saturate to 16 bit integer limits
|
||||
suml = (suml < -32768) ? -32768 : (suml > 32767) ? 32767 : suml; |
||||
// saturate to 16 bit integer limits
|
||||
sumr = (sumr < -32768) ? -32768 : (sumr > 32767) ? 32767 : sumr; |
||||
#endif // SOUNDTOUCH_INTEGER_SAMPLES
|
||||
dest[j] = (SAMPLETYPE)suml; |
||||
dest[j + 1] = (SAMPLETYPE)sumr; |
||||
} |
||||
return numSamples - ilength; |
||||
} |
||||
|
||||
|
||||
// Usual C-version of the filter routine for mono sound
|
||||
uint FIRFilter::evaluateFilterMono(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples) const |
||||
{ |
||||
int j, end; |
||||
|
||||
// hint compiler autovectorization that loop length is divisible by 8
|
||||
int ilength = length & -8; |
||||
|
||||
assert(ilength != 0); |
||||
|
||||
end = numSamples - ilength; |
||||
for (j = 0; j < end; j ++) |
||||
{ |
||||
const SAMPLETYPE *pSrc = src + j; |
||||
LONG_SAMPLETYPE sum; |
||||
int i; |
||||
|
||||
sum = 0; |
||||
for (i = 0; i < ilength; i ++) |
||||
{ |
||||
sum += pSrc[i] * filterCoeffs[i]; |
||||
} |
||||
#ifdef SOUNDTOUCH_INTEGER_SAMPLES |
||||
sum >>= resultDivFactor; |
||||
// saturate to 16 bit integer limits
|
||||
sum = (sum < -32768) ? -32768 : (sum > 32767) ? 32767 : sum; |
||||
#endif // SOUNDTOUCH_INTEGER_SAMPLES
|
||||
dest[j] = (SAMPLETYPE)sum; |
||||
} |
||||
return end; |
||||
} |
||||
|
||||
|
||||
uint FIRFilter::evaluateFilterMulti(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples, uint numChannels) |
||||
{ |
||||
int j, end; |
||||
|
||||
assert(length != 0); |
||||
assert(src != nullptr); |
||||
assert(dest != nullptr); |
||||
assert(filterCoeffs != nullptr); |
||||
assert(numChannels < 16); |
||||
|
||||
// hint compiler autovectorization that loop length is divisible by 8
|
||||
int ilength = length & -8; |
||||
|
||||
end = numChannels * (numSamples - ilength); |
||||
|
||||
for (j = 0; j < end; j += numChannels) |
||||
{ |
||||
const SAMPLETYPE *ptr; |
||||
LONG_SAMPLETYPE sums[16]; |
||||
uint c; |
||||
int i; |
||||
|
||||
for (c = 0; c < numChannels; c ++) |
||||
{ |
||||
sums[c] = 0; |
||||
} |
||||
|
||||
ptr = src + j; |
||||
|
||||
for (i = 0; i < ilength; i ++) |
||||
{ |
||||
SAMPLETYPE coef=filterCoeffs[i]; |
||||
for (c = 0; c < numChannels; c ++) |
||||
{ |
||||
sums[c] += ptr[0] * coef; |
||||
ptr ++; |
||||
} |
||||
} |
||||
|
||||
for (c = 0; c < numChannels; c ++) |
||||
{ |
||||
#ifdef SOUNDTOUCH_INTEGER_SAMPLES |
||||
sums[c] >>= resultDivFactor; |
||||
#endif // SOUNDTOUCH_INTEGER_SAMPLES
|
||||
dest[j+c] = (SAMPLETYPE)sums[c]; |
||||
} |
||||
} |
||||
return numSamples - ilength; |
||||
} |
||||
|
||||
|
||||
// Set filter coeffiecients and length.
|
||||
//
|
||||
// Throws an exception if filter length isn't divisible by 8
|
||||
void FIRFilter::setCoefficients(const SAMPLETYPE *coeffs, uint newLength, uint uResultDivFactor) |
||||
{ |
||||
assert(newLength > 0); |
||||
if (newLength % 8) ST_THROW_RT_ERROR("FIR filter length not divisible by 8"); |
||||
|
||||
#ifdef SOUNDTOUCH_FLOAT_SAMPLES |
||||
// scale coefficients already here if using floating samples
|
||||
double scale = 1.0 / resultDivider; |
||||
#else |
||||
short scale = 1; |
||||
#endif |
||||
|
||||
lengthDiv8 = newLength / 8; |
||||
length = lengthDiv8 * 8; |
||||
assert(length == newLength); |
||||
|
||||
resultDivFactor = uResultDivFactor; |
||||
resultDivider = (SAMPLETYPE)::pow(2.0, (int)resultDivFactor); |
||||
|
||||
delete[] filterCoeffs; |
||||
filterCoeffs = new SAMPLETYPE[length]; |
||||
delete[] filterCoeffsStereo; |
||||
filterCoeffsStereo = new SAMPLETYPE[length*2]; |
||||
for (uint i = 0; i < length; i ++) |
||||
{ |
||||
filterCoeffs[i] = (SAMPLETYPE)(coeffs[i] * scale); |
||||
// create also stereo set of filter coefficients: this allows compiler
|
||||
// to autovectorize filter evaluation much more efficiently
|
||||
filterCoeffsStereo[2 * i] = (SAMPLETYPE)(coeffs[i] * scale); |
||||
filterCoeffsStereo[2 * i + 1] = (SAMPLETYPE)(coeffs[i] * scale); |
||||
} |
||||
} |
||||
|
||||
|
||||
uint FIRFilter::getLength() const |
||||
{ |
||||
return length; |
||||
} |
||||
|
||||
|
||||
// Applies the filter to the given sequence of samples.
|
||||
//
|
||||
// Note : The amount of outputted samples is by value of 'filter_length'
|
||||
// smaller than the amount of input samples.
|
||||
uint FIRFilter::evaluate(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples, uint numChannels) |
||||
{ |
||||
assert(length > 0); |
||||
assert(lengthDiv8 * 8 == length); |
||||
|
||||
if (numSamples < length) return 0; |
||||
|
||||
#ifndef USE_MULTICH_ALWAYS |
||||
if (numChannels == 1) |
||||
{ |
||||
return evaluateFilterMono(dest, src, numSamples); |
||||
} |
||||
else if (numChannels == 2) |
||||
{ |
||||
return evaluateFilterStereo(dest, src, numSamples); |
||||
} |
||||
else |
||||
#endif // USE_MULTICH_ALWAYS
|
||||
{ |
||||
assert(numChannels > 0); |
||||
return evaluateFilterMulti(dest, src, numSamples, numChannels); |
||||
} |
||||
} |
||||
|
||||
|
||||
// Operator 'new' is overloaded so that it automatically creates a suitable instance
|
||||
// depending on if we've a MMX-capable CPU available or not.
|
||||
void * FIRFilter::operator new(size_t) |
||||
{ |
||||
// Notice! don't use "new FIRFilter" directly, use "newInstance" to create a new instance instead!
|
||||
ST_THROW_RT_ERROR("Error in FIRFilter::new: Don't use 'new FIRFilter', use 'newInstance' member instead!"); |
||||
return newInstance(); |
||||
} |
||||
|
||||
|
||||
FIRFilter * FIRFilter::newInstance() |
||||
{ |
||||
uint uExtensions; |
||||
|
||||
uExtensions = detectCPUextensions(); |
||||
(void)uExtensions; |
||||
|
||||
// Check if MMX/SSE instruction set extensions supported by CPU
|
||||
|
||||
#ifdef SOUNDTOUCH_ALLOW_MMX |
||||
// MMX routines available only with integer sample types
|
||||
if (uExtensions & SUPPORT_MMX) |
||||
{ |
||||
return ::new FIRFilterMMX; |
||||
} |
||||
else |
||||
#endif // SOUNDTOUCH_ALLOW_MMX
|
||||
|
||||
#ifdef SOUNDTOUCH_ALLOW_SSE |
||||
if (uExtensions & SUPPORT_SSE) |
||||
{ |
||||
// SSE support
|
||||
return ::new FIRFilterSSE; |
||||
} |
||||
else |
||||
#endif // SOUNDTOUCH_ALLOW_SSE
|
||||
|
||||
{ |
||||
// ISA optimizations not supported, use plain C version
|
||||
return ::new FIRFilter; |
||||
} |
||||
} |
||||
@ -1,140 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// General FIR digital filter routines with MMX optimization.
|
||||
///
|
||||
/// Note : MMX optimized functions reside in a separate, platform-specific file,
|
||||
/// e.g. 'mmx_win.cpp' or 'mmx_gcc.cpp'
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef FIRFilter_H |
||||
#define FIRFilter_H |
||||
|
||||
#include <stddef.h> |
||||
#include "STTypes.h" |
||||
|
||||
namespace soundtouch |
||||
{ |
||||
|
||||
class FIRFilter |
||||
{ |
||||
protected: |
||||
// Number of FIR filter taps
|
||||
uint length; |
||||
// Number of FIR filter taps divided by 8
|
||||
uint lengthDiv8; |
||||
|
||||
// Result divider factor in 2^k format
|
||||
uint resultDivFactor; |
||||
|
||||
// Result divider value.
|
||||
SAMPLETYPE resultDivider; |
||||
|
||||
// Memory for filter coefficients
|
||||
SAMPLETYPE *filterCoeffs; |
||||
SAMPLETYPE *filterCoeffsStereo; |
||||
|
||||
virtual uint evaluateFilterStereo(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
uint numSamples) const; |
||||
virtual uint evaluateFilterMono(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
uint numSamples) const; |
||||
virtual uint evaluateFilterMulti(SAMPLETYPE *dest, const SAMPLETYPE *src, uint numSamples, uint numChannels); |
||||
|
||||
public: |
||||
FIRFilter(); |
||||
virtual ~FIRFilter(); |
||||
|
||||
/// Operator 'new' is overloaded so that it automatically creates a suitable instance
|
||||
/// depending on if we've a MMX-capable CPU available or not.
|
||||
static void * operator new(size_t s); |
||||
|
||||
static FIRFilter *newInstance(); |
||||
|
||||
/// Applies the filter to the given sequence of samples.
|
||||
/// Note : The amount of outputted samples is by value of 'filter_length'
|
||||
/// smaller than the amount of input samples.
|
||||
///
|
||||
/// \return Number of samples copied to 'dest'.
|
||||
uint evaluate(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
uint numSamples, |
||||
uint numChannels); |
||||
|
||||
uint getLength() const; |
||||
|
||||
virtual void setCoefficients(const SAMPLETYPE *coeffs, |
||||
uint newLength, |
||||
uint uResultDivFactor); |
||||
}; |
||||
|
||||
|
||||
// Optional subclasses that implement CPU-specific optimizations:
|
||||
|
||||
#ifdef SOUNDTOUCH_ALLOW_MMX |
||||
|
||||
/// Class that implements MMX optimized functions exclusive for 16bit integer samples type.
|
||||
class FIRFilterMMX : public FIRFilter |
||||
{ |
||||
protected: |
||||
short *filterCoeffsUnalign; |
||||
short *filterCoeffsAlign; |
||||
|
||||
virtual uint evaluateFilterStereo(short *dest, const short *src, uint numSamples) const override; |
||||
public: |
||||
FIRFilterMMX(); |
||||
~FIRFilterMMX(); |
||||
|
||||
virtual void setCoefficients(const short *coeffs, uint newLength, uint uResultDivFactor) override; |
||||
}; |
||||
|
||||
#endif // SOUNDTOUCH_ALLOW_MMX
|
||||
|
||||
|
||||
#ifdef SOUNDTOUCH_ALLOW_SSE |
||||
/// Class that implements SSE optimized functions exclusive for floating point samples type.
|
||||
class FIRFilterSSE : public FIRFilter |
||||
{ |
||||
protected: |
||||
float *filterCoeffsUnalign; |
||||
float *filterCoeffsAlign; |
||||
|
||||
virtual uint evaluateFilterStereo(float *dest, const float *src, uint numSamples) const override; |
||||
public: |
||||
FIRFilterSSE(); |
||||
~FIRFilterSSE(); |
||||
|
||||
virtual void setCoefficients(const float *coeffs, uint newLength, uint uResultDivFactor) override; |
||||
}; |
||||
|
||||
#endif // SOUNDTOUCH_ALLOW_SSE
|
||||
|
||||
} |
||||
|
||||
#endif // FIRFilter_H
|
||||
@ -1,196 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Cubic interpolation routine.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <stddef.h> |
||||
#include <math.h> |
||||
#include "InterpolateCubic.h" |
||||
#include "STTypes.h" |
||||
|
||||
using namespace soundtouch; |
||||
|
||||
// cubic interpolation coefficients
|
||||
static const float _coeffs[]= |
||||
{ -0.5f, 1.0f, -0.5f, 0.0f, |
||||
1.5f, -2.5f, 0.0f, 1.0f, |
||||
-1.5f, 2.0f, 0.5f, 0.0f, |
||||
0.5f, -0.5f, 0.0f, 0.0f}; |
||||
|
||||
|
||||
InterpolateCubic::InterpolateCubic() |
||||
{ |
||||
fract = 0; |
||||
} |
||||
|
||||
|
||||
void InterpolateCubic::resetRegisters() |
||||
{ |
||||
fract = 0; |
||||
} |
||||
|
||||
|
||||
/// Transpose mono audio. Returns number of produced output samples, and
|
||||
/// updates "srcSamples" to amount of consumed source samples
|
||||
int InterpolateCubic::transposeMono(SAMPLETYPE *pdest, |
||||
const SAMPLETYPE *psrc, |
||||
int &srcSamples) |
||||
{ |
||||
int i; |
||||
int srcSampleEnd = srcSamples - 4; |
||||
int srcCount = 0; |
||||
|
||||
i = 0; |
||||
while (srcCount < srcSampleEnd) |
||||
{ |
||||
float out; |
||||
const float x3 = 1.0f; |
||||
const float x2 = (float)fract; // x
|
||||
const float x1 = x2*x2; // x^2
|
||||
const float x0 = x1*x2; // x^3
|
||||
float y0, y1, y2, y3; |
||||
|
||||
assert(fract < 1.0); |
||||
|
||||
y0 = _coeffs[0] * x0 + _coeffs[1] * x1 + _coeffs[2] * x2 + _coeffs[3] * x3; |
||||
y1 = _coeffs[4] * x0 + _coeffs[5] * x1 + _coeffs[6] * x2 + _coeffs[7] * x3; |
||||
y2 = _coeffs[8] * x0 + _coeffs[9] * x1 + _coeffs[10] * x2 + _coeffs[11] * x3; |
||||
y3 = _coeffs[12] * x0 + _coeffs[13] * x1 + _coeffs[14] * x2 + _coeffs[15] * x3; |
||||
|
||||
out = y0 * psrc[0] + y1 * psrc[1] + y2 * psrc[2] + y3 * psrc[3]; |
||||
|
||||
pdest[i] = (SAMPLETYPE)out; |
||||
i ++; |
||||
|
||||
// update position fraction
|
||||
fract += rate; |
||||
// update whole positions
|
||||
int whole = (int)fract; |
||||
fract -= whole; |
||||
psrc += whole; |
||||
srcCount += whole; |
||||
} |
||||
srcSamples = srcCount; |
||||
return i; |
||||
} |
||||
|
||||
|
||||
/// Transpose stereo audio. Returns number of produced output samples, and
|
||||
/// updates "srcSamples" to amount of consumed source samples
|
||||
int InterpolateCubic::transposeStereo(SAMPLETYPE *pdest, |
||||
const SAMPLETYPE *psrc, |
||||
int &srcSamples) |
||||
{ |
||||
int i; |
||||
int srcSampleEnd = srcSamples - 4; |
||||
int srcCount = 0; |
||||
|
||||
i = 0; |
||||
while (srcCount < srcSampleEnd) |
||||
{ |
||||
const float x3 = 1.0f; |
||||
const float x2 = (float)fract; // x
|
||||
const float x1 = x2*x2; // x^2
|
||||
const float x0 = x1*x2; // x^3
|
||||
float y0, y1, y2, y3; |
||||
float out0, out1; |
||||
|
||||
assert(fract < 1.0); |
||||
|
||||
y0 = _coeffs[0] * x0 + _coeffs[1] * x1 + _coeffs[2] * x2 + _coeffs[3] * x3; |
||||
y1 = _coeffs[4] * x0 + _coeffs[5] * x1 + _coeffs[6] * x2 + _coeffs[7] * x3; |
||||
y2 = _coeffs[8] * x0 + _coeffs[9] * x1 + _coeffs[10] * x2 + _coeffs[11] * x3; |
||||
y3 = _coeffs[12] * x0 + _coeffs[13] * x1 + _coeffs[14] * x2 + _coeffs[15] * x3; |
||||
|
||||
out0 = y0 * psrc[0] + y1 * psrc[2] + y2 * psrc[4] + y3 * psrc[6]; |
||||
out1 = y0 * psrc[1] + y1 * psrc[3] + y2 * psrc[5] + y3 * psrc[7]; |
||||
|
||||
pdest[2*i] = (SAMPLETYPE)out0; |
||||
pdest[2*i+1] = (SAMPLETYPE)out1; |
||||
i ++; |
||||
|
||||
// update position fraction
|
||||
fract += rate; |
||||
// update whole positions
|
||||
int whole = (int)fract; |
||||
fract -= whole; |
||||
psrc += 2*whole; |
||||
srcCount += whole; |
||||
} |
||||
srcSamples = srcCount; |
||||
return i; |
||||
} |
||||
|
||||
|
||||
/// Transpose multi-channel audio. Returns number of produced output samples, and
|
||||
/// updates "srcSamples" to amount of consumed source samples
|
||||
int InterpolateCubic::transposeMulti(SAMPLETYPE *pdest, |
||||
const SAMPLETYPE *psrc, |
||||
int &srcSamples) |
||||
{ |
||||
int i; |
||||
int srcSampleEnd = srcSamples - 4; |
||||
int srcCount = 0; |
||||
|
||||
i = 0; |
||||
while (srcCount < srcSampleEnd) |
||||
{ |
||||
const float x3 = 1.0f; |
||||
const float x2 = (float)fract; // x
|
||||
const float x1 = x2*x2; // x^2
|
||||
const float x0 = x1*x2; // x^3
|
||||
float y0, y1, y2, y3; |
||||
|
||||
assert(fract < 1.0); |
||||
|
||||
y0 = _coeffs[0] * x0 + _coeffs[1] * x1 + _coeffs[2] * x2 + _coeffs[3] * x3; |
||||
y1 = _coeffs[4] * x0 + _coeffs[5] * x1 + _coeffs[6] * x2 + _coeffs[7] * x3; |
||||
y2 = _coeffs[8] * x0 + _coeffs[9] * x1 + _coeffs[10] * x2 + _coeffs[11] * x3; |
||||
y3 = _coeffs[12] * x0 + _coeffs[13] * x1 + _coeffs[14] * x2 + _coeffs[15] * x3; |
||||
|
||||
for (int c = 0; c < numChannels; c ++) |
||||
{ |
||||
float out; |
||||
out = y0 * psrc[c] + y1 * psrc[c + numChannels] + y2 * psrc[c + 2 * numChannels] + y3 * psrc[c + 3 * numChannels]; |
||||
pdest[0] = (SAMPLETYPE)out; |
||||
pdest ++; |
||||
} |
||||
i ++; |
||||
|
||||
// update position fraction
|
||||
fract += rate; |
||||
// update whole positions
|
||||
int whole = (int)fract; |
||||
fract -= whole; |
||||
psrc += numChannels*whole; |
||||
srcCount += whole; |
||||
} |
||||
srcSamples = srcCount; |
||||
return i; |
||||
} |
||||
@ -1,69 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Cubic interpolation routine.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _InterpolateCubic_H_ |
||||
#define _InterpolateCubic_H_ |
||||
|
||||
#include "RateTransposer.h" |
||||
#include "STTypes.h" |
||||
|
||||
namespace soundtouch |
||||
{ |
||||
|
||||
class InterpolateCubic : public TransposerBase |
||||
{ |
||||
protected: |
||||
virtual int transposeMono(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
int &srcSamples) override; |
||||
virtual int transposeStereo(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
int &srcSamples) override; |
||||
virtual int transposeMulti(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
int &srcSamples) override; |
||||
|
||||
double fract; |
||||
|
||||
public: |
||||
InterpolateCubic(); |
||||
|
||||
virtual void resetRegisters() override; |
||||
|
||||
virtual int getLatency() const override |
||||
{ |
||||
return 1; |
||||
} |
||||
}; |
||||
|
||||
} |
||||
|
||||
#endif |
||||
@ -1,296 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Linear interpolation algorithm.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <assert.h> |
||||
#include <stdlib.h> |
||||
#include "InterpolateLinear.h" |
||||
|
||||
using namespace soundtouch; |
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// InterpolateLinearInteger - integer arithmetic implementation
|
||||
//
|
||||
|
||||
/// fixed-point interpolation routine precision
|
||||
#define SCALE 65536 |
||||
|
||||
|
||||
// Constructor
|
||||
InterpolateLinearInteger::InterpolateLinearInteger() : TransposerBase() |
||||
{ |
||||
// Notice: use local function calling syntax for sake of clarity,
|
||||
// to indicate the fact that C++ constructor can't call virtual functions.
|
||||
resetRegisters(); |
||||
setRate(1.0f); |
||||
} |
||||
|
||||
|
||||
void InterpolateLinearInteger::resetRegisters() |
||||
{ |
||||
iFract = 0; |
||||
} |
||||
|
||||
|
||||
// Transposes the sample rate of the given samples using linear interpolation.
|
||||
// 'Mono' version of the routine. Returns the number of samples returned in
|
||||
// the "dest" buffer
|
||||
int InterpolateLinearInteger::transposeMono(SAMPLETYPE *dest, const SAMPLETYPE *src, int &srcSamples) |
||||
{ |
||||
int i; |
||||
int srcSampleEnd = srcSamples - 1; |
||||
int srcCount = 0; |
||||
|
||||
i = 0; |
||||
while (srcCount < srcSampleEnd) |
||||
{ |
||||
LONG_SAMPLETYPE temp; |
||||
|
||||
assert(iFract < SCALE); |
||||
|
||||
temp = (SCALE - iFract) * src[0] + iFract * src[1]; |
||||
dest[i] = (SAMPLETYPE)(temp / SCALE); |
||||
i++; |
||||
|
||||
iFract += iRate; |
||||
|
||||
int iWhole = iFract / SCALE; |
||||
iFract -= iWhole * SCALE; |
||||
srcCount += iWhole; |
||||
src += iWhole; |
||||
} |
||||
srcSamples = srcCount; |
||||
|
||||
return i; |
||||
} |
||||
|
||||
|
||||
// Transposes the sample rate of the given samples using linear interpolation.
|
||||
// 'Stereo' version of the routine. Returns the number of samples returned in
|
||||
// the "dest" buffer
|
||||
int InterpolateLinearInteger::transposeStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, int &srcSamples) |
||||
{ |
||||
int i; |
||||
int srcSampleEnd = srcSamples - 1; |
||||
int srcCount = 0; |
||||
|
||||
i = 0; |
||||
while (srcCount < srcSampleEnd) |
||||
{ |
||||
LONG_SAMPLETYPE temp0; |
||||
LONG_SAMPLETYPE temp1; |
||||
|
||||
assert(iFract < SCALE); |
||||
|
||||
temp0 = (SCALE - iFract) * src[0] + iFract * src[2]; |
||||
temp1 = (SCALE - iFract) * src[1] + iFract * src[3]; |
||||
dest[0] = (SAMPLETYPE)(temp0 / SCALE); |
||||
dest[1] = (SAMPLETYPE)(temp1 / SCALE); |
||||
dest += 2; |
||||
i++; |
||||
|
||||
iFract += iRate; |
||||
|
||||
int iWhole = iFract / SCALE; |
||||
iFract -= iWhole * SCALE; |
||||
srcCount += iWhole; |
||||
src += 2*iWhole; |
||||
} |
||||
srcSamples = srcCount; |
||||
|
||||
return i; |
||||
} |
||||
|
||||
|
||||
int InterpolateLinearInteger::transposeMulti(SAMPLETYPE *dest, const SAMPLETYPE *src, int &srcSamples) |
||||
{ |
||||
int i; |
||||
int srcSampleEnd = srcSamples - 1; |
||||
int srcCount = 0; |
||||
|
||||
i = 0; |
||||
while (srcCount < srcSampleEnd) |
||||
{ |
||||
LONG_SAMPLETYPE temp, vol1; |
||||
|
||||
assert(iFract < SCALE); |
||||
vol1 = (LONG_SAMPLETYPE)(SCALE - iFract); |
||||
for (int c = 0; c < numChannels; c ++) |
||||
{ |
||||
temp = vol1 * src[c] + iFract * src[c + numChannels]; |
||||
dest[0] = (SAMPLETYPE)(temp / SCALE); |
||||
dest ++; |
||||
} |
||||
i++; |
||||
|
||||
iFract += iRate; |
||||
|
||||
int iWhole = iFract / SCALE; |
||||
iFract -= iWhole * SCALE; |
||||
srcCount += iWhole; |
||||
src += iWhole * numChannels; |
||||
} |
||||
srcSamples = srcCount; |
||||
|
||||
return i; |
||||
} |
||||
|
||||
|
||||
// Sets new target iRate. Normal iRate = 1.0, smaller values represent slower
|
||||
// iRate, larger faster iRates.
|
||||
void InterpolateLinearInteger::setRate(double newRate) |
||||
{ |
||||
iRate = (int)(newRate * SCALE + 0.5); |
||||
TransposerBase::setRate(newRate); |
||||
} |
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// InterpolateLinearFloat - floating point arithmetic implementation
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// Constructor
|
||||
InterpolateLinearFloat::InterpolateLinearFloat() : TransposerBase() |
||||
{ |
||||
// Notice: use local function calling syntax for sake of clarity,
|
||||
// to indicate the fact that C++ constructor can't call virtual functions.
|
||||
resetRegisters(); |
||||
setRate(1.0); |
||||
} |
||||
|
||||
|
||||
void InterpolateLinearFloat::resetRegisters() |
||||
{ |
||||
fract = 0; |
||||
} |
||||
|
||||
|
||||
// Transposes the sample rate of the given samples using linear interpolation.
|
||||
// 'Mono' version of the routine. Returns the number of samples returned in
|
||||
// the "dest" buffer
|
||||
int InterpolateLinearFloat::transposeMono(SAMPLETYPE *dest, const SAMPLETYPE *src, int &srcSamples) |
||||
{ |
||||
int i; |
||||
int srcSampleEnd = srcSamples - 1; |
||||
int srcCount = 0; |
||||
|
||||
i = 0; |
||||
while (srcCount < srcSampleEnd) |
||||
{ |
||||
double out; |
||||
assert(fract < 1.0); |
||||
|
||||
out = (1.0 - fract) * src[0] + fract * src[1]; |
||||
dest[i] = (SAMPLETYPE)out; |
||||
i ++; |
||||
|
||||
// update position fraction
|
||||
fract += rate; |
||||
// update whole positions
|
||||
int whole = (int)fract; |
||||
fract -= whole; |
||||
src += whole; |
||||
srcCount += whole; |
||||
} |
||||
srcSamples = srcCount; |
||||
return i; |
||||
} |
||||
|
||||
|
||||
// Transposes the sample rate of the given samples using linear interpolation.
|
||||
// 'Mono' version of the routine. Returns the number of samples returned in
|
||||
// the "dest" buffer
|
||||
int InterpolateLinearFloat::transposeStereo(SAMPLETYPE *dest, const SAMPLETYPE *src, int &srcSamples) |
||||
{ |
||||
int i; |
||||
int srcSampleEnd = srcSamples - 1; |
||||
int srcCount = 0; |
||||
|
||||
i = 0; |
||||
while (srcCount < srcSampleEnd) |
||||
{ |
||||
double out0, out1; |
||||
assert(fract < 1.0); |
||||
|
||||
out0 = (1.0 - fract) * src[0] + fract * src[2]; |
||||
out1 = (1.0 - fract) * src[1] + fract * src[3]; |
||||
dest[2*i] = (SAMPLETYPE)out0; |
||||
dest[2*i+1] = (SAMPLETYPE)out1; |
||||
i ++; |
||||
|
||||
// update position fraction
|
||||
fract += rate; |
||||
// update whole positions
|
||||
int whole = (int)fract; |
||||
fract -= whole; |
||||
src += 2*whole; |
||||
srcCount += whole; |
||||
} |
||||
srcSamples = srcCount; |
||||
return i; |
||||
} |
||||
|
||||
|
||||
int InterpolateLinearFloat::transposeMulti(SAMPLETYPE *dest, const SAMPLETYPE *src, int &srcSamples) |
||||
{ |
||||
int i; |
||||
int srcSampleEnd = srcSamples - 1; |
||||
int srcCount = 0; |
||||
|
||||
i = 0; |
||||
while (srcCount < srcSampleEnd) |
||||
{ |
||||
float temp, vol1, fract_float; |
||||
|
||||
vol1 = (float)(1.0 - fract); |
||||
fract_float = (float)fract; |
||||
for (int c = 0; c < numChannels; c ++) |
||||
{ |
||||
temp = vol1 * src[c] + fract_float * src[c + numChannels]; |
||||
*dest = (SAMPLETYPE)temp; |
||||
dest ++; |
||||
} |
||||
i++; |
||||
|
||||
fract += rate; |
||||
|
||||
int iWhole = (int)fract; |
||||
fract -= iWhole; |
||||
srcCount += iWhole; |
||||
src += iWhole * numChannels; |
||||
} |
||||
srcSamples = srcCount; |
||||
|
||||
return i; |
||||
} |
||||
@ -1,98 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Linear interpolation routine.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _InterpolateLinear_H_ |
||||
#define _InterpolateLinear_H_ |
||||
|
||||
#include "RateTransposer.h" |
||||
#include "STTypes.h" |
||||
|
||||
namespace soundtouch |
||||
{ |
||||
|
||||
/// Linear transposer class that uses integer arithmetic
|
||||
class InterpolateLinearInteger final : public TransposerBase |
||||
{ |
||||
protected: |
||||
int iFract; |
||||
int iRate; |
||||
|
||||
virtual int transposeMono(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
int &srcSamples) override; |
||||
virtual int transposeStereo(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
int &srcSamples) override; |
||||
virtual int transposeMulti(SAMPLETYPE *dest, const SAMPLETYPE *src, int &srcSamples) override; |
||||
public: |
||||
InterpolateLinearInteger(); |
||||
|
||||
/// Sets new target rate. Normal rate = 1.0, smaller values represent slower
|
||||
/// rate, larger faster rates.
|
||||
virtual void setRate(double newRate) override; |
||||
|
||||
virtual void resetRegisters() override; |
||||
|
||||
virtual int getLatency() const override |
||||
{ |
||||
return 0; |
||||
} |
||||
}; |
||||
|
||||
|
||||
/// Linear transposer class that uses floating point arithmetic
|
||||
class InterpolateLinearFloat final : public TransposerBase |
||||
{ |
||||
protected: |
||||
double fract; |
||||
|
||||
virtual int transposeMono(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
int &srcSamples); |
||||
virtual int transposeStereo(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
int &srcSamples); |
||||
virtual int transposeMulti(SAMPLETYPE *dest, const SAMPLETYPE *src, int &srcSamples); |
||||
|
||||
public: |
||||
InterpolateLinearFloat(); |
||||
|
||||
virtual void resetRegisters(); |
||||
|
||||
int getLatency() const |
||||
{ |
||||
return 0; |
||||
} |
||||
}; |
||||
|
||||
} |
||||
|
||||
#endif |
||||
@ -1,181 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Sample interpolation routine using 8-tap band-limited Shannon interpolation
|
||||
/// with kaiser window.
|
||||
///
|
||||
/// Notice. This algorithm is remarkably much heavier than linear or cubic
|
||||
/// interpolation, and not remarkably better than cubic algorithm. Thus mostly
|
||||
/// for experimental purposes
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <math.h> |
||||
#include "InterpolateShannon.h" |
||||
#include "STTypes.h" |
||||
|
||||
using namespace soundtouch; |
||||
|
||||
|
||||
/// Kaiser window with beta = 2.0
|
||||
/// Values scaled down by 5% to avoid overflows
|
||||
static const double _kaiser8[8] = |
||||
{ |
||||
0.41778693317814, |
||||
0.64888025049173, |
||||
0.83508562409944, |
||||
0.93887857733412, |
||||
0.93887857733412, |
||||
0.83508562409944, |
||||
0.64888025049173, |
||||
0.41778693317814 |
||||
}; |
||||
|
||||
|
||||
InterpolateShannon::InterpolateShannon() |
||||
{ |
||||
fract = 0; |
||||
} |
||||
|
||||
|
||||
void InterpolateShannon::resetRegisters() |
||||
{ |
||||
fract = 0; |
||||
} |
||||
|
||||
|
||||
#define PI 3.1415926536 |
||||
#define sinc(x) (sin(PI * (x)) / (PI * (x))) |
||||
|
||||
/// Transpose mono audio. Returns number of produced output samples, and
|
||||
/// updates "srcSamples" to amount of consumed source samples
|
||||
int InterpolateShannon::transposeMono(SAMPLETYPE *pdest, |
||||
const SAMPLETYPE *psrc, |
||||
int &srcSamples) |
||||
{ |
||||
int i; |
||||
int srcSampleEnd = srcSamples - 8; |
||||
int srcCount = 0; |
||||
|
||||
i = 0; |
||||
while (srcCount < srcSampleEnd) |
||||
{ |
||||
double out; |
||||
assert(fract < 1.0); |
||||
|
||||
out = psrc[0] * sinc(-3.0 - fract) * _kaiser8[0]; |
||||
out += psrc[1] * sinc(-2.0 - fract) * _kaiser8[1]; |
||||
out += psrc[2] * sinc(-1.0 - fract) * _kaiser8[2]; |
||||
if (fract < 1e-6) |
||||
{ |
||||
out += psrc[3] * _kaiser8[3]; // sinc(0) = 1
|
||||
} |
||||
else |
||||
{ |
||||
out += psrc[3] * sinc(- fract) * _kaiser8[3]; |
||||
} |
||||
out += psrc[4] * sinc( 1.0 - fract) * _kaiser8[4]; |
||||
out += psrc[5] * sinc( 2.0 - fract) * _kaiser8[5]; |
||||
out += psrc[6] * sinc( 3.0 - fract) * _kaiser8[6]; |
||||
out += psrc[7] * sinc( 4.0 - fract) * _kaiser8[7]; |
||||
|
||||
pdest[i] = (SAMPLETYPE)out; |
||||
i ++; |
||||
|
||||
// update position fraction
|
||||
fract += rate; |
||||
// update whole positions
|
||||
int whole = (int)fract; |
||||
fract -= whole; |
||||
psrc += whole; |
||||
srcCount += whole; |
||||
} |
||||
srcSamples = srcCount; |
||||
return i; |
||||
} |
||||
|
||||
|
||||
/// Transpose stereo audio. Returns number of produced output samples, and
|
||||
/// updates "srcSamples" to amount of consumed source samples
|
||||
int InterpolateShannon::transposeStereo(SAMPLETYPE *pdest, |
||||
const SAMPLETYPE *psrc, |
||||
int &srcSamples) |
||||
{ |
||||
int i; |
||||
int srcSampleEnd = srcSamples - 8; |
||||
int srcCount = 0; |
||||
|
||||
i = 0; |
||||
while (srcCount < srcSampleEnd) |
||||
{ |
||||
double out0, out1, w; |
||||
assert(fract < 1.0); |
||||
|
||||
w = sinc(-3.0 - fract) * _kaiser8[0]; |
||||
out0 = psrc[0] * w; out1 = psrc[1] * w; |
||||
w = sinc(-2.0 - fract) * _kaiser8[1]; |
||||
out0 += psrc[2] * w; out1 += psrc[3] * w; |
||||
w = sinc(-1.0 - fract) * _kaiser8[2]; |
||||
out0 += psrc[4] * w; out1 += psrc[5] * w; |
||||
w = _kaiser8[3] * ((fract < 1e-5) ? 1.0 : sinc(- fract)); // sinc(0) = 1
|
||||
out0 += psrc[6] * w; out1 += psrc[7] * w; |
||||
w = sinc( 1.0 - fract) * _kaiser8[4]; |
||||
out0 += psrc[8] * w; out1 += psrc[9] * w; |
||||
w = sinc( 2.0 - fract) * _kaiser8[5]; |
||||
out0 += psrc[10] * w; out1 += psrc[11] * w; |
||||
w = sinc( 3.0 - fract) * _kaiser8[6]; |
||||
out0 += psrc[12] * w; out1 += psrc[13] * w; |
||||
w = sinc( 4.0 - fract) * _kaiser8[7]; |
||||
out0 += psrc[14] * w; out1 += psrc[15] * w; |
||||
|
||||
pdest[2*i] = (SAMPLETYPE)out0; |
||||
pdest[2*i+1] = (SAMPLETYPE)out1; |
||||
i ++; |
||||
|
||||
// update position fraction
|
||||
fract += rate; |
||||
// update whole positions
|
||||
int whole = (int)fract; |
||||
fract -= whole; |
||||
psrc += 2*whole; |
||||
srcCount += whole; |
||||
} |
||||
srcSamples = srcCount; |
||||
return i; |
||||
} |
||||
|
||||
|
||||
/// Transpose stereo audio. Returns number of produced output samples, and
|
||||
/// updates "srcSamples" to amount of consumed source samples
|
||||
int InterpolateShannon::transposeMulti(SAMPLETYPE *, |
||||
const SAMPLETYPE *, |
||||
int &) |
||||
{ |
||||
// not implemented
|
||||
assert(false); |
||||
return 0; |
||||
} |
||||
@ -1,74 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Sample interpolation routine using 8-tap band-limited Shannon interpolation
|
||||
/// with kaiser window.
|
||||
///
|
||||
/// Notice. This algorithm is remarkably much heavier than linear or cubic
|
||||
/// interpolation, and not remarkably better than cubic algorithm. Thus mostly
|
||||
/// for experimental purposes
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _InterpolateShannon_H_ |
||||
#define _InterpolateShannon_H_ |
||||
|
||||
#include "RateTransposer.h" |
||||
#include "STTypes.h" |
||||
|
||||
namespace soundtouch |
||||
{ |
||||
|
||||
class InterpolateShannon final : public TransposerBase |
||||
{ |
||||
protected: |
||||
int transposeMono(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
int &srcSamples) override; |
||||
int transposeStereo(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
int &srcSamples) override; |
||||
int transposeMulti(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
int &srcSamples) override; |
||||
|
||||
double fract; |
||||
|
||||
public: |
||||
InterpolateShannon(); |
||||
|
||||
void resetRegisters() override; |
||||
|
||||
virtual int getLatency() const override |
||||
{ |
||||
return 3; |
||||
} |
||||
}; |
||||
|
||||
} |
||||
|
||||
#endif |
||||
@ -1,277 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Peak detection routine.
|
||||
///
|
||||
/// The routine detects highest value on an array of values and calculates the
|
||||
/// precise peak location as a mass-center of the 'hump' around the peak value.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <math.h> |
||||
#include <assert.h> |
||||
|
||||
#include "PeakFinder.h" |
||||
|
||||
using namespace soundtouch; |
||||
|
||||
#define max(x, y) (((x) > (y)) ? (x) : (y)) |
||||
|
||||
|
||||
PeakFinder::PeakFinder() |
||||
{ |
||||
minPos = maxPos = 0; |
||||
} |
||||
|
||||
|
||||
// Finds real 'top' of a peak hump from neighnourhood of the given 'peakpos'.
|
||||
int PeakFinder::findTop(const float *data, int peakpos) const |
||||
{ |
||||
int i; |
||||
int start, end; |
||||
float refvalue; |
||||
|
||||
refvalue = data[peakpos]; |
||||
|
||||
// seek within ±10 points
|
||||
start = peakpos - 10; |
||||
if (start < minPos) start = minPos; |
||||
end = peakpos + 10; |
||||
if (end > maxPos) end = maxPos; |
||||
|
||||
for (i = start; i <= end; i ++) |
||||
{ |
||||
if (data[i] > refvalue) |
||||
{ |
||||
peakpos = i; |
||||
refvalue = data[i]; |
||||
} |
||||
} |
||||
|
||||
// failure if max value is at edges of seek range => it's not peak, it's at slope.
|
||||
if ((peakpos == start) || (peakpos == end)) return 0; |
||||
|
||||
return peakpos; |
||||
} |
||||
|
||||
|
||||
// Finds 'ground level' of a peak hump by starting from 'peakpos' and proceeding
|
||||
// to direction defined by 'direction' until next 'hump' after minimum value will
|
||||
// begin
|
||||
int PeakFinder::findGround(const float *data, int peakpos, int direction) const |
||||
{ |
||||
int lowpos; |
||||
int pos; |
||||
int climb_count; |
||||
float refvalue; |
||||
float delta; |
||||
|
||||
climb_count = 0; |
||||
refvalue = data[peakpos]; |
||||
lowpos = peakpos; |
||||
|
||||
pos = peakpos; |
||||
|
||||
while ((pos > minPos+1) && (pos < maxPos-1)) |
||||
{ |
||||
int prevpos; |
||||
|
||||
prevpos = pos; |
||||
pos += direction; |
||||
|
||||
// calculate derivate
|
||||
delta = data[pos] - data[prevpos]; |
||||
if (delta <= 0) |
||||
{ |
||||
// going downhill, ok
|
||||
if (climb_count) |
||||
{ |
||||
climb_count --; // decrease climb count
|
||||
} |
||||
|
||||
// check if new minimum found
|
||||
if (data[pos] < refvalue) |
||||
{ |
||||
// new minimum found
|
||||
lowpos = pos; |
||||
refvalue = data[pos]; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
// going uphill, increase climbing counter
|
||||
climb_count ++; |
||||
if (climb_count > 5) break; // we've been climbing too long => it's next uphill => quit
|
||||
} |
||||
} |
||||
return lowpos; |
||||
} |
||||
|
||||
|
||||
// Find offset where the value crosses the given level, when starting from 'peakpos' and
|
||||
// proceeds to direction defined in 'direction'
|
||||
int PeakFinder::findCrossingLevel(const float *data, float level, int peakpos, int direction) const |
||||
{ |
||||
float peaklevel; |
||||
int pos; |
||||
|
||||
peaklevel = data[peakpos]; |
||||
assert(peaklevel >= level); |
||||
pos = peakpos; |
||||
while ((pos >= minPos) && (pos + direction < maxPos)) |
||||
{ |
||||
if (data[pos + direction] < level) return pos; // crossing found
|
||||
pos += direction; |
||||
} |
||||
return -1; // not found
|
||||
} |
||||
|
||||
|
||||
// Calculates the center of mass location of 'data' array items between 'firstPos' and 'lastPos'
|
||||
double PeakFinder::calcMassCenter(const float *data, int firstPos, int lastPos) const |
||||
{ |
||||
int i; |
||||
float sum; |
||||
float wsum; |
||||
|
||||
sum = 0; |
||||
wsum = 0; |
||||
for (i = firstPos; i <= lastPos; i ++) |
||||
{ |
||||
sum += (float)i * data[i]; |
||||
wsum += data[i]; |
||||
} |
||||
|
||||
if (wsum < 1e-6) return 0; |
||||
return sum / wsum; |
||||
} |
||||
|
||||
|
||||
/// get exact center of peak near given position by calculating local mass of center
|
||||
double PeakFinder::getPeakCenter(const float *data, int peakpos) const |
||||
{ |
||||
float peakLevel; // peak level
|
||||
int crosspos1, crosspos2; // position where the peak 'hump' crosses cutting level
|
||||
float cutLevel; // cutting value
|
||||
float groundLevel; // ground level of the peak
|
||||
int gp1, gp2; // bottom positions of the peak 'hump'
|
||||
|
||||
// find ground positions.
|
||||
gp1 = findGround(data, peakpos, -1); |
||||
gp2 = findGround(data, peakpos, 1); |
||||
|
||||
peakLevel = data[peakpos]; |
||||
|
||||
if (gp1 == gp2) |
||||
{ |
||||
// avoid rounding errors when all are equal
|
||||
assert(gp1 == peakpos); |
||||
cutLevel = groundLevel = peakLevel; |
||||
} else { |
||||
// get average of the ground levels
|
||||
groundLevel = 0.5f * (data[gp1] + data[gp2]); |
||||
|
||||
// calculate 70%-level of the peak
|
||||
cutLevel = 0.70f * peakLevel + 0.30f * groundLevel; |
||||
} |
||||
|
||||
// find mid-level crossings
|
||||
crosspos1 = findCrossingLevel(data, cutLevel, peakpos, -1); |
||||
crosspos2 = findCrossingLevel(data, cutLevel, peakpos, 1); |
||||
|
||||
if ((crosspos1 < 0) || (crosspos2 < 0)) return 0; // no crossing, no peak..
|
||||
|
||||
// calculate mass center of the peak surroundings
|
||||
return calcMassCenter(data, crosspos1, crosspos2); |
||||
} |
||||
|
||||
|
||||
double PeakFinder::detectPeak(const float *data, int aminPos, int amaxPos) |
||||
{ |
||||
|
||||
int i; |
||||
int peakpos; // position of peak level
|
||||
double highPeak, peak; |
||||
|
||||
this->minPos = aminPos; |
||||
this->maxPos = amaxPos; |
||||
|
||||
// find absolute peak
|
||||
peakpos = minPos; |
||||
peak = data[minPos]; |
||||
for (i = minPos + 1; i < maxPos; i ++) |
||||
{ |
||||
if (data[i] > peak) |
||||
{ |
||||
peak = data[i]; |
||||
peakpos = i; |
||||
} |
||||
} |
||||
|
||||
// Calculate exact location of the highest peak mass center
|
||||
highPeak = getPeakCenter(data, peakpos); |
||||
peak = highPeak; |
||||
|
||||
// Now check if the highest peak were in fact harmonic of the true base beat peak
|
||||
// - sometimes the highest peak can be Nth harmonic of the true base peak yet
|
||||
// just a slightly higher than the true base
|
||||
|
||||
for (i = 1; i < 3; i ++) |
||||
{ |
||||
double peaktmp, harmonic; |
||||
int i1,i2; |
||||
|
||||
harmonic = (double)pow(2.0, i); |
||||
peakpos = (int)(highPeak / harmonic + 0.5f); |
||||
if (peakpos < minPos) break; |
||||
peakpos = findTop(data, peakpos); // seek true local maximum index
|
||||
if (peakpos == 0) continue; // no local max here
|
||||
|
||||
// calculate mass-center of possible harmonic peak
|
||||
peaktmp = getPeakCenter(data, peakpos); |
||||
|
||||
// accept harmonic peak if
|
||||
// (a) it is found
|
||||
// (b) is within ±4% of the expected harmonic interval
|
||||
// (c) has at least half x-corr value of the max. peak
|
||||
|
||||
double diff = harmonic * peaktmp / highPeak; |
||||
if ((diff < 0.96) || (diff > 1.04)) continue; // peak too afar from expected
|
||||
|
||||
// now compare to highest detected peak
|
||||
i1 = (int)(highPeak + 0.5); |
||||
i2 = (int)(peaktmp + 0.5); |
||||
if (data[i2] >= 0.4*data[i1]) |
||||
{ |
||||
// The harmonic is at least half as high primary peak,
|
||||
// thus use the harmonic peak instead
|
||||
peak = peaktmp; |
||||
} |
||||
} |
||||
|
||||
return peak; |
||||
} |
||||
@ -1,90 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// The routine detects highest value on an array of values and calculates the
|
||||
/// precise peak location as a mass-center of the 'hump' around the peak value.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _PeakFinder_H_ |
||||
#define _PeakFinder_H_ |
||||
|
||||
namespace soundtouch |
||||
{ |
||||
|
||||
class PeakFinder |
||||
{ |
||||
protected: |
||||
/// Min, max allowed peak positions within the data vector
|
||||
int minPos, maxPos; |
||||
|
||||
/// Calculates the mass center between given vector items.
|
||||
double calcMassCenter(const float *data, ///< Data vector.
|
||||
int firstPos, ///< Index of first vector item belonging to the peak.
|
||||
int lastPos ///< Index of last vector item belonging to the peak.
|
||||
) const; |
||||
|
||||
/// Finds the data vector index where the monotoniously decreasing signal crosses the
|
||||
/// given level.
|
||||
int findCrossingLevel(const float *data, ///< Data vector.
|
||||
float level, ///< Goal crossing level.
|
||||
int peakpos, ///< Peak position index within the data vector.
|
||||
int direction /// Direction where to proceed from the peak: 1 = right, -1 = left.
|
||||
) const; |
||||
|
||||
// Finds real 'top' of a peak hump from neighnourhood of the given 'peakpos'.
|
||||
int findTop(const float *data, int peakpos) const; |
||||
|
||||
|
||||
/// Finds the 'ground' level, i.e. smallest level between two neighbouring peaks, to right-
|
||||
/// or left-hand side of the given peak position.
|
||||
int findGround(const float *data, /// Data vector.
|
||||
int peakpos, /// Peak position index within the data vector.
|
||||
int direction /// Direction where to proceed from the peak: 1 = right, -1 = left.
|
||||
) const; |
||||
|
||||
/// get exact center of peak near given position by calculating local mass of center
|
||||
double getPeakCenter(const float *data, int peakpos) const; |
||||
|
||||
public: |
||||
/// Constructor.
|
||||
PeakFinder(); |
||||
|
||||
/// Detect exact peak position of the data vector by finding the largest peak 'hump'
|
||||
/// and calculating the mass-center location of the peak hump.
|
||||
///
|
||||
/// \return The location of the largest base harmonic peak hump.
|
||||
double detectPeak(const float *data, /// Data vector to be analyzed. The data vector has
|
||||
/// to be at least 'maxPos' items long.
|
||||
int minPos, ///< Min allowed peak location within the vector data.
|
||||
int maxPos ///< Max allowed peak location within the vector data.
|
||||
); |
||||
}; |
||||
|
||||
} |
||||
|
||||
#endif // _PeakFinder_H_
|
||||
@ -1,313 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Sample rate transposer. Changes sample rate by using linear interpolation
|
||||
/// together with anti-alias filtering (first order interpolation with anti-
|
||||
/// alias filtering should be quite adequate for this application)
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <memory.h> |
||||
#include <assert.h> |
||||
#include <stdlib.h> |
||||
#include <stdio.h> |
||||
#include "RateTransposer.h" |
||||
#include "InterpolateLinear.h" |
||||
#include "InterpolateCubic.h" |
||||
#include "InterpolateShannon.h" |
||||
#include "AAFilter.h" |
||||
|
||||
using namespace soundtouch; |
||||
|
||||
// Define default interpolation algorithm here
|
||||
TransposerBase::ALGORITHM TransposerBase::algorithm = TransposerBase::CUBIC; |
||||
|
||||
|
||||
// Constructor
|
||||
RateTransposer::RateTransposer() : FIFOProcessor(&outputBuffer) |
||||
{ |
||||
bUseAAFilter = |
||||
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER |
||||
true; |
||||
#else |
||||
// Disable Anti-alias filter if desirable to avoid click at rate change zero value crossover
|
||||
false; |
||||
#endif |
||||
|
||||
// Instantiates the anti-alias filter
|
||||
pAAFilter = new AAFilter(64); |
||||
pTransposer = TransposerBase::newInstance(); |
||||
clear(); |
||||
} |
||||
|
||||
|
||||
RateTransposer::~RateTransposer() |
||||
{ |
||||
delete pAAFilter; |
||||
delete pTransposer; |
||||
} |
||||
|
||||
|
||||
/// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable
|
||||
void RateTransposer::enableAAFilter(bool newMode) |
||||
{ |
||||
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER |
||||
// Disable Anti-alias filter if desirable to avoid click at rate change zero value crossover
|
||||
bUseAAFilter = newMode; |
||||
clear(); |
||||
#endif |
||||
} |
||||
|
||||
|
||||
/// Returns nonzero if anti-alias filter is enabled.
|
||||
bool RateTransposer::isAAFilterEnabled() const |
||||
{ |
||||
return bUseAAFilter; |
||||
} |
||||
|
||||
|
||||
AAFilter *RateTransposer::getAAFilter() |
||||
{ |
||||
return pAAFilter; |
||||
} |
||||
|
||||
|
||||
// Sets new target iRate. Normal iRate = 1.0, smaller values represent slower
|
||||
// iRate, larger faster iRates.
|
||||
void RateTransposer::setRate(double newRate) |
||||
{ |
||||
double fCutoff; |
||||
|
||||
pTransposer->setRate(newRate); |
||||
|
||||
// design a new anti-alias filter
|
||||
if (newRate > 1.0) |
||||
{ |
||||
fCutoff = 0.5 / newRate; |
||||
} |
||||
else |
||||
{ |
||||
fCutoff = 0.5 * newRate; |
||||
} |
||||
pAAFilter->setCutoffFreq(fCutoff); |
||||
} |
||||
|
||||
|
||||
// Adds 'nSamples' pcs of samples from the 'samples' memory position into
|
||||
// the input of the object.
|
||||
void RateTransposer::putSamples(const SAMPLETYPE *samples, uint nSamples) |
||||
{ |
||||
processSamples(samples, nSamples); |
||||
} |
||||
|
||||
|
||||
// Transposes sample rate by applying anti-alias filter to prevent folding.
|
||||
// Returns amount of samples returned in the "dest" buffer.
|
||||
// The maximum amount of samples that can be returned at a time is set by
|
||||
// the 'set_returnBuffer_size' function.
|
||||
void RateTransposer::processSamples(const SAMPLETYPE *src, uint nSamples) |
||||
{ |
||||
if (nSamples == 0) return; |
||||
|
||||
// Store samples to input buffer
|
||||
inputBuffer.putSamples(src, nSamples); |
||||
|
||||
// If anti-alias filter is turned off, simply transpose without applying
|
||||
// the filter
|
||||
if (bUseAAFilter == false) |
||||
{ |
||||
(void)pTransposer->transpose(outputBuffer, inputBuffer); |
||||
return; |
||||
} |
||||
|
||||
assert(pAAFilter); |
||||
|
||||
// Transpose with anti-alias filter
|
||||
if (pTransposer->rate < 1.0f) |
||||
{ |
||||
// If the parameter 'Rate' value is smaller than 1, first transpose
|
||||
// the samples and then apply the anti-alias filter to remove aliasing.
|
||||
|
||||
// Transpose the samples, store the result to end of "midBuffer"
|
||||
pTransposer->transpose(midBuffer, inputBuffer); |
||||
|
||||
// Apply the anti-alias filter for transposed samples in midBuffer
|
||||
pAAFilter->evaluate(outputBuffer, midBuffer); |
||||
} |
||||
else |
||||
{ |
||||
// If the parameter 'Rate' value is larger than 1, first apply the
|
||||
// anti-alias filter to remove high frequencies (prevent them from folding
|
||||
// over the lover frequencies), then transpose.
|
||||
|
||||
// Apply the anti-alias filter for samples in inputBuffer
|
||||
pAAFilter->evaluate(midBuffer, inputBuffer); |
||||
|
||||
// Transpose the AA-filtered samples in "midBuffer"
|
||||
pTransposer->transpose(outputBuffer, midBuffer); |
||||
} |
||||
} |
||||
|
||||
|
||||
// Sets the number of channels, 1 = mono, 2 = stereo
|
||||
void RateTransposer::setChannels(int nChannels) |
||||
{ |
||||
if (!verifyNumberOfChannels(nChannels) || |
||||
(pTransposer->numChannels == nChannels)) return; |
||||
|
||||
pTransposer->setChannels(nChannels); |
||||
inputBuffer.setChannels(nChannels); |
||||
midBuffer.setChannels(nChannels); |
||||
outputBuffer.setChannels(nChannels); |
||||
} |
||||
|
||||
|
||||
// Clears all the samples in the object
|
||||
void RateTransposer::clear() |
||||
{ |
||||
outputBuffer.clear(); |
||||
midBuffer.clear(); |
||||
inputBuffer.clear(); |
||||
pTransposer->resetRegisters(); |
||||
|
||||
// prefill buffer to avoid losing first samples at beginning of stream
|
||||
int prefill = getLatency(); |
||||
inputBuffer.addSilent(prefill); |
||||
} |
||||
|
||||
|
||||
// Returns nonzero if there aren't any samples available for outputting.
|
||||
int RateTransposer::isEmpty() const |
||||
{ |
||||
int res; |
||||
|
||||
res = FIFOProcessor::isEmpty(); |
||||
if (res == 0) return 0; |
||||
return inputBuffer.isEmpty(); |
||||
} |
||||
|
||||
|
||||
/// Return approximate initial input-output latency
|
||||
int RateTransposer::getLatency() const |
||||
{ |
||||
return pTransposer->getLatency() + |
||||
((bUseAAFilter) ? (pAAFilter->getLength() / 2) : 0); |
||||
} |
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TransposerBase - Base class for interpolation
|
||||
//
|
||||
|
||||
// static function to set interpolation algorithm
|
||||
void TransposerBase::setAlgorithm(TransposerBase::ALGORITHM a) |
||||
{ |
||||
TransposerBase::algorithm = a; |
||||
} |
||||
|
||||
|
||||
// Transposes the sample rate of the given samples using linear interpolation.
|
||||
// Returns the number of samples returned in the "dest" buffer
|
||||
int TransposerBase::transpose(FIFOSampleBuffer &dest, FIFOSampleBuffer &src) |
||||
{ |
||||
int numSrcSamples = src.numSamples(); |
||||
int sizeDemand = (int)((double)numSrcSamples / rate) + 8; |
||||
int numOutput; |
||||
SAMPLETYPE *psrc = src.ptrBegin(); |
||||
SAMPLETYPE *pdest = dest.ptrEnd(sizeDemand); |
||||
|
||||
#ifndef USE_MULTICH_ALWAYS |
||||
if (numChannels == 1) |
||||
{ |
||||
numOutput = transposeMono(pdest, psrc, numSrcSamples); |
||||
} |
||||
else if (numChannels == 2) |
||||
{ |
||||
numOutput = transposeStereo(pdest, psrc, numSrcSamples); |
||||
} |
||||
else |
||||
#endif // USE_MULTICH_ALWAYS
|
||||
{ |
||||
assert(numChannels > 0); |
||||
numOutput = transposeMulti(pdest, psrc, numSrcSamples); |
||||
} |
||||
dest.putSamples(numOutput); |
||||
src.receiveSamples(numSrcSamples); |
||||
return numOutput; |
||||
} |
||||
|
||||
|
||||
TransposerBase::TransposerBase() |
||||
{ |
||||
numChannels = 0; |
||||
rate = 1.0f; |
||||
} |
||||
|
||||
|
||||
TransposerBase::~TransposerBase() |
||||
{ |
||||
} |
||||
|
||||
|
||||
void TransposerBase::setChannels(int channels) |
||||
{ |
||||
numChannels = channels; |
||||
resetRegisters(); |
||||
} |
||||
|
||||
|
||||
void TransposerBase::setRate(double newRate) |
||||
{ |
||||
rate = newRate; |
||||
} |
||||
|
||||
|
||||
// static factory function
|
||||
TransposerBase *TransposerBase::newInstance() |
||||
{ |
||||
#ifdef SOUNDTOUCH_INTEGER_SAMPLES |
||||
// Notice: For integer arithmetic support only linear algorithm (due to simplest calculus)
|
||||
return ::new InterpolateLinearInteger; |
||||
#else |
||||
switch (algorithm) |
||||
{ |
||||
case LINEAR: |
||||
return new InterpolateLinearFloat; |
||||
|
||||
case CUBIC: |
||||
return new InterpolateCubic; |
||||
|
||||
case SHANNON: |
||||
return new InterpolateShannon; |
||||
|
||||
default: |
||||
assert(false); |
||||
return nullptr; |
||||
} |
||||
#endif |
||||
} |
||||
@ -1,164 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Sample rate transposer. Changes sample rate by using linear interpolation
|
||||
/// together with anti-alias filtering (first order interpolation with anti-
|
||||
/// alias filtering should be quite adequate for this application).
|
||||
///
|
||||
/// Use either of the derived classes of 'RateTransposerInteger' or
|
||||
/// 'RateTransposerFloat' for corresponding integer/floating point tranposing
|
||||
/// algorithm implementation.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef RateTransposer_H |
||||
#define RateTransposer_H |
||||
|
||||
#include <stddef.h> |
||||
#include "AAFilter.h" |
||||
#include "FIFOSamplePipe.h" |
||||
#include "FIFOSampleBuffer.h" |
||||
|
||||
#include "STTypes.h" |
||||
|
||||
namespace soundtouch |
||||
{ |
||||
|
||||
/// Abstract base class for transposer implementations (linear, advanced vs integer, float etc)
|
||||
class TransposerBase |
||||
{ |
||||
public: |
||||
enum ALGORITHM { |
||||
LINEAR = 0, |
||||
CUBIC, |
||||
SHANNON |
||||
}; |
||||
|
||||
protected: |
||||
virtual int transposeMono(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
int &srcSamples) = 0; |
||||
virtual int transposeStereo(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
int &srcSamples) = 0; |
||||
virtual int transposeMulti(SAMPLETYPE *dest, |
||||
const SAMPLETYPE *src, |
||||
int &srcSamples) = 0; |
||||
|
||||
static ALGORITHM algorithm; |
||||
|
||||
public: |
||||
double rate; |
||||
int numChannels; |
||||
|
||||
TransposerBase(); |
||||
virtual ~TransposerBase(); |
||||
|
||||
virtual int transpose(FIFOSampleBuffer &dest, FIFOSampleBuffer &src); |
||||
virtual void setRate(double newRate); |
||||
virtual void setChannels(int channels); |
||||
virtual int getLatency() const = 0; |
||||
|
||||
virtual void resetRegisters() = 0; |
||||
|
||||
// static factory function
|
||||
static TransposerBase *newInstance(); |
||||
|
||||
// static function to set interpolation algorithm
|
||||
static void setAlgorithm(ALGORITHM a); |
||||
}; |
||||
|
||||
|
||||
/// A common linear samplerate transposer class.
|
||||
///
|
||||
class RateTransposer final : public FIFOProcessor |
||||
{ |
||||
protected: |
||||
/// Anti-alias filter object
|
||||
AAFilter *pAAFilter; |
||||
TransposerBase *pTransposer; |
||||
|
||||
/// Buffer for collecting samples to feed the anti-alias filter between
|
||||
/// two batches
|
||||
FIFOSampleBuffer inputBuffer; |
||||
|
||||
/// Buffer for keeping samples between transposing & anti-alias filter
|
||||
FIFOSampleBuffer midBuffer; |
||||
|
||||
/// Output sample buffer
|
||||
FIFOSampleBuffer outputBuffer; |
||||
|
||||
bool bUseAAFilter; |
||||
|
||||
|
||||
/// Transposes sample rate by applying anti-alias filter to prevent folding.
|
||||
/// Returns amount of samples returned in the "dest" buffer.
|
||||
/// The maximum amount of samples that can be returned at a time is set by
|
||||
/// the 'set_returnBuffer_size' function.
|
||||
void processSamples(const SAMPLETYPE *src, |
||||
uint numSamples); |
||||
|
||||
public: |
||||
RateTransposer(); |
||||
virtual ~RateTransposer() override; |
||||
|
||||
/// Returns the output buffer object
|
||||
FIFOSamplePipe *getOutput() { return &outputBuffer; }; |
||||
|
||||
/// Return anti-alias filter object
|
||||
AAFilter *getAAFilter(); |
||||
|
||||
/// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable
|
||||
void enableAAFilter(bool newMode); |
||||
|
||||
/// Returns nonzero if anti-alias filter is enabled.
|
||||
bool isAAFilterEnabled() const; |
||||
|
||||
/// Sets new target rate. Normal rate = 1.0, smaller values represent slower
|
||||
/// rate, larger faster rates.
|
||||
virtual void setRate(double newRate); |
||||
|
||||
/// Sets the number of channels, 1 = mono, 2 = stereo
|
||||
void setChannels(int channels); |
||||
|
||||
/// Adds 'numSamples' pcs of samples from the 'samples' memory position into
|
||||
/// the input of the object.
|
||||
void putSamples(const SAMPLETYPE *samples, uint numSamples) override; |
||||
|
||||
/// Clears all the samples in the object
|
||||
void clear() override; |
||||
|
||||
/// Returns nonzero if there aren't any samples available for outputting.
|
||||
int isEmpty() const override; |
||||
|
||||
/// Return approximate initial input-output latency
|
||||
int getLatency() const; |
||||
}; |
||||
|
||||
} |
||||
|
||||
#endif |
||||
@ -1,538 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// SoundTouch - main class for tempo/pitch/rate adjusting routines.
|
||||
///
|
||||
/// Notes:
|
||||
/// - Initialize the SoundTouch object instance by setting up the sound stream
|
||||
/// parameters with functions 'setSampleRate' and 'setChannels', then set
|
||||
/// desired tempo/pitch/rate settings with the corresponding functions.
|
||||
///
|
||||
/// - The SoundTouch class behaves like a first-in-first-out pipeline: The
|
||||
/// samples that are to be processed are fed into one of the pipe by calling
|
||||
/// function 'putSamples', while the ready processed samples can be read
|
||||
/// from the other end of the pipeline with function 'receiveSamples'.
|
||||
///
|
||||
/// - The SoundTouch processing classes require certain sized 'batches' of
|
||||
/// samples in order to process the sound. For this reason the classes buffer
|
||||
/// incoming samples until there are enough of samples available for
|
||||
/// processing, then they carry out the processing step and consequently
|
||||
/// make the processed samples available for outputting.
|
||||
///
|
||||
/// - For the above reason, the processing routines introduce a certain
|
||||
/// 'latency' between the input and output, so that the samples input to
|
||||
/// SoundTouch may not be immediately available in the output, and neither
|
||||
/// the amount of outputtable samples may not immediately be in direct
|
||||
/// relationship with the amount of previously input samples.
|
||||
///
|
||||
/// - The tempo/pitch/rate control parameters can be altered during processing.
|
||||
/// Please notice though that they aren't currently protected by semaphores,
|
||||
/// so in multi-thread application external semaphore protection may be
|
||||
/// required.
|
||||
///
|
||||
/// - This class utilizes classes 'TDStretch' for tempo change (without modifying
|
||||
/// pitch) and 'RateTransposer' for changing the playback rate (that is, both
|
||||
/// tempo and pitch in the same ratio) of the sound. The third available control
|
||||
/// 'pitch' (change pitch but maintain tempo) is produced by a combination of
|
||||
/// combining the two other controls.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <assert.h> |
||||
#include <stdlib.h> |
||||
#include <memory.h> |
||||
#include <math.h> |
||||
#include <stdio.h> |
||||
|
||||
#include "SoundTouch.h" |
||||
#include "TDStretch.h" |
||||
#include "RateTransposer.h" |
||||
#include "cpu_detect.h" |
||||
|
||||
using namespace soundtouch; |
||||
|
||||
/// test if two floating point numbers are equal
|
||||
#define TEST_FLOAT_EQUAL(a, b) (fabs(a - b) < 1e-10) |
||||
|
||||
|
||||
/// Print library version string for autoconf
|
||||
extern "C" void soundtouch_ac_test() |
||||
{ |
||||
printf("SoundTouch Version: %s\n",SOUNDTOUCH_VERSION); |
||||
} |
||||
|
||||
|
||||
SoundTouch::SoundTouch() |
||||
{ |
||||
// Initialize rate transposer and tempo changer instances
|
||||
|
||||
pRateTransposer = new RateTransposer(); |
||||
pTDStretch = TDStretch::newInstance(); |
||||
|
||||
setOutPipe(pTDStretch); |
||||
|
||||
rate = tempo = 0; |
||||
|
||||
virtualPitch = |
||||
virtualRate = |
||||
virtualTempo = 1.0; |
||||
|
||||
calcEffectiveRateAndTempo(); |
||||
|
||||
samplesExpectedOut = 0; |
||||
samplesOutput = 0; |
||||
|
||||
channels = 0; |
||||
bSrateSet = false; |
||||
} |
||||
|
||||
|
||||
SoundTouch::~SoundTouch() |
||||
{ |
||||
delete pRateTransposer; |
||||
delete pTDStretch; |
||||
} |
||||
|
||||
|
||||
/// Get SoundTouch library version string
|
||||
const char *SoundTouch::getVersionString() |
||||
{ |
||||
static const char *_version = SOUNDTOUCH_VERSION; |
||||
|
||||
return _version; |
||||
} |
||||
|
||||
|
||||
/// Get SoundTouch library version Id
|
||||
uint SoundTouch::getVersionId() |
||||
{ |
||||
return SOUNDTOUCH_VERSION_ID; |
||||
} |
||||
|
||||
|
||||
// Sets the number of channels, 1 = mono, 2 = stereo
|
||||
void SoundTouch::setChannels(uint numChannels) |
||||
{ |
||||
if (!verifyNumberOfChannels(numChannels)) return; |
||||
|
||||
channels = numChannels; |
||||
pRateTransposer->setChannels((int)numChannels); |
||||
pTDStretch->setChannels((int)numChannels); |
||||
} |
||||
|
||||
|
||||
// Sets new rate control value. Normal rate = 1.0, smaller values
|
||||
// represent slower rate, larger faster rates.
|
||||
void SoundTouch::setRate(double newRate) |
||||
{ |
||||
virtualRate = newRate; |
||||
calcEffectiveRateAndTempo(); |
||||
} |
||||
|
||||
|
||||
// Sets new rate control value as a difference in percents compared
|
||||
// to the original rate (-50 .. +100 %)
|
||||
void SoundTouch::setRateChange(double newRate) |
||||
{ |
||||
virtualRate = 1.0 + 0.01 * newRate; |
||||
calcEffectiveRateAndTempo(); |
||||
} |
||||
|
||||
|
||||
// Sets new tempo control value. Normal tempo = 1.0, smaller values
|
||||
// represent slower tempo, larger faster tempo.
|
||||
void SoundTouch::setTempo(double newTempo) |
||||
{ |
||||
virtualTempo = newTempo; |
||||
calcEffectiveRateAndTempo(); |
||||
} |
||||
|
||||
|
||||
// Sets new tempo control value as a difference in percents compared
|
||||
// to the original tempo (-50 .. +100 %)
|
||||
void SoundTouch::setTempoChange(double newTempo) |
||||
{ |
||||
virtualTempo = 1.0 + 0.01 * newTempo; |
||||
calcEffectiveRateAndTempo(); |
||||
} |
||||
|
||||
|
||||
// Sets new pitch control value. Original pitch = 1.0, smaller values
|
||||
// represent lower pitches, larger values higher pitch.
|
||||
void SoundTouch::setPitch(double newPitch) |
||||
{ |
||||
virtualPitch = newPitch; |
||||
calcEffectiveRateAndTempo(); |
||||
} |
||||
|
||||
|
||||
// Sets pitch change in octaves compared to the original pitch
|
||||
// (-1.00 .. +1.00)
|
||||
void SoundTouch::setPitchOctaves(double newPitch) |
||||
{ |
||||
virtualPitch = exp(0.69314718056 * newPitch); |
||||
calcEffectiveRateAndTempo(); |
||||
} |
||||
|
||||
|
||||
// Sets pitch change in semi-tones compared to the original pitch
|
||||
// (-12 .. +12)
|
||||
void SoundTouch::setPitchSemiTones(int newPitch) |
||||
{ |
||||
setPitchOctaves((double)newPitch / 12.0); |
||||
} |
||||
|
||||
|
||||
void SoundTouch::setPitchSemiTones(double newPitch) |
||||
{ |
||||
setPitchOctaves(newPitch / 12.0); |
||||
} |
||||
|
||||
|
||||
// Calculates 'effective' rate and tempo values from the
|
||||
// nominal control values.
|
||||
void SoundTouch::calcEffectiveRateAndTempo() |
||||
{ |
||||
double oldTempo = tempo; |
||||
double oldRate = rate; |
||||
|
||||
tempo = virtualTempo / virtualPitch; |
||||
rate = virtualPitch * virtualRate; |
||||
|
||||
if (!TEST_FLOAT_EQUAL(rate,oldRate)) pRateTransposer->setRate(rate); |
||||
if (!TEST_FLOAT_EQUAL(tempo, oldTempo)) pTDStretch->setTempo(tempo); |
||||
|
||||
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER |
||||
if (rate <= 1.0f) |
||||
{ |
||||
if (output != pTDStretch) |
||||
{ |
||||
FIFOSamplePipe *tempoOut; |
||||
|
||||
assert(output == pRateTransposer); |
||||
// move samples in the current output buffer to the output of pTDStretch
|
||||
tempoOut = pTDStretch->getOutput(); |
||||
tempoOut->moveSamples(*output); |
||||
// move samples in pitch transposer's store buffer to tempo changer's input
|
||||
// deprecated : pTDStretch->moveSamples(*pRateTransposer->getStore());
|
||||
|
||||
output = pTDStretch; |
||||
} |
||||
} |
||||
else |
||||
#endif |
||||
{ |
||||
if (output != pRateTransposer) |
||||
{ |
||||
FIFOSamplePipe *transOut; |
||||
|
||||
assert(output == pTDStretch); |
||||
// move samples in the current output buffer to the output of pRateTransposer
|
||||
transOut = pRateTransposer->getOutput(); |
||||
transOut->moveSamples(*output); |
||||
// move samples in tempo changer's input to pitch transposer's input
|
||||
pRateTransposer->moveSamples(*pTDStretch->getInput()); |
||||
|
||||
output = pRateTransposer; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
// Sets sample rate.
|
||||
void SoundTouch::setSampleRate(uint srate) |
||||
{ |
||||
// set sample rate, leave other tempo changer parameters as they are.
|
||||
pTDStretch->setParameters((int)srate); |
||||
bSrateSet = true; |
||||
} |
||||
|
||||
|
||||
// Adds 'numSamples' pcs of samples from the 'samples' memory position into
|
||||
// the input of the object.
|
||||
void SoundTouch::putSamples(const SAMPLETYPE *samples, uint nSamples) |
||||
{ |
||||
if (bSrateSet == false) |
||||
{ |
||||
ST_THROW_RT_ERROR("SoundTouch : Sample rate not defined"); |
||||
} |
||||
else if (channels == 0) |
||||
{ |
||||
ST_THROW_RT_ERROR("SoundTouch : Number of channels not defined"); |
||||
} |
||||
|
||||
// accumulate how many samples are expected out from processing, given the current
|
||||
// processing setting
|
||||
samplesExpectedOut += (double)nSamples / ((double)rate * (double)tempo); |
||||
|
||||
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER |
||||
if (rate <= 1.0f) |
||||
{ |
||||
// transpose the rate down, output the transposed sound to tempo changer buffer
|
||||
assert(output == pTDStretch); |
||||
pRateTransposer->putSamples(samples, nSamples); |
||||
pTDStretch->moveSamples(*pRateTransposer); |
||||
} |
||||
else |
||||
#endif |
||||
{ |
||||
// evaluate the tempo changer, then transpose the rate up,
|
||||
assert(output == pRateTransposer); |
||||
pTDStretch->putSamples(samples, nSamples); |
||||
pRateTransposer->moveSamples(*pTDStretch); |
||||
} |
||||
} |
||||
|
||||
|
||||
// Flushes the last samples from the processing pipeline to the output.
|
||||
// Clears also the internal processing buffers.
|
||||
//
|
||||
// Note: This function is meant for extracting the last samples of a sound
|
||||
// stream. This function may introduce additional blank samples in the end
|
||||
// of the sound stream, and thus it's not recommended to call this function
|
||||
// in the middle of a sound stream.
|
||||
void SoundTouch::flush() |
||||
{ |
||||
int i; |
||||
int numStillExpected; |
||||
SAMPLETYPE *buff = new SAMPLETYPE[128 * channels]; |
||||
|
||||
// how many samples are still expected to output
|
||||
numStillExpected = (int)((long)(samplesExpectedOut + 0.5) - samplesOutput); |
||||
if (numStillExpected < 0) numStillExpected = 0; |
||||
|
||||
memset(buff, 0, 128 * channels * sizeof(SAMPLETYPE)); |
||||
// "Push" the last active samples out from the processing pipeline by
|
||||
// feeding blank samples into the processing pipeline until new,
|
||||
// processed samples appear in the output (not however, more than
|
||||
// 24ksamples in any case)
|
||||
for (i = 0; (numStillExpected > (int)numSamples()) && (i < 200); i ++) |
||||
{ |
||||
putSamples(buff, 128); |
||||
} |
||||
|
||||
adjustAmountOfSamples(numStillExpected); |
||||
|
||||
delete[] buff; |
||||
|
||||
// Clear input buffers
|
||||
pTDStretch->clearInput(); |
||||
// yet leave the output intouched as that's where the
|
||||
// flushed samples are!
|
||||
} |
||||
|
||||
|
||||
// Changes a setting controlling the processing system behaviour. See the
|
||||
// 'SETTING_...' defines for available setting ID's.
|
||||
bool SoundTouch::setSetting(int settingId, int value) |
||||
{ |
||||
int sampleRate, sequenceMs, seekWindowMs, overlapMs; |
||||
|
||||
// read current tdstretch routine parameters
|
||||
pTDStretch->getParameters(&sampleRate, &sequenceMs, &seekWindowMs, &overlapMs); |
||||
|
||||
switch (settingId) |
||||
{ |
||||
case SETTING_USE_AA_FILTER : |
||||
// enables / disabless anti-alias filter
|
||||
pRateTransposer->enableAAFilter((value != 0) ? true : false); |
||||
return true; |
||||
|
||||
case SETTING_AA_FILTER_LENGTH : |
||||
// sets anti-alias filter length
|
||||
pRateTransposer->getAAFilter()->setLength(value); |
||||
return true; |
||||
|
||||
case SETTING_USE_QUICKSEEK : |
||||
// enables / disables tempo routine quick seeking algorithm
|
||||
pTDStretch->enableQuickSeek((value != 0) ? true : false); |
||||
return true; |
||||
|
||||
case SETTING_SEQUENCE_MS: |
||||
// change time-stretch sequence duration parameter
|
||||
pTDStretch->setParameters(sampleRate, value, seekWindowMs, overlapMs); |
||||
return true; |
||||
|
||||
case SETTING_SEEKWINDOW_MS: |
||||
// change time-stretch seek window length parameter
|
||||
pTDStretch->setParameters(sampleRate, sequenceMs, value, overlapMs); |
||||
return true; |
||||
|
||||
case SETTING_OVERLAP_MS: |
||||
// change time-stretch overlap length parameter
|
||||
pTDStretch->setParameters(sampleRate, sequenceMs, seekWindowMs, value); |
||||
return true; |
||||
|
||||
default : |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
|
||||
// Reads a setting controlling the processing system behaviour. See the
|
||||
// 'SETTING_...' defines for available setting ID's.
|
||||
//
|
||||
// Returns the setting value.
|
||||
int SoundTouch::getSetting(int settingId) const |
||||
{ |
||||
int temp; |
||||
|
||||
switch (settingId) |
||||
{ |
||||
case SETTING_USE_AA_FILTER : |
||||
return (uint)pRateTransposer->isAAFilterEnabled(); |
||||
|
||||
case SETTING_AA_FILTER_LENGTH : |
||||
return pRateTransposer->getAAFilter()->getLength(); |
||||
|
||||
case SETTING_USE_QUICKSEEK : |
||||
return (uint)pTDStretch->isQuickSeekEnabled(); |
||||
|
||||
case SETTING_SEQUENCE_MS: |
||||
pTDStretch->getParameters(nullptr, &temp, nullptr, nullptr); |
||||
return temp; |
||||
|
||||
case SETTING_SEEKWINDOW_MS: |
||||
pTDStretch->getParameters(nullptr, nullptr, &temp, nullptr); |
||||
return temp; |
||||
|
||||
case SETTING_OVERLAP_MS: |
||||
pTDStretch->getParameters(nullptr, nullptr, nullptr, &temp); |
||||
return temp; |
||||
|
||||
case SETTING_NOMINAL_INPUT_SEQUENCE : |
||||
{ |
||||
int size = pTDStretch->getInputSampleReq(); |
||||
|
||||
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER |
||||
if (rate <= 1.0) |
||||
{ |
||||
// transposing done before timestretch, which impacts latency
|
||||
return (int)(size * rate + 0.5); |
||||
} |
||||
#endif |
||||
return size; |
||||
} |
||||
|
||||
case SETTING_NOMINAL_OUTPUT_SEQUENCE : |
||||
{ |
||||
int size = pTDStretch->getOutputBatchSize(); |
||||
|
||||
if (rate > 1.0) |
||||
{ |
||||
// transposing done after timestretch, which impacts latency
|
||||
return (int)(size / rate + 0.5); |
||||
} |
||||
return size; |
||||
} |
||||
|
||||
case SETTING_INITIAL_LATENCY: |
||||
{ |
||||
double latency = pTDStretch->getLatency(); |
||||
int latency_tr = pRateTransposer->getLatency(); |
||||
|
||||
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER |
||||
if (rate <= 1.0) |
||||
{ |
||||
// transposing done before timestretch, which impacts latency
|
||||
latency = (latency + latency_tr) * rate; |
||||
} |
||||
else |
||||
#endif |
||||
{ |
||||
latency += (double)latency_tr / rate; |
||||
} |
||||
|
||||
return (int)(latency + 0.5); |
||||
} |
||||
|
||||
default : |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
|
||||
// Clears all the samples in the object's output and internal processing
|
||||
// buffers.
|
||||
void SoundTouch::clear() |
||||
{ |
||||
samplesExpectedOut = 0; |
||||
samplesOutput = 0; |
||||
pRateTransposer->clear(); |
||||
pTDStretch->clear(); |
||||
} |
||||
|
||||
|
||||
/// Returns number of samples currently unprocessed.
|
||||
uint SoundTouch::numUnprocessedSamples() const |
||||
{ |
||||
FIFOSamplePipe * psp; |
||||
if (pTDStretch) |
||||
{ |
||||
psp = pTDStretch->getInput(); |
||||
if (psp) |
||||
{ |
||||
return psp->numSamples(); |
||||
} |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
|
||||
/// Output samples from beginning of the sample buffer. Copies requested samples to
|
||||
/// output buffer and removes them from the sample buffer. If there are less than
|
||||
/// 'numsample' samples in the buffer, returns all that available.
|
||||
///
|
||||
/// \return Number of samples returned.
|
||||
uint SoundTouch::receiveSamples(SAMPLETYPE *output, uint maxSamples) |
||||
{ |
||||
uint ret = FIFOProcessor::receiveSamples(output, maxSamples); |
||||
samplesOutput += (long)ret; |
||||
return ret; |
||||
} |
||||
|
||||
|
||||
/// Adjusts book-keeping so that given number of samples are removed from beginning of the
|
||||
/// sample buffer without copying them anywhere.
|
||||
///
|
||||
/// Used to reduce the number of samples in the buffer when accessing the sample buffer directly
|
||||
/// with 'ptrBegin' function.
|
||||
uint SoundTouch::receiveSamples(uint maxSamples) |
||||
{ |
||||
uint ret = FIFOProcessor::receiveSamples(maxSamples); |
||||
samplesOutput += (long)ret; |
||||
return ret; |
||||
} |
||||
|
||||
|
||||
/// Get ratio between input and output audio durations, useful for calculating
|
||||
/// processed output duration: if you'll process a stream of N samples, then
|
||||
/// you can expect to get out N * getInputOutputSampleRatio() samples.
|
||||
double SoundTouch::getInputOutputSampleRatio() |
||||
{ |
||||
return 1.0 / (tempo * rate); |
||||
} |
||||
@ -1,29 +0,0 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00 |
||||
# Visual Studio 14 |
||||
VisualStudioVersion = 14.0.23107.0 |
||||
MinimumVisualStudioVersion = 10.0.40219.1 |
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SoundTouch", "SoundTouch.vcxproj", "{68A5DD20-7057-448B-8FE0-B6AC8D205509}" |
||||
EndProject |
||||
Global |
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution |
||||
Debug|Win32 = Debug|Win32 |
||||
Debug|x64 = Debug|x64 |
||||
Release|Win32 = Release|Win32 |
||||
Release|x64 = Release|x64 |
||||
ReleaseX64|Win32 = ReleaseX64|Win32 |
||||
ReleaseX64|x64 = ReleaseX64|x64 |
||||
EndGlobalSection |
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution |
||||
{68A5DD20-7057-448B-8FE0-B6AC8D205509}.Debug|Win32.ActiveCfg = Debug|Win32 |
||||
{68A5DD20-7057-448B-8FE0-B6AC8D205509}.Debug|x64.ActiveCfg = Debug|x64 |
||||
{68A5DD20-7057-448B-8FE0-B6AC8D205509}.Release|Win32.ActiveCfg = Release|Win32 |
||||
{68A5DD20-7057-448B-8FE0-B6AC8D205509}.Release|x64.ActiveCfg = Release|x64 |
||||
{68A5DD20-7057-448B-8FE0-B6AC8D205509}.ReleaseX64|Win32.ActiveCfg = ReleaseX64|Win32 |
||||
{68A5DD20-7057-448B-8FE0-B6AC8D205509}.ReleaseX64|Win32.Build.0 = ReleaseX64|Win32 |
||||
{68A5DD20-7057-448B-8FE0-B6AC8D205509}.ReleaseX64|x64.ActiveCfg = ReleaseX64|x64 |
||||
{68A5DD20-7057-448B-8FE0-B6AC8D205509}.ReleaseX64|x64.Build.0 = ReleaseX64|x64 |
||||
EndGlobalSection |
||||
GlobalSection(SolutionProperties) = preSolution |
||||
HideSolutionNode = FALSE |
||||
EndGlobalSection |
||||
EndGlobal |
||||
@ -1,342 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup Label="ProjectConfigurations"> |
||||
<ProjectConfiguration Include="Debug|Win32"> |
||||
<Configuration>Debug</Configuration> |
||||
<Platform>Win32</Platform> |
||||
</ProjectConfiguration> |
||||
<ProjectConfiguration Include="Debug|x64"> |
||||
<Configuration>Debug</Configuration> |
||||
<Platform>x64</Platform> |
||||
</ProjectConfiguration> |
||||
<ProjectConfiguration Include="Release|Win32"> |
||||
<Configuration>Release</Configuration> |
||||
<Platform>Win32</Platform> |
||||
</ProjectConfiguration> |
||||
<ProjectConfiguration Include="Release|x64"> |
||||
<Configuration>Release</Configuration> |
||||
<Platform>x64</Platform> |
||||
</ProjectConfiguration> |
||||
</ItemGroup> |
||||
<PropertyGroup Label="Globals"> |
||||
<ProjectGuid>{68A5DD20-7057-448B-8FE0-B6AC8D205509}</ProjectGuid> |
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion> |
||||
</PropertyGroup> |
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> |
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> |
||||
<ConfigurationType>StaticLibrary</ConfigurationType> |
||||
<PlatformToolset>v142</PlatformToolset> |
||||
<UseOfMfc>false</UseOfMfc> |
||||
<CharacterSet>Unicode</CharacterSet> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> |
||||
<ConfigurationType>StaticLibrary</ConfigurationType> |
||||
<PlatformToolset>v142</PlatformToolset> |
||||
<UseOfMfc>false</UseOfMfc> |
||||
<CharacterSet>Unicode</CharacterSet> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> |
||||
<ConfigurationType>StaticLibrary</ConfigurationType> |
||||
<PlatformToolset>v142</PlatformToolset> |
||||
<UseOfMfc>false</UseOfMfc> |
||||
<CharacterSet>Unicode</CharacterSet> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> |
||||
<ConfigurationType>StaticLibrary</ConfigurationType> |
||||
<PlatformToolset>v142</PlatformToolset> |
||||
<UseOfMfc>false</UseOfMfc> |
||||
<CharacterSet>Unicode</CharacterSet> |
||||
</PropertyGroup> |
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> |
||||
<ImportGroup Label="ExtensionSettings"> |
||||
</ImportGroup> |
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> |
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> |
||||
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" /> |
||||
</ImportGroup> |
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> |
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> |
||||
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" /> |
||||
</ImportGroup> |
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> |
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> |
||||
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" /> |
||||
</ImportGroup> |
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> |
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> |
||||
<Import Project="$(VCTargetsPath)Microsoft.CPP.UpgradeFromVC71.props" /> |
||||
</ImportGroup> |
||||
<PropertyGroup Label="UserMacros" /> |
||||
<PropertyGroup> |
||||
<_ProjectFileVersion>14.0.23107.0</_ProjectFileVersion> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> |
||||
<OutDir>$(Platform)\$(Configuration)\</OutDir> |
||||
<IntDir>$(Platform)\$(Configuration)\</IntDir> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> |
||||
<OutDir>$(Platform)\$(Configuration)\</OutDir> |
||||
<IntDir>$(Platform)\$(Configuration)\</IntDir> |
||||
<TargetName>$(ProjectName)_x64</TargetName> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> |
||||
<OutDir>$(Platform)\$(Configuration)\</OutDir> |
||||
<IntDir>$(Platform)\$(Configuration)\</IntDir> |
||||
<TargetName>$(ProjectName)D</TargetName> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> |
||||
<OutDir>$(Platform)\$(Configuration)\</OutDir> |
||||
<IntDir>$(Platform)\$(Configuration)\</IntDir> |
||||
<TargetName>$(ProjectName)D_x64</TargetName> |
||||
</PropertyGroup> |
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> |
||||
<ClCompile> |
||||
<Optimization>Full</Optimization> |
||||
<InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion> |
||||
<IntrinsicFunctions>true</IntrinsicFunctions> |
||||
<AdditionalIncludeDirectories>..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> |
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
<StringPooling>true</StringPooling> |
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary> |
||||
<FunctionLevelLinking>true</FunctionLevelLinking> |
||||
<FloatingPointModel>Fast</FloatingPointModel> |
||||
<PrecompiledHeader /> |
||||
<PrecompiledHeaderOutputFile>$(OutDir)$(TargetName).pch</PrecompiledHeaderOutputFile> |
||||
<AssemblerListingLocation>$(OutDir)</AssemblerListingLocation> |
||||
<ObjectFileName>$(OutDir)</ObjectFileName> |
||||
<ProgramDataBaseFileName>$(OutDir)</ProgramDataBaseFileName> |
||||
<WarningLevel>Level3</WarningLevel> |
||||
<SuppressStartupBanner>true</SuppressStartupBanner> |
||||
<DebugInformationFormat /> |
||||
<CompileAs>Default</CompileAs> |
||||
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet> |
||||
<XMLDocumentationFileName>$(IntDir)</XMLDocumentationFileName> |
||||
<BrowseInformationFile>$(IntDir)</BrowseInformationFile> |
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation> |
||||
</ClCompile> |
||||
<ResourceCompile> |
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
<Culture>0x040b</Culture> |
||||
</ResourceCompile> |
||||
<Lib> |
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> |
||||
<SuppressStartupBanner>true</SuppressStartupBanner> |
||||
</Lib> |
||||
<PostBuildEvent> |
||||
<Command>if not exist ..\..\lib mkdir ..\..\lib |
||||
copy $(OutDir)$(TargetName)$(TargetExt) ..\..\lib</Command> |
||||
</PostBuildEvent> |
||||
</ItemDefinitionGroup> |
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> |
||||
<Midl> |
||||
<TargetEnvironment>X64</TargetEnvironment> |
||||
</Midl> |
||||
<ClCompile> |
||||
<Optimization>Full</Optimization> |
||||
<InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion> |
||||
<IntrinsicFunctions>true</IntrinsicFunctions> |
||||
<AdditionalIncludeDirectories>..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> |
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
<StringPooling>true</StringPooling> |
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary> |
||||
<FunctionLevelLinking>true</FunctionLevelLinking> |
||||
<FloatingPointModel>Fast</FloatingPointModel> |
||||
<PrecompiledHeader /> |
||||
<PrecompiledHeaderOutputFile>$(OutDir)$(TargetName).pch</PrecompiledHeaderOutputFile> |
||||
<AssemblerListingLocation>$(OutDir)</AssemblerListingLocation> |
||||
<ObjectFileName>$(OutDir)</ObjectFileName> |
||||
<ProgramDataBaseFileName>$(OutDir)</ProgramDataBaseFileName> |
||||
<WarningLevel>Level3</WarningLevel> |
||||
<SuppressStartupBanner>true</SuppressStartupBanner> |
||||
<DebugInformationFormat /> |
||||
<CompileAs>Default</CompileAs> |
||||
<EnableEnhancedInstructionSet> |
||||
</EnableEnhancedInstructionSet> |
||||
<XMLDocumentationFileName>$(IntDir)</XMLDocumentationFileName> |
||||
<BrowseInformationFile>$(IntDir)</BrowseInformationFile> |
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation> |
||||
</ClCompile> |
||||
<ResourceCompile> |
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
<Culture>0x040b</Culture> |
||||
</ResourceCompile> |
||||
<Lib> |
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> |
||||
<SuppressStartupBanner>true</SuppressStartupBanner> |
||||
</Lib> |
||||
<PostBuildEvent> |
||||
<Command>if not exist ..\..\lib mkdir ..\..\lib |
||||
copy $(OutDir)$(TargetName)$(TargetExt) ..\..\lib</Command> |
||||
</PostBuildEvent> |
||||
</ItemDefinitionGroup> |
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> |
||||
<ClCompile> |
||||
<Optimization>Disabled</Optimization> |
||||
<AdditionalIncludeDirectories>..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> |
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> |
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> |
||||
<FloatingPointModel>Fast</FloatingPointModel> |
||||
<PrecompiledHeader /> |
||||
<PrecompiledHeaderOutputFile>$(OutDir)$(TargetName).pch</PrecompiledHeaderOutputFile> |
||||
<AssemblerListingLocation>$(OutDir)</AssemblerListingLocation> |
||||
<ObjectFileName>$(OutDir)</ObjectFileName> |
||||
<ProgramDataBaseFileName>$(OutDir)</ProgramDataBaseFileName> |
||||
<BrowseInformation>true</BrowseInformation> |
||||
<WarningLevel>Level3</WarningLevel> |
||||
<SuppressStartupBanner>true</SuppressStartupBanner> |
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> |
||||
<CompileAs>Default</CompileAs> |
||||
<EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet> |
||||
<XMLDocumentationFileName>$(IntDir)</XMLDocumentationFileName> |
||||
<BrowseInformationFile>$(IntDir)</BrowseInformationFile> |
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation> |
||||
</ClCompile> |
||||
<ResourceCompile> |
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
<Culture>0x040b</Culture> |
||||
</ResourceCompile> |
||||
<Lib> |
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> |
||||
<SuppressStartupBanner>true</SuppressStartupBanner> |
||||
</Lib> |
||||
<PostBuildEvent> |
||||
<Command>if not exist ..\..\lib mkdir ..\..\lib |
||||
copy $(OutDir)$(TargetName)$(TargetExt) ..\..\lib</Command> |
||||
</PostBuildEvent> |
||||
</ItemDefinitionGroup> |
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> |
||||
<Midl> |
||||
<TargetEnvironment>X64</TargetEnvironment> |
||||
</Midl> |
||||
<ClCompile> |
||||
<Optimization>Disabled</Optimization> |
||||
<AdditionalIncludeDirectories>..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> |
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> |
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> |
||||
<FloatingPointModel>Fast</FloatingPointModel> |
||||
<PrecompiledHeader /> |
||||
<PrecompiledHeaderOutputFile>$(OutDir)$(TargetName).pch</PrecompiledHeaderOutputFile> |
||||
<AssemblerListingLocation>$(OutDir)</AssemblerListingLocation> |
||||
<ObjectFileName>$(OutDir)</ObjectFileName> |
||||
<ProgramDataBaseFileName>$(OutDir)</ProgramDataBaseFileName> |
||||
<BrowseInformation>true</BrowseInformation> |
||||
<WarningLevel>Level3</WarningLevel> |
||||
<SuppressStartupBanner>true</SuppressStartupBanner> |
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat> |
||||
<CompileAs>Default</CompileAs> |
||||
<EnableEnhancedInstructionSet> |
||||
</EnableEnhancedInstructionSet> |
||||
<XMLDocumentationFileName>$(IntDir)</XMLDocumentationFileName> |
||||
<BrowseInformationFile>$(IntDir)</BrowseInformationFile> |
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation> |
||||
</ClCompile> |
||||
<ResourceCompile> |
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> |
||||
<Culture>0x040b</Culture> |
||||
</ResourceCompile> |
||||
<Lib> |
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> |
||||
<SuppressStartupBanner>true</SuppressStartupBanner> |
||||
</Lib> |
||||
<PostBuildEvent> |
||||
<Command>if not exist ..\..\lib mkdir ..\..\lib |
||||
copy $(OutDir)$(TargetName)$(TargetExt) ..\..\lib</Command> |
||||
</PostBuildEvent> |
||||
</ItemDefinitionGroup> |
||||
<ItemGroup> |
||||
<ClCompile Include="AAFilter.cpp"> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization> |
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks> |
||||
<BrowseInformation Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</BrowseInformation> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Disabled</Optimization> |
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">EnableFastChecks</BasicRuntimeChecks> |
||||
<BrowseInformation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</BrowseInformation> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">MaxSpeed</Optimization> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization> |
||||
</ClCompile> |
||||
<ClCompile Include="BPMDetect.cpp"> |
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">4996</DisableSpecificWarnings> |
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">4996</DisableSpecificWarnings> |
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">4996</DisableSpecificWarnings> |
||||
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Release|x64'">4996</DisableSpecificWarnings> |
||||
</ClCompile> |
||||
<ClCompile Include="cpu_detect_x86.cpp" /> |
||||
<ClCompile Include="FIFOSampleBuffer.cpp"> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization> |
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks> |
||||
<BrowseInformation Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</BrowseInformation> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Disabled</Optimization> |
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">EnableFastChecks</BasicRuntimeChecks> |
||||
<BrowseInformation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</BrowseInformation> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">MaxSpeed</Optimization> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization> |
||||
</ClCompile> |
||||
<ClCompile Include="FIRFilter.cpp"> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization> |
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks> |
||||
<BrowseInformation Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</BrowseInformation> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Disabled</Optimization> |
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">EnableFastChecks</BasicRuntimeChecks> |
||||
<BrowseInformation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</BrowseInformation> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">MaxSpeed</Optimization> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization> |
||||
</ClCompile> |
||||
<ClCompile Include="InterpolateCubic.cpp" /> |
||||
<ClCompile Include="InterpolateLinear.cpp" /> |
||||
<ClCompile Include="InterpolateShannon.cpp" /> |
||||
<ClCompile Include="mmx_optimized.cpp" /> |
||||
<ClCompile Include="PeakFinder.cpp" /> |
||||
<ClCompile Include="RateTransposer.cpp"> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization> |
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks> |
||||
<BrowseInformation Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</BrowseInformation> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Disabled</Optimization> |
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">EnableFastChecks</BasicRuntimeChecks> |
||||
<BrowseInformation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</BrowseInformation> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">MaxSpeed</Optimization> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization> |
||||
</ClCompile> |
||||
<ClCompile Include="SoundTouch.cpp"> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization> |
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks> |
||||
<BrowseInformation Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</BrowseInformation> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Disabled</Optimization> |
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">EnableFastChecks</BasicRuntimeChecks> |
||||
<BrowseInformation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</BrowseInformation> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">MaxSpeed</Optimization> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization> |
||||
</ClCompile> |
||||
<ClCompile Include="sse_optimized.cpp" /> |
||||
<ClCompile Include="TDStretch.cpp"> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization> |
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks> |
||||
<BrowseInformation Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</BrowseInformation> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Disabled</Optimization> |
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">EnableFastChecks</BasicRuntimeChecks> |
||||
<BrowseInformation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</BrowseInformation> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">MaxSpeed</Optimization> |
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization> |
||||
</ClCompile> |
||||
</ItemGroup> |
||||
<ItemGroup> |
||||
<ClInclude Include="..\..\include\BPMDetect.h" /> |
||||
<ClInclude Include="..\..\include\FIFOSampleBuffer.h" /> |
||||
<ClInclude Include="..\..\include\FIFOSamplePipe.h" /> |
||||
<ClInclude Include="..\..\include\SoundTouch.h" /> |
||||
<ClInclude Include="..\..\include\STTypes.h" /> |
||||
<ClInclude Include="AAFilter.h" /> |
||||
<ClInclude Include="cpu_detect.h" /> |
||||
<ClInclude Include="FIRFilter.h" /> |
||||
<ClInclude Include="InterpolateCubic.h" /> |
||||
<ClInclude Include="InterpolateLinear.h" /> |
||||
<ClInclude Include="InterpolateShannon.h" /> |
||||
<ClInclude Include="PeakFinder.h" /> |
||||
<ClInclude Include="RateTransposer.h" /> |
||||
<ClInclude Include="TDStretch.h" /> |
||||
</ItemGroup> |
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> |
||||
<ImportGroup Label="ExtensionTargets"> |
||||
</ImportGroup> |
||||
</Project> |
||||
File diff suppressed because it is too large
Load Diff
@ -1,279 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Sampled sound tempo changer/time stretch algorithm. Changes the sound tempo
|
||||
/// while maintaining the original pitch by using a time domain WSOLA-like method
|
||||
/// with several performance-increasing tweaks.
|
||||
///
|
||||
/// Note : MMX/SSE optimized functions reside in separate, platform-specific files
|
||||
/// 'mmx_optimized.cpp' and 'sse_optimized.cpp'
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef TDStretch_H |
||||
#define TDStretch_H |
||||
|
||||
#include <stddef.h> |
||||
#include "STTypes.h" |
||||
#include "RateTransposer.h" |
||||
#include "FIFOSamplePipe.h" |
||||
|
||||
namespace soundtouch |
||||
{ |
||||
|
||||
/// Default values for sound processing parameters:
|
||||
/// Notice that the default parameters are tuned for contemporary popular music
|
||||
/// processing. For speech processing applications these parameters suit better:
|
||||
/// #define DEFAULT_SEQUENCE_MS 40
|
||||
/// #define DEFAULT_SEEKWINDOW_MS 15
|
||||
/// #define DEFAULT_OVERLAP_MS 8
|
||||
///
|
||||
|
||||
/// Default length of a single processing sequence, in milliseconds. This determines to how
|
||||
/// long sequences the original sound is chopped in the time-stretch algorithm.
|
||||
///
|
||||
/// The larger this value is, the lesser sequences are used in processing. In principle
|
||||
/// a bigger value sounds better when slowing down tempo, but worse when increasing tempo
|
||||
/// and vice versa.
|
||||
///
|
||||
/// Increasing this value reduces computational burden & vice versa.
|
||||
//#define DEFAULT_SEQUENCE_MS 40
|
||||
#define DEFAULT_SEQUENCE_MS USE_AUTO_SEQUENCE_LEN |
||||
|
||||
/// Giving this value for the sequence length sets automatic parameter value
|
||||
/// according to tempo setting (recommended)
|
||||
#define USE_AUTO_SEQUENCE_LEN 0 |
||||
|
||||
/// Seeking window default length in milliseconds for algorithm that finds the best possible
|
||||
/// overlapping location. This determines from how wide window the algorithm may look for an
|
||||
/// optimal joining location when mixing the sound sequences back together.
|
||||
///
|
||||
/// The bigger this window setting is, the higher the possibility to find a better mixing
|
||||
/// position will become, but at the same time large values may cause a "drifting" artifact
|
||||
/// because consequent sequences will be taken at more uneven intervals.
|
||||
///
|
||||
/// If there's a disturbing artifact that sounds as if a constant frequency was drifting
|
||||
/// around, try reducing this setting.
|
||||
///
|
||||
/// Increasing this value increases computational burden & vice versa.
|
||||
//#define DEFAULT_SEEKWINDOW_MS 15
|
||||
#define DEFAULT_SEEKWINDOW_MS USE_AUTO_SEEKWINDOW_LEN |
||||
|
||||
/// Giving this value for the seek window length sets automatic parameter value
|
||||
/// according to tempo setting (recommended)
|
||||
#define USE_AUTO_SEEKWINDOW_LEN 0 |
||||
|
||||
/// Overlap length in milliseconds. When the chopped sound sequences are mixed back together,
|
||||
/// to form a continuous sound stream, this parameter defines over how long period the two
|
||||
/// consecutive sequences are let to overlap each other.
|
||||
///
|
||||
/// This shouldn't be that critical parameter. If you reduce the DEFAULT_SEQUENCE_MS setting
|
||||
/// by a large amount, you might wish to try a smaller value on this.
|
||||
///
|
||||
/// Increasing this value increases computational burden & vice versa.
|
||||
#define DEFAULT_OVERLAP_MS 8 |
||||
|
||||
|
||||
/// Class that does the time-stretch (tempo change) effect for the processed
|
||||
/// sound.
|
||||
class TDStretch : public FIFOProcessor |
||||
{ |
||||
protected: |
||||
int channels; |
||||
int sampleReq; |
||||
|
||||
int overlapLength; |
||||
int seekLength; |
||||
int seekWindowLength; |
||||
int overlapDividerBitsNorm; |
||||
int overlapDividerBitsPure; |
||||
int slopingDivider; |
||||
int sampleRate; |
||||
int sequenceMs; |
||||
int seekWindowMs; |
||||
int overlapMs; |
||||
|
||||
unsigned long maxnorm; |
||||
float maxnormf; |
||||
|
||||
double tempo; |
||||
double nominalSkip; |
||||
double skipFract; |
||||
|
||||
bool bQuickSeek; |
||||
bool bAutoSeqSetting; |
||||
bool bAutoSeekSetting; |
||||
bool isBeginning; |
||||
|
||||
SAMPLETYPE *pMidBuffer; |
||||
SAMPLETYPE *pMidBufferUnaligned; |
||||
|
||||
FIFOSampleBuffer outputBuffer; |
||||
FIFOSampleBuffer inputBuffer; |
||||
|
||||
void acceptNewOverlapLength(int newOverlapLength); |
||||
|
||||
virtual void clearCrossCorrState(); |
||||
void calculateOverlapLength(int overlapMs); |
||||
|
||||
virtual double calcCrossCorr(const SAMPLETYPE *mixingPos, const SAMPLETYPE *compare, double &norm); |
||||
virtual double calcCrossCorrAccumulate(const SAMPLETYPE *mixingPos, const SAMPLETYPE *compare, double &norm); |
||||
|
||||
virtual int seekBestOverlapPositionFull(const SAMPLETYPE *refPos); |
||||
virtual int seekBestOverlapPositionQuick(const SAMPLETYPE *refPos); |
||||
virtual int seekBestOverlapPosition(const SAMPLETYPE *refPos); |
||||
|
||||
virtual void overlapStereo(SAMPLETYPE *output, const SAMPLETYPE *input) const; |
||||
virtual void overlapMono(SAMPLETYPE *output, const SAMPLETYPE *input) const; |
||||
virtual void overlapMulti(SAMPLETYPE *output, const SAMPLETYPE *input) const; |
||||
|
||||
void clearMidBuffer(); |
||||
void overlap(SAMPLETYPE *output, const SAMPLETYPE *input, uint ovlPos) const; |
||||
|
||||
void calcSeqParameters(); |
||||
void adaptNormalizer(); |
||||
|
||||
/// Changes the tempo of the given sound samples.
|
||||
/// Returns amount of samples returned in the "output" buffer.
|
||||
/// The maximum amount of samples that can be returned at a time is set by
|
||||
/// the 'set_returnBuffer_size' function.
|
||||
void processSamples(); |
||||
|
||||
public: |
||||
TDStretch(); |
||||
virtual ~TDStretch() override; |
||||
|
||||
/// Operator 'new' is overloaded so that it automatically creates a suitable instance
|
||||
/// depending on if we've a MMX/SSE/etc-capable CPU available or not.
|
||||
static void *operator new(size_t s); |
||||
|
||||
/// Use this function instead of "new" operator to create a new instance of this class.
|
||||
/// This function automatically chooses a correct feature set depending on if the CPU
|
||||
/// supports MMX/SSE/etc extensions.
|
||||
static TDStretch *newInstance(); |
||||
|
||||
/// Returns the output buffer object
|
||||
FIFOSamplePipe *getOutput() { return &outputBuffer; }; |
||||
|
||||
/// Returns the input buffer object
|
||||
FIFOSamplePipe *getInput() { return &inputBuffer; }; |
||||
|
||||
/// Sets new target tempo. Normal tempo = 'SCALE', smaller values represent slower
|
||||
/// tempo, larger faster tempo.
|
||||
void setTempo(double newTempo); |
||||
|
||||
/// Returns nonzero if there aren't any samples available for outputting.
|
||||
virtual void clear() override; |
||||
|
||||
/// Clears the input buffer
|
||||
void clearInput(); |
||||
|
||||
/// Sets the number of channels, 1 = mono, 2 = stereo
|
||||
void setChannels(int numChannels); |
||||
|
||||
/// Enables/disables the quick position seeking algorithm. Zero to disable,
|
||||
/// nonzero to enable
|
||||
void enableQuickSeek(bool enable); |
||||
|
||||
/// Returns nonzero if the quick seeking algorithm is enabled.
|
||||
bool isQuickSeekEnabled() const; |
||||
|
||||
/// Sets routine control parameters. These control are certain time constants
|
||||
/// defining how the sound is stretched to the desired duration.
|
||||
//
|
||||
/// 'sampleRate' = sample rate of the sound
|
||||
/// 'sequenceMS' = one processing sequence length in milliseconds
|
||||
/// 'seekwindowMS' = seeking window length for scanning the best overlapping
|
||||
/// position
|
||||
/// 'overlapMS' = overlapping length
|
||||
void setParameters(int sampleRate, ///< Samplerate of sound being processed (Hz)
|
||||
int sequenceMS = -1, ///< Single processing sequence length (ms)
|
||||
int seekwindowMS = -1, ///< Offset seeking window length (ms)
|
||||
int overlapMS = -1 ///< Sequence overlapping length (ms)
|
||||
); |
||||
|
||||
/// Get routine control parameters, see setParameters() function.
|
||||
/// Any of the parameters to this function can be nullptr, in such case corresponding parameter
|
||||
/// value isn't returned.
|
||||
void getParameters(int *pSampleRate, int *pSequenceMs, int *pSeekWindowMs, int *pOverlapMs) const; |
||||
|
||||
/// Adds 'numsamples' pcs of samples from the 'samples' memory position into
|
||||
/// the input of the object.
|
||||
virtual void putSamples( |
||||
const SAMPLETYPE *samples, ///< Input sample data
|
||||
uint numSamples ///< Number of samples in 'samples' so that one sample
|
||||
///< contains both channels if stereo
|
||||
) override; |
||||
|
||||
/// return nominal input sample requirement for triggering a processing batch
|
||||
int getInputSampleReq() const |
||||
{ |
||||
return (int)(nominalSkip + 0.5); |
||||
} |
||||
|
||||
/// return nominal output sample amount when running a processing batch
|
||||
int getOutputBatchSize() const |
||||
{ |
||||
return seekWindowLength - overlapLength; |
||||
} |
||||
|
||||
/// return approximate initial input-output latency
|
||||
int getLatency() const |
||||
{ |
||||
return sampleReq; |
||||
} |
||||
}; |
||||
|
||||
|
||||
// Implementation-specific class declarations:
|
||||
|
||||
#ifdef SOUNDTOUCH_ALLOW_MMX |
||||
/// Class that implements MMX optimized routines for 16bit integer samples type.
|
||||
class TDStretchMMX : public TDStretch |
||||
{ |
||||
protected: |
||||
double calcCrossCorr(const short *mixingPos, const short *compare, double &norm) override; |
||||
double calcCrossCorrAccumulate(const short *mixingPos, const short *compare, double &norm) override; |
||||
virtual void overlapStereo(short *output, const short *input) const override; |
||||
virtual void clearCrossCorrState() override; |
||||
}; |
||||
#endif /// SOUNDTOUCH_ALLOW_MMX
|
||||
|
||||
|
||||
#ifdef SOUNDTOUCH_ALLOW_SSE |
||||
/// Class that implements SSE optimized routines for floating point samples type.
|
||||
class TDStretchSSE : public TDStretch |
||||
{ |
||||
protected: |
||||
double calcCrossCorr(const float *mixingPos, const float *compare, double &norm) override; |
||||
double calcCrossCorrAccumulate(const float *mixingPos, const float *compare, double &norm) override; |
||||
}; |
||||
|
||||
#endif /// SOUNDTOUCH_ALLOW_SSE
|
||||
|
||||
} |
||||
#endif /// TDStretch_H
|
||||
@ -1,55 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// A header file for detecting the Intel MMX instructions set extension.
|
||||
///
|
||||
/// Please see 'mmx_win.cpp', 'mmx_cpp.cpp' and 'mmx_non_x86.cpp' for the
|
||||
/// routine implementations for x86 Windows, x86 gnu version and non-x86
|
||||
/// platforms, respectively.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _CPU_DETECT_H_ |
||||
#define _CPU_DETECT_H_ |
||||
|
||||
#include "STTypes.h" |
||||
|
||||
#define SUPPORT_MMX 0x0001 |
||||
#define SUPPORT_3DNOW 0x0002 |
||||
#define SUPPORT_ALTIVEC 0x0004 |
||||
#define SUPPORT_SSE 0x0008 |
||||
#define SUPPORT_SSE2 0x0010 |
||||
|
||||
/// Checks which instruction set extensions are supported by the CPU.
|
||||
///
|
||||
/// \return A bitmask of supported extensions, see SUPPORT_... defines.
|
||||
uint detectCPUextensions(void); |
||||
|
||||
/// Disables given set of instruction extensions. See SUPPORT_... defines.
|
||||
void disableExtensions(uint wDisableMask); |
||||
|
||||
#endif // _CPU_DETECT_H_
|
||||
@ -1,130 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// Generic version of the x86 CPU extension detection routine.
|
||||
///
|
||||
/// This file is for GNU & other non-Windows compilers, see 'cpu_detect_x86_win.cpp'
|
||||
/// for the Microsoft compiler version.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "cpu_detect.h" |
||||
#include "STTypes.h" |
||||
|
||||
|
||||
#if defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) |
||||
|
||||
#if defined(__GNUC__) && defined(__i386__) |
||||
// gcc
|
||||
#include "cpuid.h" |
||||
#elif defined(_M_IX86) |
||||
// windows non-gcc
|
||||
#include <intrin.h> |
||||
#endif |
||||
|
||||
#define bit_MMX (1 << 23) |
||||
#define bit_SSE (1 << 25) |
||||
#define bit_SSE2 (1 << 26) |
||||
#endif |
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// processor instructions extension detection routines
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Flag variable indicating whick ISA extensions are disabled (for debugging)
|
||||
static uint _dwDisabledISA = 0x00; // 0xffffffff; //<- use this to disable all extensions
|
||||
|
||||
// Disables given set of instruction extensions. See SUPPORT_... defines.
|
||||
void disableExtensions(uint dwDisableMask) |
||||
{ |
||||
_dwDisabledISA = dwDisableMask; |
||||
} |
||||
|
||||
|
||||
/// Checks which instruction set extensions are supported by the CPU.
|
||||
uint detectCPUextensions(void) |
||||
{ |
||||
/// If building for a 64bit system (no Itanium) and the user wants optimizations.
|
||||
/// Return the OR of SUPPORT_{MMX,SSE,SSE2}. 11001 or 0x19.
|
||||
/// Keep the _dwDisabledISA test (2 more operations, could be eliminated).
|
||||
#if ((defined(__GNUC__) && defined(__x86_64__)) \ |
||||
|| defined(_M_X64)) \
|
||||
&& defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) |
||||
return 0x19 & ~_dwDisabledISA; |
||||
|
||||
/// If building for a 32bit system and the user wants optimizations.
|
||||
/// Keep the _dwDisabledISA test (2 more operations, could be eliminated).
|
||||
#elif ((defined(__GNUC__) && defined(__i386__)) \ |
||||
|| defined(_M_IX86)) \
|
||||
&& defined(SOUNDTOUCH_ALLOW_X86_OPTIMIZATIONS) |
||||
|
||||
if (_dwDisabledISA == 0xffffffff) return 0; |
||||
|
||||
uint res = 0; |
||||
|
||||
#if defined(__GNUC__) |
||||
// GCC version of cpuid. Requires GCC 4.3.0 or later for __cpuid intrinsic support.
|
||||
uint eax, ebx, ecx, edx; // unsigned int is the standard type. uint is defined by the compiler and not guaranteed to be portable.
|
||||
|
||||
// Check if no cpuid support.
|
||||
if (!__get_cpuid (1, &eax, &ebx, &ecx, &edx)) return 0; // always disable extensions.
|
||||
|
||||
if (edx & bit_MMX) res = res | SUPPORT_MMX; |
||||
if (edx & bit_SSE) res = res | SUPPORT_SSE; |
||||
if (edx & bit_SSE2) res = res | SUPPORT_SSE2; |
||||
|
||||
#else |
||||
// Window / VS version of cpuid. Notice that Visual Studio 2005 or later required
|
||||
// for __cpuid intrinsic support.
|
||||
int reg[4] = {-1}; |
||||
|
||||
// Check if no cpuid support.
|
||||
__cpuid(reg,0); |
||||
if ((unsigned int)reg[0] == 0) return 0; // always disable extensions.
|
||||
|
||||
__cpuid(reg,1); |
||||
if ((unsigned int)reg[3] & bit_MMX) res = res | SUPPORT_MMX; |
||||
if ((unsigned int)reg[3] & bit_SSE) res = res | SUPPORT_SSE; |
||||
if ((unsigned int)reg[3] & bit_SSE2) res = res | SUPPORT_SSE2; |
||||
|
||||
#endif |
||||
|
||||
return res & ~_dwDisabledISA; |
||||
|
||||
#else |
||||
|
||||
/// One of these is true:
|
||||
/// 1) We don't want optimizations.
|
||||
/// 2) Using an unsupported compiler.
|
||||
/// 3) Running on a non-x86 platform.
|
||||
return 0; |
||||
|
||||
#endif |
||||
} |
||||
@ -1,392 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// MMX optimized routines. All MMX optimized functions have been gathered into
|
||||
/// this single source code file, regardless to their class or original source
|
||||
/// code file, in order to ease porting the library to other compiler and
|
||||
/// processor platforms.
|
||||
///
|
||||
/// The MMX-optimizations are programmed using MMX compiler intrinsics that
|
||||
/// are supported both by Microsoft Visual C++ and GCC compilers, so this file
|
||||
/// should compile with both toolsets.
|
||||
///
|
||||
/// NOTICE: If using Visual Studio 6.0, you'll need to install the "Visual C++
|
||||
/// 6.0 processor pack" update to support compiler intrinsic syntax. The update
|
||||
/// is available for download at Microsoft Developers Network, see here:
|
||||
/// http://msdn.microsoft.com/en-us/vstudio/aa718349.aspx
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "STTypes.h" |
||||
|
||||
#ifdef SOUNDTOUCH_ALLOW_MMX |
||||
// MMX routines available only with integer sample type
|
||||
|
||||
using namespace soundtouch; |
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// implementation of MMX optimized functions of class 'TDStretchMMX'
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "TDStretch.h" |
||||
#include <mmintrin.h> |
||||
#include <limits.h> |
||||
#include <math.h> |
||||
|
||||
|
||||
// Calculates cross correlation of two buffers
|
||||
double TDStretchMMX::calcCrossCorr(const short *pV1, const short *pV2, double &dnorm) |
||||
{ |
||||
const __m64 *pVec1, *pVec2; |
||||
__m64 shifter; |
||||
__m64 accu, normaccu; |
||||
long corr, norm; |
||||
int i; |
||||
|
||||
pVec1 = (__m64*)pV1; |
||||
pVec2 = (__m64*)pV2; |
||||
|
||||
shifter = _m_from_int(overlapDividerBitsNorm); |
||||
normaccu = accu = _mm_setzero_si64(); |
||||
|
||||
// Process 4 parallel sets of 2 * stereo samples or 4 * mono samples
|
||||
// during each round for improved CPU-level parallellization.
|
||||
for (i = 0; i < channels * overlapLength / 16; i ++) |
||||
{ |
||||
__m64 temp, temp2; |
||||
|
||||
// dictionary of instructions:
|
||||
// _m_pmaddwd : 4*16bit multiply-add, resulting two 32bits = [a0*b0+a1*b1 ; a2*b2+a3*b3]
|
||||
// _mm_add_pi32 : 2*32bit add
|
||||
// _m_psrad : 32bit right-shift
|
||||
|
||||
temp = _mm_add_pi32(_mm_sra_pi32(_mm_madd_pi16(pVec1[0], pVec2[0]), shifter), |
||||
_mm_sra_pi32(_mm_madd_pi16(pVec1[1], pVec2[1]), shifter)); |
||||
temp2 = _mm_add_pi32(_mm_sra_pi32(_mm_madd_pi16(pVec1[0], pVec1[0]), shifter), |
||||
_mm_sra_pi32(_mm_madd_pi16(pVec1[1], pVec1[1]), shifter)); |
||||
accu = _mm_add_pi32(accu, temp); |
||||
normaccu = _mm_add_pi32(normaccu, temp2); |
||||
|
||||
temp = _mm_add_pi32(_mm_sra_pi32(_mm_madd_pi16(pVec1[2], pVec2[2]), shifter), |
||||
_mm_sra_pi32(_mm_madd_pi16(pVec1[3], pVec2[3]), shifter)); |
||||
temp2 = _mm_add_pi32(_mm_sra_pi32(_mm_madd_pi16(pVec1[2], pVec1[2]), shifter), |
||||
_mm_sra_pi32(_mm_madd_pi16(pVec1[3], pVec1[3]), shifter)); |
||||
accu = _mm_add_pi32(accu, temp); |
||||
normaccu = _mm_add_pi32(normaccu, temp2); |
||||
|
||||
pVec1 += 4; |
||||
pVec2 += 4; |
||||
} |
||||
|
||||
// copy hi-dword of mm0 to lo-dword of mm1, then sum mmo+mm1
|
||||
// and finally store the result into the variable "corr"
|
||||
|
||||
accu = _mm_add_pi32(accu, _mm_srli_si64(accu, 32)); |
||||
corr = _m_to_int(accu); |
||||
|
||||
normaccu = _mm_add_pi32(normaccu, _mm_srli_si64(normaccu, 32)); |
||||
norm = _m_to_int(normaccu); |
||||
|
||||
// Clear MMS state
|
||||
_m_empty(); |
||||
|
||||
if (norm > (long)maxnorm) |
||||
{ |
||||
// modify 'maxnorm' inside critical section to avoid multi-access conflict if in OpenMP mode
|
||||
maxnorm = norm; |
||||
} |
||||
|
||||
// Normalize result by dividing by sqrt(norm) - this step is easiest
|
||||
// done using floating point operation
|
||||
dnorm = (double)norm; |
||||
|
||||
return (double)corr / sqrt(dnorm < 1e-9 ? 1.0 : dnorm); |
||||
// Note: Warning about the missing EMMS instruction is harmless
|
||||
// as it'll be called elsewhere.
|
||||
} |
||||
|
||||
|
||||
/// Update cross-correlation by accumulating "norm" coefficient by previously calculated value
|
||||
double TDStretchMMX::calcCrossCorrAccumulate(const short *pV1, const short *pV2, double &dnorm) |
||||
{ |
||||
const __m64 *pVec1, *pVec2; |
||||
__m64 shifter; |
||||
__m64 accu; |
||||
long corr, lnorm; |
||||
int i; |
||||
|
||||
// cancel first normalizer tap from previous round
|
||||
lnorm = 0; |
||||
for (i = 1; i <= channels; i ++) |
||||
{ |
||||
lnorm -= (pV1[-i] * pV1[-i]) >> overlapDividerBitsNorm; |
||||
} |
||||
|
||||
pVec1 = (__m64*)pV1; |
||||
pVec2 = (__m64*)pV2; |
||||
|
||||
shifter = _m_from_int(overlapDividerBitsNorm); |
||||
accu = _mm_setzero_si64(); |
||||
|
||||
// Process 4 parallel sets of 2 * stereo samples or 4 * mono samples
|
||||
// during each round for improved CPU-level parallellization.
|
||||
for (i = 0; i < channels * overlapLength / 16; i ++) |
||||
{ |
||||
__m64 temp; |
||||
|
||||
// dictionary of instructions:
|
||||
// _m_pmaddwd : 4*16bit multiply-add, resulting two 32bits = [a0*b0+a1*b1 ; a2*b2+a3*b3]
|
||||
// _mm_add_pi32 : 2*32bit add
|
||||
// _m_psrad : 32bit right-shift
|
||||
|
||||
temp = _mm_add_pi32(_mm_sra_pi32(_mm_madd_pi16(pVec1[0], pVec2[0]), shifter), |
||||
_mm_sra_pi32(_mm_madd_pi16(pVec1[1], pVec2[1]), shifter)); |
||||
accu = _mm_add_pi32(accu, temp); |
||||
|
||||
temp = _mm_add_pi32(_mm_sra_pi32(_mm_madd_pi16(pVec1[2], pVec2[2]), shifter), |
||||
_mm_sra_pi32(_mm_madd_pi16(pVec1[3], pVec2[3]), shifter)); |
||||
accu = _mm_add_pi32(accu, temp); |
||||
|
||||
pVec1 += 4; |
||||
pVec2 += 4; |
||||
} |
||||
|
||||
// copy hi-dword of mm0 to lo-dword of mm1, then sum mmo+mm1
|
||||
// and finally store the result into the variable "corr"
|
||||
|
||||
accu = _mm_add_pi32(accu, _mm_srli_si64(accu, 32)); |
||||
corr = _m_to_int(accu); |
||||
|
||||
// Clear MMS state
|
||||
_m_empty(); |
||||
|
||||
// update normalizer with last samples of this round
|
||||
pV1 = (short *)pVec1; |
||||
for (int j = 1; j <= channels; j ++) |
||||
{ |
||||
lnorm += (pV1[-j] * pV1[-j]) >> overlapDividerBitsNorm; |
||||
} |
||||
dnorm += (double)lnorm; |
||||
|
||||
if (lnorm > (long)maxnorm) |
||||
{ |
||||
maxnorm = lnorm; |
||||
} |
||||
|
||||
// Normalize result by dividing by sqrt(norm) - this step is easiest
|
||||
// done using floating point operation
|
||||
return (double)corr / sqrt((dnorm < 1e-9) ? 1.0 : dnorm); |
||||
} |
||||
|
||||
|
||||
void TDStretchMMX::clearCrossCorrState() |
||||
{ |
||||
// Clear MMS state
|
||||
_m_empty(); |
||||
//_asm EMMS;
|
||||
} |
||||
|
||||
|
||||
// MMX-optimized version of the function overlapStereo
|
||||
void TDStretchMMX::overlapStereo(short *output, const short *input) const |
||||
{ |
||||
const __m64 *pVinput, *pVMidBuf; |
||||
__m64 *pVdest; |
||||
__m64 mix1, mix2, adder, shifter; |
||||
int i; |
||||
|
||||
pVinput = (const __m64*)input; |
||||
pVMidBuf = (const __m64*)pMidBuffer; |
||||
pVdest = (__m64*)output; |
||||
|
||||
// mix1 = mixer values for 1st stereo sample
|
||||
// mix1 = mixer values for 2nd stereo sample
|
||||
// adder = adder for updating mixer values after each round
|
||||
|
||||
mix1 = _mm_set_pi16(0, overlapLength, 0, overlapLength); |
||||
adder = _mm_set_pi16(1, -1, 1, -1); |
||||
mix2 = _mm_add_pi16(mix1, adder); |
||||
adder = _mm_add_pi16(adder, adder); |
||||
|
||||
// Overlaplength-division by shifter. "+1" is to account for "-1" deduced in
|
||||
// overlapDividerBits calculation earlier.
|
||||
shifter = _m_from_int(overlapDividerBitsPure + 1); |
||||
|
||||
for (i = 0; i < overlapLength / 4; i ++) |
||||
{ |
||||
__m64 temp1, temp2; |
||||
|
||||
// load & shuffle data so that input & mixbuffer data samples are paired
|
||||
temp1 = _mm_unpacklo_pi16(pVMidBuf[0], pVinput[0]); // = i0l m0l i0r m0r
|
||||
temp2 = _mm_unpackhi_pi16(pVMidBuf[0], pVinput[0]); // = i1l m1l i1r m1r
|
||||
|
||||
// temp = (temp .* mix) >> shifter
|
||||
temp1 = _mm_sra_pi32(_mm_madd_pi16(temp1, mix1), shifter); |
||||
temp2 = _mm_sra_pi32(_mm_madd_pi16(temp2, mix2), shifter); |
||||
pVdest[0] = _mm_packs_pi32(temp1, temp2); // pack 2*2*32bit => 4*16bit
|
||||
|
||||
// update mix += adder
|
||||
mix1 = _mm_add_pi16(mix1, adder); |
||||
mix2 = _mm_add_pi16(mix2, adder); |
||||
|
||||
// --- second round begins here ---
|
||||
|
||||
// load & shuffle data so that input & mixbuffer data samples are paired
|
||||
temp1 = _mm_unpacklo_pi16(pVMidBuf[1], pVinput[1]); // = i2l m2l i2r m2r
|
||||
temp2 = _mm_unpackhi_pi16(pVMidBuf[1], pVinput[1]); // = i3l m3l i3r m3r
|
||||
|
||||
// temp = (temp .* mix) >> shifter
|
||||
temp1 = _mm_sra_pi32(_mm_madd_pi16(temp1, mix1), shifter); |
||||
temp2 = _mm_sra_pi32(_mm_madd_pi16(temp2, mix2), shifter); |
||||
pVdest[1] = _mm_packs_pi32(temp1, temp2); // pack 2*2*32bit => 4*16bit
|
||||
|
||||
// update mix += adder
|
||||
mix1 = _mm_add_pi16(mix1, adder); |
||||
mix2 = _mm_add_pi16(mix2, adder); |
||||
|
||||
pVinput += 2; |
||||
pVMidBuf += 2; |
||||
pVdest += 2; |
||||
} |
||||
|
||||
_m_empty(); // clear MMS state
|
||||
} |
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// implementation of MMX optimized functions of class 'FIRFilter'
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "FIRFilter.h" |
||||
|
||||
|
||||
FIRFilterMMX::FIRFilterMMX() : FIRFilter() |
||||
{ |
||||
filterCoeffsAlign = nullptr; |
||||
filterCoeffsUnalign = nullptr; |
||||
} |
||||
|
||||
|
||||
FIRFilterMMX::~FIRFilterMMX() |
||||
{ |
||||
delete[] filterCoeffsUnalign; |
||||
} |
||||
|
||||
|
||||
// (overloaded) Calculates filter coefficients for MMX routine
|
||||
void FIRFilterMMX::setCoefficients(const short *coeffs, uint newLength, uint uResultDivFactor) |
||||
{ |
||||
uint i; |
||||
FIRFilter::setCoefficients(coeffs, newLength, uResultDivFactor); |
||||
|
||||
// Ensure that filter coeffs array is aligned to 16-byte boundary
|
||||
delete[] filterCoeffsUnalign; |
||||
filterCoeffsUnalign = new short[2 * newLength + 8]; |
||||
filterCoeffsAlign = (short *)SOUNDTOUCH_ALIGN_POINTER_16(filterCoeffsUnalign); |
||||
|
||||
// rearrange the filter coefficients for mmx routines
|
||||
for (i = 0;i < length; i += 4) |
||||
{ |
||||
filterCoeffsAlign[2 * i + 0] = coeffs[i + 0]; |
||||
filterCoeffsAlign[2 * i + 1] = coeffs[i + 2]; |
||||
filterCoeffsAlign[2 * i + 2] = coeffs[i + 0]; |
||||
filterCoeffsAlign[2 * i + 3] = coeffs[i + 2]; |
||||
|
||||
filterCoeffsAlign[2 * i + 4] = coeffs[i + 1]; |
||||
filterCoeffsAlign[2 * i + 5] = coeffs[i + 3]; |
||||
filterCoeffsAlign[2 * i + 6] = coeffs[i + 1]; |
||||
filterCoeffsAlign[2 * i + 7] = coeffs[i + 3]; |
||||
} |
||||
} |
||||
|
||||
|
||||
// mmx-optimized version of the filter routine for stereo sound
|
||||
uint FIRFilterMMX::evaluateFilterStereo(short *dest, const short *src, uint numSamples) const |
||||
{ |
||||
// Create stack copies of the needed member variables for asm routines :
|
||||
uint i, j; |
||||
__m64 *pVdest = (__m64*)dest; |
||||
|
||||
if (length < 2) return 0; |
||||
|
||||
for (i = 0; i < (numSamples - length) / 2; i ++) |
||||
{ |
||||
__m64 accu1; |
||||
__m64 accu2; |
||||
const __m64 *pVsrc = (const __m64*)src; |
||||
const __m64 *pVfilter = (const __m64*)filterCoeffsAlign; |
||||
|
||||
accu1 = accu2 = _mm_setzero_si64(); |
||||
for (j = 0; j < lengthDiv8 * 2; j ++) |
||||
{ |
||||
__m64 temp1, temp2; |
||||
|
||||
temp1 = _mm_unpacklo_pi16(pVsrc[0], pVsrc[1]); // = l2 l0 r2 r0
|
||||
temp2 = _mm_unpackhi_pi16(pVsrc[0], pVsrc[1]); // = l3 l1 r3 r1
|
||||
|
||||
accu1 = _mm_add_pi32(accu1, _mm_madd_pi16(temp1, pVfilter[0])); // += l2*f2+l0*f0 r2*f2+r0*f0
|
||||
accu1 = _mm_add_pi32(accu1, _mm_madd_pi16(temp2, pVfilter[1])); // += l3*f3+l1*f1 r3*f3+r1*f1
|
||||
|
||||
temp1 = _mm_unpacklo_pi16(pVsrc[1], pVsrc[2]); // = l4 l2 r4 r2
|
||||
|
||||
accu2 = _mm_add_pi32(accu2, _mm_madd_pi16(temp2, pVfilter[0])); // += l3*f2+l1*f0 r3*f2+r1*f0
|
||||
accu2 = _mm_add_pi32(accu2, _mm_madd_pi16(temp1, pVfilter[1])); // += l4*f3+l2*f1 r4*f3+r2*f1
|
||||
|
||||
// accu1 += l2*f2+l0*f0 r2*f2+r0*f0
|
||||
// += l3*f3+l1*f1 r3*f3+r1*f1
|
||||
|
||||
// accu2 += l3*f2+l1*f0 r3*f2+r1*f0
|
||||
// l4*f3+l2*f1 r4*f3+r2*f1
|
||||
|
||||
pVfilter += 2; |
||||
pVsrc += 2; |
||||
} |
||||
// accu >>= resultDivFactor
|
||||
accu1 = _mm_srai_pi32(accu1, resultDivFactor); |
||||
accu2 = _mm_srai_pi32(accu2, resultDivFactor); |
||||
|
||||
// pack 2*2*32bits => 4*16 bits
|
||||
pVdest[0] = _mm_packs_pi32(accu1, accu2); |
||||
src += 4; |
||||
pVdest ++; |
||||
} |
||||
|
||||
_m_empty(); // clear emms state
|
||||
|
||||
return (numSamples & 0xfffffffe) - length; |
||||
} |
||||
|
||||
#else |
||||
|
||||
// workaround to not complain about empty module
|
||||
bool _dontcomplain_mmx_empty; |
||||
|
||||
#endif // SOUNDTOUCH_ALLOW_MMX
|
||||
@ -1,364 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// SSE optimized routines for Pentium-III, Athlon-XP and later CPUs. All SSE
|
||||
/// optimized functions have been gathered into this single source
|
||||
/// code file, regardless to their class or original source code file, in order
|
||||
/// to ease porting the library to other compiler and processor platforms.
|
||||
///
|
||||
/// The SSE-optimizations are programmed using SSE compiler intrinsics that
|
||||
/// are supported both by Microsoft Visual C++ and GCC compilers, so this file
|
||||
/// should compile with both toolsets.
|
||||
///
|
||||
/// NOTICE: If using Visual Studio 6.0, you'll need to install the "Visual C++
|
||||
/// 6.0 processor pack" update to support SSE instruction set. The update is
|
||||
/// available for download at Microsoft Developers Network, see here:
|
||||
/// http://msdn.microsoft.com/en-us/vstudio/aa718349.aspx
|
||||
///
|
||||
/// If the above URL is expired or removed, go to "http://msdn.microsoft.com" and
|
||||
/// perform a search with keywords "processor pack".
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "cpu_detect.h" |
||||
#include "STTypes.h" |
||||
|
||||
using namespace soundtouch; |
||||
|
||||
#ifdef SOUNDTOUCH_ALLOW_SSE |
||||
|
||||
// SSE routines available only with float sample type
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// implementation of SSE optimized functions of class 'TDStretchSSE'
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "TDStretch.h" |
||||
#include <xmmintrin.h> |
||||
#include <math.h> |
||||
|
||||
// Calculates cross correlation of two buffers
|
||||
double TDStretchSSE::calcCrossCorr(const float *pV1, const float *pV2, double &anorm) |
||||
{ |
||||
int i; |
||||
const float *pVec1; |
||||
const __m128 *pVec2; |
||||
__m128 vSum, vNorm; |
||||
|
||||
// Note. It means a major slow-down if the routine needs to tolerate
|
||||
// unaligned __m128 memory accesses. It's way faster if we can skip
|
||||
// unaligned slots and use _mm_load_ps instruction instead of _mm_loadu_ps.
|
||||
// This can mean up to ~ 10-fold difference (incl. part of which is
|
||||
// due to skipping every second round for stereo sound though).
|
||||
//
|
||||
// Compile-time define SOUNDTOUCH_ALLOW_NONEXACT_SIMD_OPTIMIZATION is provided
|
||||
// for choosing if this little cheating is allowed.
|
||||
|
||||
#ifdef ST_SIMD_AVOID_UNALIGNED |
||||
// Little cheating allowed, return valid correlation only for
|
||||
// aligned locations, meaning every second round for stereo sound.
|
||||
|
||||
#define _MM_LOAD _mm_load_ps |
||||
|
||||
if (((ulongptr)pV1) & 15) return -1e50; // skip unaligned locations
|
||||
|
||||
#else |
||||
// No cheating allowed, use unaligned load & take the resulting
|
||||
// performance hit.
|
||||
#define _MM_LOAD _mm_loadu_ps |
||||
#endif |
||||
|
||||
// ensure overlapLength is divisible by 8
|
||||
assert((overlapLength % 8) == 0); |
||||
|
||||
// Calculates the cross-correlation value between 'pV1' and 'pV2' vectors
|
||||
// Note: pV2 _must_ be aligned to 16-bit boundary, pV1 need not.
|
||||
pVec1 = (const float*)pV1; |
||||
pVec2 = (const __m128*)pV2; |
||||
vSum = vNorm = _mm_setzero_ps(); |
||||
|
||||
// Unroll the loop by factor of 4 * 4 operations. Use same routine for
|
||||
// stereo & mono, for mono it just means twice the amount of unrolling.
|
||||
for (i = 0; i < channels * overlapLength / 16; i ++) |
||||
{ |
||||
__m128 vTemp; |
||||
// vSum += pV1[0..3] * pV2[0..3]
|
||||
vTemp = _MM_LOAD(pVec1); |
||||
vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp ,pVec2[0])); |
||||
vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); |
||||
|
||||
// vSum += pV1[4..7] * pV2[4..7]
|
||||
vTemp = _MM_LOAD(pVec1 + 4); |
||||
vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[1])); |
||||
vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); |
||||
|
||||
// vSum += pV1[8..11] * pV2[8..11]
|
||||
vTemp = _MM_LOAD(pVec1 + 8); |
||||
vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[2])); |
||||
vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); |
||||
|
||||
// vSum += pV1[12..15] * pV2[12..15]
|
||||
vTemp = _MM_LOAD(pVec1 + 12); |
||||
vSum = _mm_add_ps(vSum, _mm_mul_ps(vTemp, pVec2[3])); |
||||
vNorm = _mm_add_ps(vNorm, _mm_mul_ps(vTemp ,vTemp)); |
||||
|
||||
pVec1 += 16; |
||||
pVec2 += 4; |
||||
} |
||||
|
||||
// return value = vSum[0] + vSum[1] + vSum[2] + vSum[3]
|
||||
float *pvNorm = (float*)&vNorm; |
||||
float norm = (pvNorm[0] + pvNorm[1] + pvNorm[2] + pvNorm[3]); |
||||
anorm = norm; |
||||
|
||||
float *pvSum = (float*)&vSum; |
||||
return (double)(pvSum[0] + pvSum[1] + pvSum[2] + pvSum[3]) / sqrt(norm < 1e-9 ? 1.0 : norm); |
||||
|
||||
/* This is approximately corresponding routine in C-language yet without normalization:
|
||||
double corr, norm; |
||||
uint i; |
||||
|
||||
// Calculates the cross-correlation value between 'pV1' and 'pV2' vectors
|
||||
corr = norm = 0.0; |
||||
for (i = 0; i < channels * overlapLength / 16; i ++) |
||||
{ |
||||
corr += pV1[0] * pV2[0] + |
||||
pV1[1] * pV2[1] + |
||||
pV1[2] * pV2[2] + |
||||
pV1[3] * pV2[3] + |
||||
pV1[4] * pV2[4] + |
||||
pV1[5] * pV2[5] + |
||||
pV1[6] * pV2[6] + |
||||
pV1[7] * pV2[7] + |
||||
pV1[8] * pV2[8] + |
||||
pV1[9] * pV2[9] + |
||||
pV1[10] * pV2[10] + |
||||
pV1[11] * pV2[11] + |
||||
pV1[12] * pV2[12] + |
||||
pV1[13] * pV2[13] + |
||||
pV1[14] * pV2[14] + |
||||
pV1[15] * pV2[15]; |
||||
|
||||
for (j = 0; j < 15; j ++) norm += pV1[j] * pV1[j]; |
||||
|
||||
pV1 += 16; |
||||
pV2 += 16; |
||||
} |
||||
return corr / sqrt(norm); |
||||
*/ |
||||
} |
||||
|
||||
|
||||
|
||||
double TDStretchSSE::calcCrossCorrAccumulate(const float *pV1, const float *pV2, double &norm) |
||||
{ |
||||
// call usual calcCrossCorr function because SSE does not show big benefit of
|
||||
// accumulating "norm" value, and also the "norm" rolling algorithm would get
|
||||
// complicated due to SSE-specific alignment-vs-nonexact correlation rules.
|
||||
return calcCrossCorr(pV1, pV2, norm); |
||||
} |
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// implementation of SSE optimized functions of class 'FIRFilter'
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "FIRFilter.h" |
||||
|
||||
FIRFilterSSE::FIRFilterSSE() : FIRFilter() |
||||
{ |
||||
filterCoeffsAlign = nullptr; |
||||
filterCoeffsUnalign = nullptr; |
||||
} |
||||
|
||||
|
||||
FIRFilterSSE::~FIRFilterSSE() |
||||
{ |
||||
delete[] filterCoeffsUnalign; |
||||
filterCoeffsAlign = nullptr; |
||||
filterCoeffsUnalign = nullptr; |
||||
} |
||||
|
||||
|
||||
// (overloaded) Calculates filter coefficients for SSE routine
|
||||
void FIRFilterSSE::setCoefficients(const float *coeffs, uint newLength, uint uResultDivFactor) |
||||
{ |
||||
uint i; |
||||
float fDivider; |
||||
|
||||
FIRFilter::setCoefficients(coeffs, newLength, uResultDivFactor); |
||||
|
||||
// Scale the filter coefficients so that it won't be necessary to scale the filtering result
|
||||
// also rearrange coefficients suitably for SSE
|
||||
// Ensure that filter coeffs array is aligned to 16-byte boundary
|
||||
delete[] filterCoeffsUnalign; |
||||
filterCoeffsUnalign = new float[2 * newLength + 4]; |
||||
filterCoeffsAlign = (float *)SOUNDTOUCH_ALIGN_POINTER_16(filterCoeffsUnalign); |
||||
|
||||
fDivider = (float)resultDivider; |
||||
|
||||
// rearrange the filter coefficients for mmx routines
|
||||
for (i = 0; i < newLength; i ++) |
||||
{ |
||||
filterCoeffsAlign[2 * i + 0] = |
||||
filterCoeffsAlign[2 * i + 1] = coeffs[i + 0] / fDivider; |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
// SSE-optimized version of the filter routine for stereo sound
|
||||
uint FIRFilterSSE::evaluateFilterStereo(float *dest, const float *source, uint numSamples) const |
||||
{ |
||||
int count = (int)((numSamples - length) & (uint)-2); |
||||
int j; |
||||
|
||||
assert(count % 2 == 0); |
||||
|
||||
if (count < 2) return 0; |
||||
|
||||
assert(source != nullptr); |
||||
assert(dest != nullptr); |
||||
assert((length % 8) == 0); |
||||
assert(filterCoeffsAlign != nullptr); |
||||
assert(((ulongptr)filterCoeffsAlign) % 16 == 0); |
||||
|
||||
// filter is evaluated for two stereo samples with each iteration, thus use of 'j += 2'
|
||||
for (j = 0; j < count; j += 2) |
||||
{ |
||||
const float *pSrc; |
||||
float *pDest; |
||||
const __m128 *pFil; |
||||
__m128 sum1, sum2; |
||||
uint i; |
||||
|
||||
pSrc = (const float*)source + j * 2; // source audio data
|
||||
pDest = dest + j * 2; // destination audio data
|
||||
pFil = (const __m128*)filterCoeffsAlign; // filter coefficients. NOTE: Assumes coefficients
|
||||
// are aligned to 16-byte boundary
|
||||
sum1 = sum2 = _mm_setzero_ps(); |
||||
|
||||
for (i = 0; i < length / 8; i ++) |
||||
{ |
||||
// Unroll loop for efficiency & calculate filter for 2*2 stereo samples
|
||||
// at each pass
|
||||
|
||||
// sum1 is accu for 2*2 filtered stereo sound data at the primary sound data offset
|
||||
// sum2 is accu for 2*2 filtered stereo sound data for the next sound sample offset.
|
||||
|
||||
sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc) , pFil[0])); |
||||
sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 2), pFil[0])); |
||||
|
||||
sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 4), pFil[1])); |
||||
sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 6), pFil[1])); |
||||
|
||||
sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 8) , pFil[2])); |
||||
sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 10), pFil[2])); |
||||
|
||||
sum1 = _mm_add_ps(sum1, _mm_mul_ps(_mm_loadu_ps(pSrc + 12), pFil[3])); |
||||
sum2 = _mm_add_ps(sum2, _mm_mul_ps(_mm_loadu_ps(pSrc + 14), pFil[3])); |
||||
|
||||
pSrc += 16; |
||||
pFil += 4; |
||||
} |
||||
|
||||
// Now sum1 and sum2 both have a filtered 2-channel sample each, but we still need
|
||||
// to sum the two hi- and lo-floats of these registers together.
|
||||
|
||||
// post-shuffle & add the filtered values and store to dest.
|
||||
_mm_storeu_ps(pDest, _mm_add_ps( |
||||
_mm_shuffle_ps(sum1, sum2, _MM_SHUFFLE(1,0,3,2)), // s2_1 s2_0 s1_3 s1_2
|
||||
_mm_shuffle_ps(sum1, sum2, _MM_SHUFFLE(3,2,1,0)) // s2_3 s2_2 s1_1 s1_0
|
||||
)); |
||||
} |
||||
|
||||
// Ideas for further improvement:
|
||||
// 1. If it could be guaranteed that 'source' were always aligned to 16-byte
|
||||
// boundary, a faster aligned '_mm_load_ps' instruction could be used.
|
||||
// 2. If it could be guaranteed that 'dest' were always aligned to 16-byte
|
||||
// boundary, a faster '_mm_store_ps' instruction could be used.
|
||||
|
||||
return (uint)count; |
||||
|
||||
/* original routine in C-language. please notice the C-version has differently
|
||||
organized coefficients though. |
||||
double suml1, suml2; |
||||
double sumr1, sumr2; |
||||
uint i, j; |
||||
|
||||
for (j = 0; j < count; j += 2) |
||||
{ |
||||
const float *ptr; |
||||
const float *pFil; |
||||
|
||||
suml1 = sumr1 = 0.0; |
||||
suml2 = sumr2 = 0.0; |
||||
ptr = src; |
||||
pFil = filterCoeffs; |
||||
for (i = 0; i < lengthLocal; i ++) |
||||
{ |
||||
// unroll loop for efficiency.
|
||||
|
||||
suml1 += ptr[0] * pFil[0] + |
||||
ptr[2] * pFil[2] + |
||||
ptr[4] * pFil[4] + |
||||
ptr[6] * pFil[6]; |
||||
|
||||
sumr1 += ptr[1] * pFil[1] + |
||||
ptr[3] * pFil[3] + |
||||
ptr[5] * pFil[5] + |
||||
ptr[7] * pFil[7]; |
||||
|
||||
suml2 += ptr[8] * pFil[0] + |
||||
ptr[10] * pFil[2] + |
||||
ptr[12] * pFil[4] + |
||||
ptr[14] * pFil[6]; |
||||
|
||||
sumr2 += ptr[9] * pFil[1] + |
||||
ptr[11] * pFil[3] + |
||||
ptr[13] * pFil[5] + |
||||
ptr[15] * pFil[7]; |
||||
|
||||
ptr += 16; |
||||
pFil += 8; |
||||
} |
||||
dest[0] = (float)suml1; |
||||
dest[1] = (float)sumr1; |
||||
dest[2] = (float)suml2; |
||||
dest[3] = (float)sumr2; |
||||
|
||||
src += 4; |
||||
dest += 4; |
||||
} |
||||
*/ |
||||
} |
||||
|
||||
#endif // SOUNDTOUCH_ALLOW_SSE
|
||||
@ -1,250 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/// SoundTouch DLL wrapper - wraps SoundTouch routines into a Dynamic Load
|
||||
/// Library interface.
|
||||
///
|
||||
/// Author : Copyright (c) Olli Parviainen
|
||||
/// Author e-mail : oparviai 'at' iki.fi
|
||||
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||||
///
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// License :
|
||||
//
|
||||
// SoundTouch audio processing library
|
||||
// Copyright (c) Olli Parviainen
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
#if defined(_WIN32) || defined(WIN32) |
||||
#include <windows.h> |
||||
|
||||
// DLL main in Windows compilation
|
||||
BOOL APIENTRY DllMain( HANDLE hModule, |
||||
DWORD ul_reason_for_call, |
||||
LPVOID lpReserved |
||||
) |
||||
{ |
||||
switch (ul_reason_for_call) |
||||
{ |
||||
case DLL_PROCESS_ATTACH: |
||||
case DLL_THREAD_ATTACH: |
||||
case DLL_THREAD_DETACH: |
||||
case DLL_PROCESS_DETACH: |
||||
break; |
||||
} |
||||
return TRUE; |
||||
} |
||||
#endif |
||||
|
||||
#include <limits.h> |
||||
#include <string.h> |
||||
#include "SoundTouchDLL.h" |
||||
#include "SoundTouch.h" |
||||
#include "BPMDetect.h" |
||||
|
||||
using namespace soundtouch; |
||||
|
||||
#ifdef SOUNDTOUCH_INTEGER_SAMPLES |
||||
#error "error - compile the dll version with float samples" |
||||
#endif // SOUNDTOUCH_INTEGER_SAMPLES
|
||||
|
||||
//////////////
|
||||
|
||||
SOUNDTOUCHDLL_API ST_HANDLE __cdecl soundtouch_createInstance() |
||||
{ |
||||
return new SoundTouch(); |
||||
} |
||||
|
||||
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_destroyInstance(ST_HANDLE h) |
||||
{ |
||||
delete static_cast<SoundTouch*>(h); |
||||
} |
||||
|
||||
|
||||
/// Get SoundTouch library version string
|
||||
SOUNDTOUCHDLL_API const char *__cdecl soundtouch_getVersionString() |
||||
{ |
||||
return SoundTouch::getVersionString(); |
||||
} |
||||
|
||||
|
||||
/// Get SoundTouch library version Id
|
||||
SOUNDTOUCHDLL_API uint __cdecl soundtouch_getVersionId() |
||||
{ |
||||
return SoundTouch::getVersionId(); |
||||
} |
||||
|
||||
/// Sets new rate control value. Normal rate = 1.0, smaller values
|
||||
/// represent slower rate, larger faster rates.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setRate(ST_HANDLE h, float newRate) |
||||
{ |
||||
static_cast<SoundTouch*>(h)->setRate(newRate); |
||||
} |
||||
|
||||
|
||||
/// Sets new tempo control value. Normal tempo = 1.0, smaller values
|
||||
/// represent slower tempo, larger faster tempo.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setTempo(ST_HANDLE h, float newTempo) |
||||
{ |
||||
static_cast<SoundTouch*>(h)->setTempo(newTempo); |
||||
} |
||||
|
||||
/// Sets new rate control value as a difference in percents compared
|
||||
/// to the original rate (-50 .. +100 %)
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setRateChange(ST_HANDLE h, float newRate) |
||||
{ |
||||
static_cast<SoundTouch*>(h)->setRateChange(newRate); |
||||
} |
||||
|
||||
/// Sets new tempo control value as a difference in percents compared
|
||||
/// to the original tempo (-50 .. +100 %)
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setTempoChange(ST_HANDLE h, float newTempo) |
||||
{ |
||||
static_cast<SoundTouch*>(h)->setTempoChange(newTempo); |
||||
} |
||||
|
||||
/// Sets new pitch control value. Original pitch = 1.0, smaller values
|
||||
/// represent lower pitches, larger values higher pitch.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setPitch(ST_HANDLE h, float newPitch) |
||||
{ |
||||
static_cast<SoundTouch*>(h)->setPitch(newPitch); |
||||
} |
||||
|
||||
/// Sets pitch change in octaves compared to the original pitch
|
||||
/// (-1.00 .. +1.00)
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setPitchOctaves(ST_HANDLE h, float newPitch) |
||||
{ |
||||
static_cast<SoundTouch*>(h)->setPitchOctaves(newPitch); |
||||
} |
||||
|
||||
/// Sets pitch change in semi-tones compared to the original pitch
|
||||
/// (-12 .. +12)
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setPitchSemiTones(ST_HANDLE h, float newPitch) |
||||
{ |
||||
static_cast<SoundTouch*>(h)->setPitchSemiTones(newPitch); |
||||
} |
||||
|
||||
|
||||
/// Sets the number of channels, 1 = mono, 2 = stereo
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setChannels(ST_HANDLE h, uint numChannels) |
||||
{ |
||||
static_cast<SoundTouch*>(h)->setChannels(numChannels); |
||||
} |
||||
|
||||
/// Sets sample rate.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_setSampleRate(ST_HANDLE h, uint srate) |
||||
{ |
||||
static_cast<SoundTouch*>(h)->setSampleRate(srate); |
||||
} |
||||
|
||||
/// Flushes the last samples from the processing pipeline to the output.
|
||||
/// Clears also the internal processing buffers.
|
||||
//
|
||||
/// Note: This function is meant for extracting the last samples of a sound
|
||||
/// stream. This function may introduce additional blank samples in the end
|
||||
/// of the sound stream, and thus it's not recommended to call this function
|
||||
/// in the middle of a sound stream.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_flush(ST_HANDLE h) |
||||
{ |
||||
static_cast<SoundTouch*>(h)->flush(); |
||||
} |
||||
|
||||
/// Adds 'numSamples' pcs of samples from the 'samples' memory position into
|
||||
/// the input of the object. Notice that sample rate _has_to_ be set before
|
||||
/// calling this function, otherwise throws a runtime_error exception.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_putSamples(ST_HANDLE h, |
||||
const SAMPLETYPE *samples, ///< Pointer to sample buffer.
|
||||
unsigned int numSamples ///< Number of samples in buffer. Notice
|
||||
///< that in case of stereo-sound a single sample
|
||||
///< contains data for both channels.
|
||||
) |
||||
{ |
||||
static_cast<SoundTouch*>(h)->putSamples(samples, numSamples); |
||||
} |
||||
|
||||
/// Clears all the samples in the object's output and internal processing
|
||||
/// buffers.
|
||||
SOUNDTOUCHDLL_API void __cdecl soundtouch_clear(ST_HANDLE h) |
||||
{ |
||||
return static_cast<SoundTouch*>(h)->clear(); |
||||
} |
||||
|
||||
/// Changes a setting controlling the processing system behaviour. See the
|
||||
/// 'SETTING_...' defines for available setting ID's.
|
||||
///
|
||||
/// \return 'nonzero' if the setting was successfully changed
|
||||
SOUNDTOUCHDLL_API int __cdecl soundtouch_setSetting(ST_HANDLE h, |
||||
int settingId, ///< Setting ID number. see SETTING_... defines.
|
||||
int value ///< New setting value.
|
||||
) |
||||
{ |
||||
return static_cast<SoundTouch*>(h)->setSetting(settingId, value); |
||||
} |
||||
|
||||
/// Reads a setting controlling the processing system behaviour. See the
|
||||
/// 'SETTING_...' defines for available setting ID's.
|
||||
///
|
||||
/// \return the setting value.
|
||||
SOUNDTOUCHDLL_API int __cdecl soundtouch_getSetting(ST_HANDLE h, |
||||
int settingId ///< Setting ID number, see SETTING_... defines.
|
||||
) |
||||
{ |
||||
return static_cast<SoundTouch*>(h)->getSetting(settingId); |
||||
} |
||||
|
||||
|
||||
/// Returns number of samples currently unprocessed.
|
||||
SOUNDTOUCHDLL_API uint __cdecl soundtouch_numUnprocessedSamples(ST_HANDLE h) |
||||
{ |
||||
return static_cast<SoundTouch*>(h)->numUnprocessedSamples(); |
||||
} |
||||
|
||||
|
||||
/// Receive ready samples from the processing pipeline.
|
||||
///
|
||||
/// if called with outBuffer=nullptr, just reduces amount of ready samples within the pipeline.
|
||||
SOUNDTOUCHDLL_API uint __cdecl soundtouch_receiveSamples(ST_HANDLE h, |
||||
SAMPLETYPE *outBuffer, ///< Buffer where to copy output samples.
|
||||
unsigned int maxSamples ///< How many samples to receive at max.
|
||||
) |
||||
{ |
||||
if (outBuffer) |
||||
{ |
||||
return static_cast<SoundTouch*>(h)->receiveSamples(outBuffer, maxSamples); |
||||
} |
||||
else |
||||
{ |
||||
return static_cast<SoundTouch*>(h)->receiveSamples(maxSamples); |
||||
} |
||||
} |
||||
|
||||
|
||||
/// Returns number of samples currently available.
|
||||
SOUNDTOUCHDLL_API uint __cdecl soundtouch_numSamples(ST_HANDLE h) |
||||
{ |
||||
return static_cast<SoundTouch*>(h)->numSamples(); |
||||
} |
||||
|
||||
|
||||
/// Returns nonzero if there aren't any samples available for outputting.
|
||||
SOUNDTOUCHDLL_API int __cdecl soundtouch_isEmpty(ST_HANDLE h) |
||||
{ |
||||
return static_cast<SoundTouch*>(h)->isEmpty(); |
||||
} |
||||
@ -1,100 +0,0 @@
|
||||
// Microsoft Visual C++ generated resource script. |
||||
// |
||||
#include "resource.h" |
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS |
||||
///////////////////////////////////////////////////////////////////////////// |
||||
// |
||||
// Generated from the TEXTINCLUDE 2 resource. |
||||
// |
||||
#include "winres.h" |
||||
|
||||
///////////////////////////////////////////////////////////////////////////// |
||||
#undef APSTUDIO_READONLY_SYMBOLS |
||||
|
||||
///////////////////////////////////////////////////////////////////////////// |
||||
// English (United States) resources |
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) |
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US |
||||
#pragma code_page(1252) |
||||
|
||||
#ifdef APSTUDIO_INVOKED |
||||
///////////////////////////////////////////////////////////////////////////// |
||||
// |
||||
// TEXTINCLUDE |
||||
// |
||||
|
||||
1 TEXTINCLUDE |
||||
BEGIN |
||||
"resource.h\0" |
||||
END |
||||
|
||||
2 TEXTINCLUDE |
||||
BEGIN |
||||
"#include ""afxres.h""\r\n" |
||||
"\0" |
||||
END |
||||
|
||||
3 TEXTINCLUDE |
||||
BEGIN |
||||
"\r\n" |
||||
"\0" |
||||
END |
||||
|
||||
#endif // APSTUDIO_INVOKED |
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////// |
||||
// |
||||
// Version |
||||
// |
||||
|
||||
VS_VERSION_INFO VERSIONINFO |
||||
FILEVERSION 2,3,2,0 |
||||
PRODUCTVERSION 2,3,2,0 |
||||
FILEFLAGSMASK 0x17L |
||||
#ifdef _DEBUG |
||||
FILEFLAGS 0x1L |
||||
#else |
||||
FILEFLAGS 0x0L |
||||
#endif |
||||
FILEOS 0x4L |
||||
FILETYPE 0x2L |
||||
FILESUBTYPE 0x0L |
||||
BEGIN |
||||
BLOCK "StringFileInfo" |
||||
BEGIN |
||||
BLOCK "000004b0" |
||||
BEGIN |
||||
VALUE "Comments", "SoundTouch Library licensed for 3rd party applications subject to LGPL license v2.1. Visit http://www.surina.net/soundtouch for more information about the SoundTouch library." |
||||
VALUE "FileDescription", "SoundTouch Dynamic Link Library" |
||||
VALUE "FileVersion", "2.3.3.0" |
||||
VALUE "InternalName", "SoundTouch" |
||||
VALUE "LegalCopyright", "Copyright (C) Olli Parviainen 2024" |
||||
VALUE "OriginalFilename", "SoundTouch.dll" |
||||
VALUE "ProductName", " SoundTouch Dynamic Link Library" |
||||
VALUE "ProductVersion", "2.3.3.0" |
||||
END |
||||
END |
||||
BLOCK "VarFileInfo" |
||||
BEGIN |
||||
VALUE "Translation", 0x0, 1200 |
||||
END |
||||
END |
||||
|
||||
#endif // English (United States) resources |
||||
///////////////////////////////////////////////////////////////////////////// |
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED |
||||
///////////////////////////////////////////////////////////////////////////// |
||||
// |
||||
// Generated from the TEXTINCLUDE 3 resource. |
||||
// |
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////// |
||||
#endif // not APSTUDIO_INVOKED |
||||
|
||||
@ -1,15 +0,0 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by SoundTouchDLL.rc
|
||||
//
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED |
||||
#ifndef APSTUDIO_READONLY_SYMBOLS |
||||
#define _APS_NEXT_RESOURCE_VALUE 101 |
||||
#define _APS_NEXT_COMMAND_VALUE 40001 |
||||
#define _APS_NEXT_CONTROL_VALUE 1000 |
||||
#define _APS_NEXT_SYMED_VALUE 101 |
||||
#endif |
||||
#endif |
||||
@ -1,178 +0,0 @@
|
||||
// speex_aec.c — C-обёртка над SpeexDSP mdf.c (акустическое эхоподавление).
|
||||
//
|
||||
// API см. в speex_aec.h. Ключевые детали реализации:
|
||||
// - собственная линия задержки рендера на depth кадров (SpeexDSP даёт только
|
||||
// фиксированные 2 кадра — PLAYBACK_DELAY в mdf.c);
|
||||
// - синхронный speex_echo_cancellation(rec, play_delayed, out) с выравниванием
|
||||
// рендер↔захват по линии задержки;
|
||||
// - дрейф независимых потоков: переполнение линии → отбрасываем старый кадр,
|
||||
// недозаполнение (старт/underflow) → passthrough без канселлера.
|
||||
//
|
||||
// Рендер принимает чанки любого размера; захват — ровно один полный кадр.
|
||||
// Частые аномалии учитываются счётчиками; владелец публикует сводку вне audio callback.
|
||||
|
||||
#include "speex_aec.h" |
||||
#include "debug_config.h" |
||||
#include "mem.h" |
||||
|
||||
#include <string.h> |
||||
#include <limits.h> |
||||
|
||||
#include <speex/speex_echo.h> |
||||
|
||||
#define AEC_ID "speex_aec" |
||||
|
||||
struct speex_aec { |
||||
SpeexEchoState* st; |
||||
int mic_channels, render_channels; |
||||
int frame_samples; /* сэмплов в кадре (960 @48кГц) */ |
||||
int depth; /* глубина линии задержки в кадрах (>=1) */ |
||||
int16_t* line; /* кольцо depth*frame_samples */ |
||||
int head; /* индекс самого старого кадра в line */ |
||||
int count; /* кадров в линии (0..depth) */ |
||||
int16_t* play_acc; /* аккумулятор рендера до кадра */ |
||||
int play_len; |
||||
uint32_t overruns; /* отброшено рендер-кадров (дрейф: рендер быстрее) */ |
||||
uint32_t underruns; /* passthrough-кадров (дрейф: рендер медленнее/старт) */ |
||||
}; |
||||
|
||||
/* Положить полный рендер-кадр в линию задержки (вытесняя старейший при переполнении). */ |
||||
static void aec_enqueue(speex_aec_t* a, const int16_t* frame) { |
||||
if (a->count == a->depth) { |
||||
a->head = (a->head + 1) % a->depth; |
||||
a->count--; |
||||
a->overruns++; |
||||
} |
||||
memcpy(a->line + (size_t)((a->head + a->count) % a->depth) * a->frame_samples * a->render_channels, |
||||
frame, (size_t)a->frame_samples * a->render_channels * sizeof(int16_t)); |
||||
a->count++; |
||||
} |
||||
|
||||
speex_aec_t* speex_aec_create(int sample_rate, int frame_samples, int filter_samples, int delay_frames) { |
||||
return speex_aec_create_mc(sample_rate, frame_samples, filter_samples, delay_frames, 1, 1); |
||||
} |
||||
|
||||
/* Независимые каналы референса: противофазное стерео не усредняется. */ |
||||
speex_aec_t* speex_aec_create_mc(int sample_rate, int frame_samples, int filter_samples, int delay_frames, |
||||
int mic_channels, int render_channels) { |
||||
speex_aec_t* a; |
||||
int rate; |
||||
|
||||
if (sample_rate <= 0 || frame_samples <= 0 || filter_samples < frame_samples || |
||||
mic_channels < 1 || mic_channels > 2 || render_channels < 1 || render_channels > 2 || |
||||
frame_samples > INT_MAX / render_channels || frame_samples > INT_MAX / mic_channels || |
||||
(delay_frames > 0 && delay_frames > INT_MAX / frame_samples / render_channels)) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: bad args rate=%d frame=%d filter=%d delay=%d", |
||||
AEC_ID, sample_rate, frame_samples, filter_samples, delay_frames); |
||||
return NULL; |
||||
} |
||||
|
||||
a = (speex_aec_t*)u_calloc(1, sizeof(*a)); |
||||
if (!a) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: OOM for state", AEC_ID); |
||||
return NULL; |
||||
} |
||||
|
||||
a->st = speex_echo_state_init_mc(frame_samples, filter_samples, mic_channels, render_channels); |
||||
if (!a->st) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: echo state allocation failed", AEC_ID); |
||||
u_free(a); |
||||
return NULL; |
||||
} |
||||
rate = sample_rate; |
||||
if (speex_echo_ctl(a->st, SPEEX_ECHO_SET_SAMPLING_RATE, &rate) != 0) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: SET_SAMPLING_RATE(%d) failed", AEC_ID, sample_rate); |
||||
speex_echo_state_destroy(a->st); |
||||
u_free(a); |
||||
return NULL; |
||||
} |
||||
|
||||
a->mic_channels = mic_channels; a->render_channels = render_channels; |
||||
a->frame_samples = frame_samples; |
||||
a->depth = delay_frames > 0 ? delay_frames : 1; |
||||
a->line = (int16_t*)u_calloc((uint32_t)(a->depth * frame_samples * render_channels), sizeof(int16_t)); |
||||
a->play_acc = (int16_t*)u_calloc((uint32_t)(frame_samples * render_channels), sizeof(int16_t)); |
||||
if (!a->line || !a->play_acc) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: OOM for buffers", AEC_ID); |
||||
if (a->line) u_free(a->line); |
||||
if (a->play_acc) u_free(a->play_acc); |
||||
speex_echo_state_destroy(a->st); |
||||
u_free(a); |
||||
return NULL; |
||||
} |
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_AEC, "%s: created rate=%d frame=%d filter=%d delay=%d frames mic=%d render=%d", |
||||
AEC_ID, sample_rate, frame_samples, filter_samples, a->depth, mic_channels, render_channels); |
||||
return a; |
||||
} |
||||
|
||||
void speex_aec_destroy(speex_aec_t* a) { |
||||
if (!a) return; |
||||
speex_echo_state_destroy(a->st); |
||||
u_free(a->line); |
||||
u_free(a->play_acc); |
||||
u_free(a); |
||||
} |
||||
|
||||
void speex_aec_reset(speex_aec_t* a) { |
||||
if (!a) return; |
||||
speex_echo_state_reset(a->st); |
||||
a->head = 0; |
||||
a->count = 0; |
||||
a->play_len = 0; |
||||
DEBUG_INFO(DEBUG_CATEGORY_AEC, "%s: reset (filter + delay line)", AEC_ID); |
||||
} |
||||
|
||||
void speex_aec_feed_playback(speex_aec_t* a, const int16_t* pcm, int count) { |
||||
int fs, take; |
||||
|
||||
if (!a || !pcm || count <= 0) return; |
||||
fs = a->frame_samples * a->render_channels; |
||||
|
||||
while (count > 0) { |
||||
take = fs - a->play_len; |
||||
if (take > count) take = count; |
||||
memcpy(a->play_acc + a->play_len, pcm, (size_t)take * sizeof(int16_t)); |
||||
a->play_len += take; |
||||
pcm += take; |
||||
count -= take; |
||||
if (a->play_len == fs) { |
||||
aec_enqueue(a, a->play_acc); |
||||
a->play_len = 0; |
||||
} |
||||
} |
||||
} |
||||
|
||||
int speex_aec_process_capture(speex_aec_t* a, const int16_t* pcm, int count, int16_t* out) { |
||||
int fs; |
||||
|
||||
if (!a || !pcm || !out || count <= 0) return 0; |
||||
fs = a->frame_samples; |
||||
|
||||
if (count != fs * a->mic_channels) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: capture requires one frame samples=%d expected=%d", AEC_ID, count, fs * a->mic_channels); |
||||
return 0; |
||||
} |
||||
|
||||
if (a->count == a->depth) { |
||||
const int16_t* ref = a->line + (size_t)a->head * fs * a->render_channels; |
||||
speex_echo_cancellation(a->st, pcm, ref, out); |
||||
a->head = (a->head + 1) % a->depth; |
||||
a->count--; |
||||
} else { |
||||
/* линия не наполнена (старт или рендер отстаёт): passthrough без канселлера */ |
||||
memcpy(out, pcm, (size_t)count * sizeof(int16_t)); |
||||
a->underruns++; |
||||
} |
||||
|
||||
return count; |
||||
} |
||||
|
||||
int speex_aec_delay_fill(const speex_aec_t* a) { |
||||
return a ? a->count : 0; |
||||
} |
||||
|
||||
void speex_aec_get_stats(const speex_aec_t* a, uint32_t* overruns, uint32_t* underruns) { |
||||
*overruns = a ? a->overruns : 0; |
||||
*underruns = a ? a->underruns : 0; |
||||
} |
||||
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* speex_aec.h — акустическое эхоподавление (AEC), C-обёртка над SpeexDSP mdf.c. |
||||
* |
||||
* Подавляет эхо дальнего конца (то, что играем в динамик) в сигнале микрофона |
||||
* перед кодированием. Работает на interleaved int16 PCM, 1/2 канала, частота 48000 (можно |
||||
* 8000/16000/32000/48000), кадр 20 мс (960 сэмплов @48 кГц). |
||||
* |
||||
* Модель использования (duplex-контур звонка): |
||||
* - рендер (far-end, то что пошло в динамик) → speex_aec_feed_playback(); |
||||
* - захват (near-end, микрофон) → speex_aec_process_capture(). |
||||
* |
||||
* Обёртка держит собственную линию задержки рендера на `delay_frames` кадров и |
||||
* зовёт синхронный speex_echo_cancellation(rec, play_delayed, out). Это делает |
||||
* выравнивание рендер↔захват предсказуемым и настраиваемым (встроенный буфер |
||||
* SpeexDSP фиксирован в 2 кадра и для Android-задержки не годится). |
||||
* |
||||
* Дрейф двух независимых потоков (capture/play на Android) компенсируется |
||||
* ограниченной глубиной линии: переполнение → отбрасываем старый рендер-кадр, |
||||
* недозаполнение → passthrough без канселлера; счётчики публикует владелец вне callback. |
||||
* |
||||
* Зависимости: SpeexDSP (mdf.c/fftwrap.c/kiss_fft*) из lib/speexdsp, флаги |
||||
* FLOATING_POINT + USE_KISS_FFT. Собирается всегда (без внешних зависимостей). |
||||
* |
||||
* Лицензия SpeexDSP: 3-clause BSD (Xiph) — см. lib/speexdsp/COPYING. |
||||
*/ |
||||
#ifndef SPEEX_AEC_H |
||||
#define SPEEX_AEC_H |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
typedef struct speex_aec speex_aec_t; |
||||
|
||||
/**
|
||||
* Создать эхоканселлер. |
||||
* sample_rate — 48000 (также 8000/16000/32000); |
||||
* frame_samples — сэмплов в кадре (960 = 20 мс @48 кГц); |
||||
* filter_samples— длина эхо-хвоста в сэмплах (14400 = 300 мс, кратно кадру); |
||||
* delay_frames — глубина линии рендер→захват в кадрах (значения <=0 приводятся к 1). |
||||
* Возвращает NULL при ошибке (лог категории "aec"). |
||||
*/ |
||||
speex_aec_t* speex_aec_create(int sample_rate, int frame_samples, int filter_samples, int delay_frames); |
||||
|
||||
/* Многоканальный AEC: frame_samples на канал, count в feed/process — все interleaved отсчёты.
|
||||
* mic_channels и render_channels — 1/2; возвращаемый count захвата равен frame_samples*mic_channels. */ |
||||
speex_aec_t* speex_aec_create_mc(int sample_rate, int frame_samples, int filter_samples, int delay_frames, |
||||
int mic_channels, int render_channels); |
||||
|
||||
/* Освободить канселлер. NULL безопасен. */ |
||||
void speex_aec_destroy(speex_aec_t* aec); |
||||
|
||||
/* Сбросить адаптивный фильтр и линию задержки (смена устройства/роута). */ |
||||
void speex_aec_reset(speex_aec_t* aec); |
||||
|
||||
/**
|
||||
* Подать рендер (дальний конец). Накопление до кадра внутри; полный кадр |
||||
* кладётся в линию задержки. count может быть любым (обычно 480 или 960). |
||||
*/ |
||||
void speex_aec_feed_playback(speex_aec_t* aec, const int16_t* pcm, int count); |
||||
|
||||
/**
|
||||
* Обработать ровно один кадр захвата (count == frame_samples*mic_channels). |
||||
* Возвращает count при успехе, 0 при ошибке. out вмещает полный interleaved кадр |
||||
* и не должен алиаситься с pcm. Накопитель аппаратных чанков принадлежит I/O-движку. |
||||
*/ |
||||
int speex_aec_process_capture(speex_aec_t* aec, const int16_t* pcm, int count, int16_t* out); |
||||
|
||||
/* Актуальная глубина линии задержки (кадров) — для диагностики дрейфа. */ |
||||
int speex_aec_delay_fill(const speex_aec_t* aec); |
||||
/* Под той же блокировкой, что feed/process; выводить снимок вне callback. */ |
||||
void speex_aec_get_stats(const speex_aec_t* aec, uint32_t* overruns, uint32_t* underruns); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif /* SPEEX_AEC_H */ |
||||
@ -1,35 +0,0 @@
|
||||
Copyright 2002-2008 Xiph.org Foundation |
||||
Copyright 2002-2008 Jean-Marc Valin |
||||
Copyright 2005-2007 Analog Devices Inc. |
||||
Copyright 2005-2008 Commonwealth Scientific and Industrial Research |
||||
Organisation (CSIRO) |
||||
Copyright 1993, 2002, 2006 David Rowe |
||||
Copyright 2003 EpicGames |
||||
Copyright 1992-1994 Jutta Degener, Carsten Bormann |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
- Neither the name of the Xiph.org Foundation nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR |
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
@ -1,160 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2003-2004, Mark Borgerding |
||||
|
||||
All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: |
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. |
||||
* Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#define MIN(a,b) ((a)<(b) ? (a):(b)) |
||||
#define MAX(a,b) ((a)>(b) ? (a):(b)) |
||||
|
||||
/* kiss_fft.h
|
||||
defines kiss_fft_scalar as either short or a float type |
||||
and defines |
||||
typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */ |
||||
#include "kiss_fft.h" |
||||
#include "math_approx.h" |
||||
|
||||
#define MAXFACTORS 32 |
||||
/* e.g. an fft of length 128 has 4 factors
|
||||
as far as kissfft is concerned |
||||
4*4*4*2 |
||||
*/ |
||||
|
||||
struct kiss_fft_state{ |
||||
int nfft; |
||||
int inverse; |
||||
int factors[2*MAXFACTORS]; |
||||
kiss_fft_cpx twiddles[1]; |
||||
}; |
||||
|
||||
/*
|
||||
Explanation of macros dealing with complex math: |
||||
|
||||
C_MUL(m,a,b) : m = a*b |
||||
C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise |
||||
C_SUB( res, a,b) : res = a - b |
||||
C_SUBFROM( res , a) : res -= a |
||||
C_ADDTO( res , a) : res += a |
||||
* */ |
||||
#ifdef FIXED_POINT |
||||
#include "arch.h" |
||||
# define FRACBITS 15 |
||||
# define SAMPPROD spx_int32_t |
||||
#define SAMP_MAX 32767 |
||||
|
||||
#define SAMP_MIN -SAMP_MAX |
||||
|
||||
#if defined(CHECK_OVERFLOW) |
||||
# define CHECK_OVERFLOW_OP(a,op,b) \ |
||||
if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \
|
||||
fprintf(stderr,"WARNING:overflow @ " __FILE__ "(%d): (%d " #op" %d) = %ld\n",__LINE__,(a),(b),(SAMPPROD)(a) op (SAMPPROD)(b) ); } |
||||
#endif |
||||
|
||||
|
||||
# define smul(a,b) ( (SAMPPROD)(a)*(b) ) |
||||
# define sround( x ) (kiss_fft_scalar)( ( (x) + (1<<(FRACBITS-1)) ) >> FRACBITS ) |
||||
|
||||
# define S_MUL(a,b) sround( smul(a,b) ) |
||||
|
||||
# define C_MUL(m,a,b) \ |
||||
do{ (m).r = sround( smul((a).r,(b).r) - smul((a).i,(b).i) ); \
|
||||
(m).i = sround( smul((a).r,(b).i) + smul((a).i,(b).r) ); }while(0) |
||||
|
||||
# define C_MUL4(m,a,b) \ |
||||
do{ (m).r = PSHR32( smul((a).r,(b).r) - smul((a).i,(b).i),17 ); \
|
||||
(m).i = PSHR32( smul((a).r,(b).i) + smul((a).i,(b).r),17 ); }while(0) |
||||
|
||||
# define DIVSCALAR(x,k) \ |
||||
(x) = sround( smul( x, SAMP_MAX/k ) ) |
||||
|
||||
# define C_FIXDIV(c,div) \ |
||||
do { DIVSCALAR( (c).r , div); \
|
||||
DIVSCALAR( (c).i , div); }while (0) |
||||
|
||||
# define C_MULBYSCALAR( c, s ) \ |
||||
do{ (c).r = sround( smul( (c).r , s ) ) ;\
|
||||
(c).i = sround( smul( (c).i , s ) ) ; }while(0) |
||||
|
||||
#else /* not FIXED_POINT*/ |
||||
|
||||
# define S_MUL(a,b) ( (a)*(b) ) |
||||
#define C_MUL(m,a,b) \ |
||||
do{ (m).r = (a).r*(b).r - (a).i*(b).i;\
|
||||
(m).i = (a).r*(b).i + (a).i*(b).r; }while(0) |
||||
|
||||
#define C_MUL4(m,a,b) C_MUL(m,a,b) |
||||
|
||||
# define C_FIXDIV(c,div) /* NOOP */ |
||||
# define C_MULBYSCALAR( c, s ) \ |
||||
do{ (c).r *= (s);\
|
||||
(c).i *= (s); }while(0) |
||||
#endif |
||||
|
||||
#ifndef CHECK_OVERFLOW_OP |
||||
# define CHECK_OVERFLOW_OP(a,op,b) /* noop */ |
||||
#endif |
||||
|
||||
#define C_ADD( res, a,b)\ |
||||
do { \
|
||||
CHECK_OVERFLOW_OP((a).r,+,(b).r)\
|
||||
CHECK_OVERFLOW_OP((a).i,+,(b).i)\
|
||||
(res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \
|
||||
}while(0) |
||||
#define C_SUB( res, a,b)\ |
||||
do { \
|
||||
CHECK_OVERFLOW_OP((a).r,-,(b).r)\
|
||||
CHECK_OVERFLOW_OP((a).i,-,(b).i)\
|
||||
(res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \
|
||||
}while(0) |
||||
#define C_ADDTO( res , a)\ |
||||
do { \
|
||||
CHECK_OVERFLOW_OP((res).r,+,(a).r)\
|
||||
CHECK_OVERFLOW_OP((res).i,+,(a).i)\
|
||||
(res).r += (a).r; (res).i += (a).i;\
|
||||
}while(0) |
||||
|
||||
#define C_SUBFROM( res , a)\ |
||||
do {\
|
||||
CHECK_OVERFLOW_OP((res).r,-,(a).r)\
|
||||
CHECK_OVERFLOW_OP((res).i,-,(a).i)\
|
||||
(res).r -= (a).r; (res).i -= (a).i; \
|
||||
}while(0) |
||||
|
||||
|
||||
#ifdef FIXED_POINT |
||||
# define KISS_FFT_COS(phase) floor(MIN(32767,MAX(-32767,.5+32768 * cos (phase)))) |
||||
# define KISS_FFT_SIN(phase) floor(MIN(32767,MAX(-32767,.5+32768 * sin (phase)))) |
||||
# define HALF_OF(x) ((x)>>1) |
||||
#elif defined(USE_SIMD) |
||||
# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) |
||||
# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) |
||||
# define HALF_OF(x) ((x)*_mm_set1_ps(.5)) |
||||
#else |
||||
# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) |
||||
# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) |
||||
# define HALF_OF(x) ((x)*.5) |
||||
#endif |
||||
|
||||
#define kf_cexp(x,phase) \ |
||||
do{ \
|
||||
(x)->r = KISS_FFT_COS(phase);\
|
||||
(x)->i = KISS_FFT_SIN(phase);\
|
||||
}while(0) |
||||
#define kf_cexp2(x,phase) \ |
||||
do{ \
|
||||
(x)->r = spx_cos_norm((phase));\
|
||||
(x)->i = spx_cos_norm((phase)-32768);\
|
||||
}while(0) |
||||
|
||||
|
||||
/* a debugging function */ |
||||
#define pcpx(c)\ |
||||
fprintf(stderr,"%g + %gi\n",(double)((c)->r),(double)((c)->i) ) |
||||
@ -1,232 +0,0 @@
|
||||
/* Copyright (C) 2003 Jean-Marc Valin */ |
||||
/**
|
||||
@file arch.h |
||||
@brief Various architecture definitions Speex |
||||
*/ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
- Neither the name of the Xiph.org Foundation nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR |
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef ARCH_H |
||||
#define ARCH_H |
||||
|
||||
/* A couple test to catch stupid option combinations */ |
||||
#ifdef FIXED_POINT |
||||
|
||||
#ifdef FLOATING_POINT |
||||
#error You cannot compile as floating point and fixed point at the same time |
||||
#endif |
||||
#ifdef USE_SSE |
||||
#error SSE is only for floating-point |
||||
#endif |
||||
#if defined(ARM4_ASM) + defined(ARM5E_ASM) + defined(BFIN_ASM) > 1 |
||||
#error Make up your mind. What CPU do you have? |
||||
#endif |
||||
#ifdef VORBIS_PSYCHO |
||||
#error Vorbis-psy model currently not implemented in fixed-point |
||||
#endif |
||||
|
||||
#else |
||||
|
||||
#ifndef FLOATING_POINT |
||||
#error You now need to define either FIXED_POINT or FLOATING_POINT |
||||
#endif |
||||
#if defined(ARM4_ASM) || defined(ARM5E_ASM) || defined(BFIN_ASM) |
||||
#error I suppose you can have a [ARM4/ARM5E/Blackfin] that has float instructions? |
||||
#endif |
||||
#ifdef FIXED_DEBUG |
||||
#error "Don't you think enabling fixed-point is a good thing to do if you want to debug that?" |
||||
#endif |
||||
|
||||
|
||||
#endif |
||||
|
||||
#ifndef OUTSIDE_SPEEX |
||||
#include "speex/speexdsp_types.h" |
||||
#endif |
||||
|
||||
#define ABS(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute integer value. */ |
||||
#define ABS16(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 16-bit value. */ |
||||
#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 16-bit value. */ |
||||
#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */ |
||||
#define ABS32(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 32-bit value. */ |
||||
#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 32-bit value. */ |
||||
#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */ |
||||
|
||||
#ifdef FIXED_POINT |
||||
|
||||
typedef spx_int16_t spx_word16_t; |
||||
typedef spx_int32_t spx_word32_t; |
||||
typedef spx_word32_t spx_mem_t; |
||||
typedef spx_word16_t spx_coef_t; |
||||
typedef spx_word16_t spx_lsp_t; |
||||
typedef spx_word32_t spx_sig_t; |
||||
|
||||
#define Q15ONE 32767 |
||||
|
||||
#define LPC_SCALING 8192 |
||||
#define SIG_SCALING 16384 |
||||
#define LSP_SCALING 8192. |
||||
#define GAMMA_SCALING 32768. |
||||
#define GAIN_SCALING 64 |
||||
#define GAIN_SCALING_1 0.015625 |
||||
|
||||
#define LPC_SHIFT 13 |
||||
#define LSP_SHIFT 13 |
||||
#define SIG_SHIFT 14 |
||||
#define GAIN_SHIFT 6 |
||||
|
||||
#define WORD2INT(x) ((x) < -32767 ? -32768 : ((x) > 32766 ? 32767 : (x))) |
||||
|
||||
#define VERY_SMALL 0 |
||||
#define VERY_LARGE32 ((spx_word32_t)2147483647) |
||||
#define VERY_LARGE16 ((spx_word16_t)32767) |
||||
#define Q15_ONE ((spx_word16_t)32767) |
||||
|
||||
|
||||
#ifdef FIXED_DEBUG |
||||
#include "fixed_debug.h" |
||||
#else |
||||
|
||||
#include "fixed_generic.h" |
||||
|
||||
#ifdef ARM5E_ASM |
||||
#include "fixed_arm5e.h" |
||||
#elif defined(ARM4_ASM) |
||||
#include "fixed_arm4.h" |
||||
#elif defined(BFIN_ASM) |
||||
#include "fixed_bfin.h" |
||||
#endif |
||||
|
||||
#endif |
||||
|
||||
|
||||
#else |
||||
|
||||
typedef float spx_mem_t; |
||||
typedef float spx_coef_t; |
||||
typedef float spx_lsp_t; |
||||
typedef float spx_sig_t; |
||||
typedef float spx_word16_t; |
||||
typedef float spx_word32_t; |
||||
|
||||
#define Q15ONE 1.0f |
||||
#define LPC_SCALING 1.f |
||||
#define SIG_SCALING 1.f |
||||
#define LSP_SCALING 1.f |
||||
#define GAMMA_SCALING 1.f |
||||
#define GAIN_SCALING 1.f |
||||
#define GAIN_SCALING_1 1.f |
||||
|
||||
|
||||
#define VERY_SMALL 1e-15f |
||||
#define VERY_LARGE32 1e15f |
||||
#define VERY_LARGE16 1e15f |
||||
#define Q15_ONE ((spx_word16_t)1.f) |
||||
|
||||
#define QCONST16(x,bits) (x) |
||||
#define QCONST32(x,bits) (x) |
||||
|
||||
#define NEG16(x) (-(x)) |
||||
#define NEG32(x) (-(x)) |
||||
#define EXTRACT16(x) (x) |
||||
#define EXTEND32(x) (x) |
||||
#define SHR16(a,shift) (a) |
||||
#define SHL16(a,shift) (a) |
||||
#define SHR32(a,shift) (a) |
||||
#define SHL32(a,shift) (a) |
||||
#define PSHR16(a,shift) (a) |
||||
#define PSHR32(a,shift) (a) |
||||
#define VSHR32(a,shift) (a) |
||||
#define SATURATE16(x,a) (x) |
||||
#define SATURATE32(x,a) (x) |
||||
#define SATURATE32PSHR(x,shift,a) (x) |
||||
|
||||
#define PSHR(a,shift) (a) |
||||
#define SHR(a,shift) (a) |
||||
#define SHL(a,shift) (a) |
||||
#define SATURATE(x,a) (x) |
||||
|
||||
#define ADD16(a,b) ((a)+(b)) |
||||
#define SUB16(a,b) ((a)-(b)) |
||||
#define ADD32(a,b) ((a)+(b)) |
||||
#define SUB32(a,b) ((a)-(b)) |
||||
#define MULT16_16_16(a,b) ((a)*(b)) |
||||
#define MULT16_32_32(a,b) ((a)*(b)) |
||||
#define MULT16_16(a,b) ((spx_word32_t)(a)*(spx_word32_t)(b)) |
||||
#define MAC16_16(c,a,b) ((c)+(spx_word32_t)(a)*(spx_word32_t)(b)) |
||||
|
||||
#define MULT16_32_Q15(a,b) ((a)*(b)) |
||||
#define MULT16_32_P15(a,b) ((a)*(b)) |
||||
|
||||
#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b)) |
||||
|
||||
#define MAC16_16_Q11(c,a,b) ((c)+(a)*(b)) |
||||
#define MAC16_16_Q13(c,a,b) ((c)+(a)*(b)) |
||||
#define MAC16_16_P13(c,a,b) ((c)+(a)*(b)) |
||||
#define MULT16_16_Q11_32(a,b) ((a)*(b)) |
||||
#define MULT16_16_Q13(a,b) ((a)*(b)) |
||||
#define MULT16_16_Q14(a,b) ((a)*(b)) |
||||
#define MULT16_16_Q15(a,b) ((a)*(b)) |
||||
#define MULT16_16_P15(a,b) ((a)*(b)) |
||||
#define MULT16_16_P13(a,b) ((a)*(b)) |
||||
#define MULT16_16_P14(a,b) ((a)*(b)) |
||||
|
||||
#define DIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b)) |
||||
#define PDIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b)) |
||||
#define DIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b)) |
||||
#define PDIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b)) |
||||
|
||||
#define WORD2INT(x) ((x) < -32767.5f ? -32768 : \ |
||||
((x) > 32766.5f ? 32767 : (spx_int16_t)floor(.5 + (x)))) |
||||
#endif |
||||
|
||||
|
||||
#if defined(CONFIG_TI_C54X) || defined(CONFIG_TI_C55X) |
||||
|
||||
/* 2 on TI C5x DSP */ |
||||
#define BYTES_PER_CHAR 2 |
||||
#define BITS_PER_CHAR 16 |
||||
#define LOG2_BITS_PER_CHAR 4 |
||||
|
||||
#else |
||||
|
||||
#define BYTES_PER_CHAR 1 |
||||
#define BITS_PER_CHAR 8 |
||||
#define LOG2_BITS_PER_CHAR 3 |
||||
|
||||
#endif |
||||
|
||||
|
||||
|
||||
#ifdef FIXED_DEBUG |
||||
extern long long spx_mips; |
||||
#endif |
||||
|
||||
|
||||
#endif |
||||
@ -1,448 +0,0 @@
|
||||
/* Copyright (C) 2005-2006 Jean-Marc Valin
|
||||
File: fftwrap.c |
||||
|
||||
Wrapper for various FFTs |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
- Neither the name of the Xiph.org Foundation nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR |
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
*/ |
||||
|
||||
#ifdef HAVE_CONFIG_H |
||||
#include "config.h" |
||||
#endif |
||||
|
||||
#include "arch.h" |
||||
#include "os_support.h" |
||||
|
||||
#define MAX_FFT_SIZE 2048 |
||||
|
||||
#ifdef FIXED_POINT |
||||
static int maximize_range(spx_word16_t *in, spx_word16_t *out, spx_word16_t bound, int len) |
||||
{ |
||||
int i, shift; |
||||
spx_word16_t max_val = 0; |
||||
for (i=0;i<len;i++) |
||||
{ |
||||
if (in[i]>max_val) |
||||
max_val = in[i]; |
||||
if (-in[i]>max_val) |
||||
max_val = -in[i]; |
||||
} |
||||
shift=0; |
||||
while (max_val <= (bound>>1) && max_val != 0) |
||||
{ |
||||
max_val <<= 1; |
||||
shift++; |
||||
} |
||||
for (i=0;i<len;i++) |
||||
{ |
||||
out[i] = SHL16(in[i], shift); |
||||
} |
||||
return shift; |
||||
} |
||||
|
||||
static void renorm_range(spx_word16_t *in, spx_word16_t *out, int shift, int len) |
||||
{ |
||||
int i; |
||||
for (i=0;i<len;i++) |
||||
{ |
||||
out[i] = PSHR16(in[i], shift); |
||||
} |
||||
} |
||||
#endif |
||||
|
||||
#ifdef USE_SMALLFT |
||||
|
||||
#include "smallft.h" |
||||
#include <math.h> |
||||
|
||||
void *spx_fft_init(int size) |
||||
{ |
||||
struct drft_lookup *table; |
||||
table = speex_alloc(sizeof(struct drft_lookup)); |
||||
spx_drft_init((struct drft_lookup *)table, size); |
||||
return (void*)table; |
||||
} |
||||
|
||||
void spx_fft_destroy(void *table) |
||||
{ |
||||
spx_drft_clear(table); |
||||
speex_free(table); |
||||
} |
||||
|
||||
void spx_fft(void *table, float *in, float *out) |
||||
{ |
||||
if (in==out) |
||||
{ |
||||
int i; |
||||
float scale = 1./((struct drft_lookup *)table)->n; |
||||
speex_warning("FFT should not be done in-place"); |
||||
for (i=0;i<((struct drft_lookup *)table)->n;i++) |
||||
out[i] = scale*in[i]; |
||||
} else { |
||||
int i; |
||||
float scale = 1./((struct drft_lookup *)table)->n; |
||||
for (i=0;i<((struct drft_lookup *)table)->n;i++) |
||||
out[i] = scale*in[i]; |
||||
} |
||||
spx_drft_forward((struct drft_lookup *)table, out); |
||||
} |
||||
|
||||
void spx_ifft(void *table, float *in, float *out) |
||||
{ |
||||
if (in==out) |
||||
{ |
||||
speex_warning("FFT should not be done in-place"); |
||||
} else { |
||||
int i; |
||||
for (i=0;i<((struct drft_lookup *)table)->n;i++) |
||||
out[i] = in[i]; |
||||
} |
||||
spx_drft_backward((struct drft_lookup *)table, out); |
||||
} |
||||
|
||||
#elif defined(USE_INTEL_MKL) |
||||
#include <mkl.h> |
||||
|
||||
struct mkl_config { |
||||
DFTI_DESCRIPTOR_HANDLE desc; |
||||
int N; |
||||
}; |
||||
|
||||
void *spx_fft_init(int size) |
||||
{ |
||||
struct mkl_config *table = (struct mkl_config *) speex_alloc(sizeof(struct mkl_config)); |
||||
table->N = size; |
||||
DftiCreateDescriptor(&table->desc, DFTI_SINGLE, DFTI_REAL, 1, size); |
||||
DftiSetValue(table->desc, DFTI_PACKED_FORMAT, DFTI_PACK_FORMAT); |
||||
DftiSetValue(table->desc, DFTI_PLACEMENT, DFTI_NOT_INPLACE); |
||||
DftiSetValue(table->desc, DFTI_FORWARD_SCALE, 1.0f / size); |
||||
DftiCommitDescriptor(table->desc); |
||||
return table; |
||||
} |
||||
|
||||
void spx_fft_destroy(void *table) |
||||
{ |
||||
struct mkl_config *t = (struct mkl_config *) table; |
||||
DftiFreeDescriptor(t->desc); |
||||
speex_free(table); |
||||
} |
||||
|
||||
void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out) |
||||
{ |
||||
struct mkl_config *t = (struct mkl_config *) table; |
||||
DftiComputeForward(t->desc, in, out); |
||||
} |
||||
|
||||
void spx_ifft(void *table, spx_word16_t *in, spx_word16_t *out) |
||||
{ |
||||
struct mkl_config *t = (struct mkl_config *) table; |
||||
DftiComputeBackward(t->desc, in, out); |
||||
} |
||||
|
||||
#elif defined(USE_INTEL_IPP) |
||||
|
||||
#include <ipps.h> |
||||
|
||||
struct ipp_fft_config |
||||
{ |
||||
IppsDFTSpec_R_32f *dftSpec; |
||||
Ipp8u *buffer; |
||||
}; |
||||
|
||||
void *spx_fft_init(int size) |
||||
{ |
||||
int bufferSize = 0; |
||||
int hint; |
||||
struct ipp_fft_config *table; |
||||
|
||||
table = (struct ipp_fft_config *)speex_alloc(sizeof(struct ipp_fft_config)); |
||||
|
||||
/* there appears to be no performance difference between ippAlgHintFast and
|
||||
ippAlgHintAccurate when using the with the floating point version |
||||
of the fft. */ |
||||
hint = ippAlgHintAccurate; |
||||
|
||||
ippsDFTInitAlloc_R_32f(&table->dftSpec, size, IPP_FFT_DIV_FWD_BY_N, hint); |
||||
|
||||
ippsDFTGetBufSize_R_32f(table->dftSpec, &bufferSize); |
||||
table->buffer = ippsMalloc_8u(bufferSize); |
||||
|
||||
return table; |
||||
} |
||||
|
||||
void spx_fft_destroy(void *table) |
||||
{ |
||||
struct ipp_fft_config *t = (struct ipp_fft_config *)table; |
||||
ippsFree(t->buffer); |
||||
ippsDFTFree_R_32f(t->dftSpec); |
||||
speex_free(t); |
||||
} |
||||
|
||||
void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out) |
||||
{ |
||||
struct ipp_fft_config *t = (struct ipp_fft_config *)table; |
||||
ippsDFTFwd_RToPack_32f(in, out, t->dftSpec, t->buffer); |
||||
} |
||||
|
||||
void spx_ifft(void *table, spx_word16_t *in, spx_word16_t *out) |
||||
{ |
||||
struct ipp_fft_config *t = (struct ipp_fft_config *)table; |
||||
ippsDFTInv_PackToR_32f(in, out, t->dftSpec, t->buffer); |
||||
} |
||||
|
||||
#elif defined(USE_GPL_FFTW3) |
||||
|
||||
#include <fftw3.h> |
||||
|
||||
struct fftw_config { |
||||
float *in; |
||||
float *out; |
||||
fftwf_plan fft; |
||||
fftwf_plan ifft; |
||||
int N; |
||||
}; |
||||
|
||||
void *spx_fft_init(int size) |
||||
{ |
||||
struct fftw_config *table = (struct fftw_config *) speex_alloc(sizeof(struct fftw_config)); |
||||
table->in = fftwf_malloc(sizeof(float) * (size+2)); |
||||
table->out = fftwf_malloc(sizeof(float) * (size+2)); |
||||
|
||||
table->fft = fftwf_plan_dft_r2c_1d(size, table->in, (fftwf_complex *) table->out, FFTW_PATIENT); |
||||
table->ifft = fftwf_plan_dft_c2r_1d(size, (fftwf_complex *) table->in, table->out, FFTW_PATIENT); |
||||
|
||||
table->N = size; |
||||
return table; |
||||
} |
||||
|
||||
void spx_fft_destroy(void *table) |
||||
{ |
||||
struct fftw_config *t = (struct fftw_config *) table; |
||||
fftwf_destroy_plan(t->fft); |
||||
fftwf_destroy_plan(t->ifft); |
||||
fftwf_free(t->in); |
||||
fftwf_free(t->out); |
||||
speex_free(table); |
||||
} |
||||
|
||||
|
||||
void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out) |
||||
{ |
||||
int i; |
||||
struct fftw_config *t = (struct fftw_config *) table; |
||||
const int N = t->N; |
||||
float *iptr = t->in; |
||||
float *optr = t->out; |
||||
const float m = 1.0 / N; |
||||
for(i=0;i<N;++i) |
||||
iptr[i]=in[i] * m; |
||||
|
||||
fftwf_execute(t->fft); |
||||
|
||||
out[0] = optr[0]; |
||||
for(i=1;i<N;++i) |
||||
out[i] = optr[i+1]; |
||||
} |
||||
|
||||
void spx_ifft(void *table, spx_word16_t *in, spx_word16_t *out) |
||||
{ |
||||
int i; |
||||
struct fftw_config *t = (struct fftw_config *) table; |
||||
const int N = t->N; |
||||
float *iptr = t->in; |
||||
float *optr = t->out; |
||||
|
||||
iptr[0] = in[0]; |
||||
iptr[1] = 0.0f; |
||||
for(i=1;i<N;++i) |
||||
iptr[i+1] = in[i]; |
||||
iptr[N+1] = 0.0f; |
||||
|
||||
fftwf_execute(t->ifft); |
||||
|
||||
for(i=0;i<N;++i) |
||||
out[i] = optr[i]; |
||||
} |
||||
|
||||
#elif defined(USE_KISS_FFT) |
||||
|
||||
#include "kiss_fftr.h" |
||||
#include "kiss_fft.h" |
||||
|
||||
struct kiss_config { |
||||
kiss_fftr_cfg forward; |
||||
kiss_fftr_cfg backward; |
||||
int N; |
||||
}; |
||||
|
||||
void *spx_fft_init(int size) |
||||
{ |
||||
struct kiss_config *table; |
||||
table = (struct kiss_config*)speex_alloc(sizeof(struct kiss_config)); |
||||
table->forward = kiss_fftr_alloc(size,0,NULL,NULL); |
||||
table->backward = kiss_fftr_alloc(size,1,NULL,NULL); |
||||
table->N = size; |
||||
return table; |
||||
} |
||||
|
||||
void spx_fft_destroy(void *table) |
||||
{ |
||||
struct kiss_config *t = (struct kiss_config *)table; |
||||
kiss_fftr_free(t->forward); |
||||
kiss_fftr_free(t->backward); |
||||
speex_free(table); |
||||
} |
||||
|
||||
#ifdef FIXED_POINT |
||||
|
||||
void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out) |
||||
{ |
||||
int shift; |
||||
struct kiss_config *t = (struct kiss_config *)table; |
||||
shift = maximize_range(in, in, 32000, t->N); |
||||
kiss_fftr2(t->forward, in, out); |
||||
renorm_range(in, in, shift, t->N); |
||||
renorm_range(out, out, shift, t->N); |
||||
} |
||||
|
||||
#else |
||||
|
||||
void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out) |
||||
{ |
||||
int i; |
||||
float scale; |
||||
struct kiss_config *t = (struct kiss_config *)table; |
||||
scale = 1./t->N; |
||||
kiss_fftr2(t->forward, in, out); |
||||
for (i=0;i<t->N;i++) |
||||
out[i] *= scale; |
||||
} |
||||
#endif |
||||
|
||||
void spx_ifft(void *table, spx_word16_t *in, spx_word16_t *out) |
||||
{ |
||||
struct kiss_config *t = (struct kiss_config *)table; |
||||
kiss_fftri2(t->backward, in, out); |
||||
} |
||||
|
||||
|
||||
#else |
||||
|
||||
#error No other FFT implemented |
||||
|
||||
#endif |
||||
|
||||
|
||||
#ifdef FIXED_POINT |
||||
/*#include "smallft.h"*/ |
||||
|
||||
|
||||
void spx_fft_float(void *table, float *in, float *out) |
||||
{ |
||||
int i; |
||||
#ifdef USE_SMALLFT |
||||
int N = ((struct drft_lookup *)table)->n; |
||||
#elif defined(USE_KISS_FFT) |
||||
int N = ((struct kiss_config *)table)->N; |
||||
#else |
||||
#endif |
||||
#ifdef VAR_ARRAYS |
||||
spx_word16_t _in[N]; |
||||
spx_word16_t _out[N]; |
||||
#else |
||||
spx_word16_t _in[MAX_FFT_SIZE]; |
||||
spx_word16_t _out[MAX_FFT_SIZE]; |
||||
#endif |
||||
for (i=0;i<N;i++) |
||||
_in[i] = (int)floor(.5+in[i]); |
||||
spx_fft(table, _in, _out); |
||||
for (i=0;i<N;i++) |
||||
out[i] = _out[i]; |
||||
#if 0 |
||||
if (!fixed_point) |
||||
{ |
||||
float scale; |
||||
struct drft_lookup t; |
||||
spx_drft_init(&t, ((struct kiss_config *)table)->N); |
||||
scale = 1./((struct kiss_config *)table)->N; |
||||
for (i=0;i<((struct kiss_config *)table)->N;i++) |
||||
out[i] = scale*in[i]; |
||||
spx_drft_forward(&t, out); |
||||
spx_drft_clear(&t); |
||||
} |
||||
#endif |
||||
} |
||||
|
||||
void spx_ifft_float(void *table, float *in, float *out) |
||||
{ |
||||
int i; |
||||
#ifdef USE_SMALLFT |
||||
int N = ((struct drft_lookup *)table)->n; |
||||
#elif defined(USE_KISS_FFT) |
||||
int N = ((struct kiss_config *)table)->N; |
||||
#else |
||||
#endif |
||||
#ifdef VAR_ARRAYS |
||||
spx_word16_t _in[N]; |
||||
spx_word16_t _out[N]; |
||||
#else |
||||
spx_word16_t _in[MAX_FFT_SIZE]; |
||||
spx_word16_t _out[MAX_FFT_SIZE]; |
||||
#endif |
||||
for (i=0;i<N;i++) |
||||
_in[i] = (int)floor(.5+in[i]); |
||||
spx_ifft(table, _in, _out); |
||||
for (i=0;i<N;i++) |
||||
out[i] = _out[i]; |
||||
#if 0 |
||||
if (!fixed_point) |
||||
{ |
||||
int i; |
||||
struct drft_lookup t; |
||||
spx_drft_init(&t, ((struct kiss_config *)table)->N); |
||||
for (i=0;i<((struct kiss_config *)table)->N;i++) |
||||
out[i] = in[i]; |
||||
spx_drft_backward(&t, out); |
||||
spx_drft_clear(&t); |
||||
} |
||||
#endif |
||||
} |
||||
|
||||
#else |
||||
|
||||
void spx_fft_float(void *table, float *in, float *out) |
||||
{ |
||||
spx_fft(table, in, out); |
||||
} |
||||
void spx_ifft_float(void *table, float *in, float *out) |
||||
{ |
||||
spx_ifft(table, in, out); |
||||
} |
||||
|
||||
#endif |
||||
@ -1,58 +0,0 @@
|
||||
/* Copyright (C) 2005 Jean-Marc Valin
|
||||
File: fftwrap.h |
||||
|
||||
Wrapper for various FFTs |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
- Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
|
||||
- Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
- Neither the name of the Xiph.org Foundation nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR |
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
*/ |
||||
|
||||
#ifndef FFTWRAP_H |
||||
#define FFTWRAP_H |
||||
|
||||
#include "arch.h" |
||||
|
||||
/** Compute tables for an FFT */ |
||||
void *spx_fft_init(int size); |
||||
|
||||
/** Destroy tables for an FFT */ |
||||
void spx_fft_destroy(void *table); |
||||
|
||||
/** Forward (real to half-complex) transform */ |
||||
void spx_fft(void *table, spx_word16_t *in, spx_word16_t *out); |
||||
|
||||
/** Backward (half-complex to real) transform */ |
||||
void spx_ifft(void *table, spx_word16_t *in, spx_word16_t *out); |
||||
|
||||
/** Forward (real to half-complex) transform of float data */ |
||||
void spx_fft_float(void *table, float *in, float *out); |
||||
|
||||
/** Backward (half-complex to real) transform of float data */ |
||||
void spx_ifft_float(void *table, float *in, float *out); |
||||
|
||||
#endif |
||||
@ -1,170 +0,0 @@
|
||||
/* Copyright (C) Jean-Marc Valin */ |
||||
/**
|
||||
@file speex_echo.h |
||||
@brief Echo cancellation |
||||
*/ |
||||
/*
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are |
||||
met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
3. The name of the author may not be used to endorse or promote products |
||||
derived from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||
DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, |
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef SPEEX_ECHO_H |
||||
#define SPEEX_ECHO_H |
||||
/** @defgroup SpeexEchoState SpeexEchoState: Acoustic echo canceller
|
||||
* This is the acoustic echo canceller module. |
||||
* @{ |
||||
*/ |
||||
#include "speexdsp_types.h" |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/** Obtain frame size used by the AEC */ |
||||
#define SPEEX_ECHO_GET_FRAME_SIZE 3 |
||||
|
||||
/** Set sampling rate */ |
||||
#define SPEEX_ECHO_SET_SAMPLING_RATE 24 |
||||
/** Get sampling rate */ |
||||
#define SPEEX_ECHO_GET_SAMPLING_RATE 25 |
||||
|
||||
/* Can't set window sizes */ |
||||
/** Get size of impulse response (int32) */ |
||||
#define SPEEX_ECHO_GET_IMPULSE_RESPONSE_SIZE 27 |
||||
|
||||
/* Can't set window content */ |
||||
/** Get impulse response (int32[]) */ |
||||
#define SPEEX_ECHO_GET_IMPULSE_RESPONSE 29 |
||||
|
||||
/** Internal echo canceller state. Should never be accessed directly. */ |
||||
struct SpeexEchoState_; |
||||
|
||||
/** @class SpeexEchoState
|
||||
* This holds the state of the echo canceller. You need one per channel. |
||||
*/ |
||||
|
||||
/** Internal echo canceller state. Should never be accessed directly. */ |
||||
typedef struct SpeexEchoState_ SpeexEchoState; |
||||
|
||||
/** Creates a new echo canceller state
|
||||
* @param frame_size Number of samples to process at one time (should correspond to 10-20 ms) |
||||
* @param filter_length Number of samples of echo to cancel (should generally correspond to 100-500 ms) |
||||
* @return Newly-created echo canceller state |
||||
*/ |
||||
SpeexEchoState *speex_echo_state_init(int frame_size, int filter_length); |
||||
|
||||
/** Creates a new multi-channel echo canceller state
|
||||
* @param frame_size Number of samples to process at one time (should correspond to 10-20 ms) |
||||
* @param filter_length Number of samples of echo to cancel (should generally correspond to 100-500 ms) |
||||
* @param nb_mic Number of microphone channels |
||||
* @param nb_speakers Number of speaker channels |
||||
* @return Newly-created echo canceller state |
||||
*/ |
||||
SpeexEchoState *speex_echo_state_init_mc(int frame_size, int filter_length, int nb_mic, int nb_speakers); |
||||
|
||||
/** Destroys an echo canceller state
|
||||
* @param st Echo canceller state |
||||
*/ |
||||
void speex_echo_state_destroy(SpeexEchoState *st); |
||||
|
||||
/** Performs echo cancellation a frame, based on the audio sent to the speaker (no delay is added
|
||||
* to playback in this form) |
||||
* |
||||
* @param st Echo canceller state |
||||
* @param rec Signal from the microphone (near end + far end echo) |
||||
* @param play Signal played to the speaker (received from far end) |
||||
* @param out Returns near-end signal with echo removed |
||||
*/ |
||||
void speex_echo_cancellation(SpeexEchoState *st, const spx_int16_t *rec, const spx_int16_t *play, spx_int16_t *out); |
||||
|
||||
/** Performs echo cancellation a frame (deprecated) */ |
||||
void speex_echo_cancel(SpeexEchoState *st, const spx_int16_t *rec, const spx_int16_t *play, spx_int16_t *out, spx_int32_t *Yout); |
||||
|
||||
/** Perform echo cancellation using internal playback buffer, which is delayed by two frames
|
||||
* to account for the delay introduced by most soundcards (but it could be off!) |
||||
* @param st Echo canceller state |
||||
* @param rec Signal from the microphone (near end + far end echo) |
||||
* @param out Returns near-end signal with echo removed |
||||
*/ |
||||
void speex_echo_capture(SpeexEchoState *st, const spx_int16_t *rec, spx_int16_t *out); |
||||
|
||||
/** Let the echo canceller know that a frame was just queued to the soundcard
|
||||
* @param st Echo canceller state |
||||
* @param play Signal played to the speaker (received from far end) |
||||
*/ |
||||
void speex_echo_playback(SpeexEchoState *st, const spx_int16_t *play); |
||||
|
||||
/** Reset the echo canceller to its original state
|
||||
* @param st Echo canceller state |
||||
*/ |
||||
void speex_echo_state_reset(SpeexEchoState *st); |
||||
|
||||
/** Used like the ioctl function to control the echo canceller parameters
|
||||
* |
||||
* @param st Echo canceller state |
||||
* @param request ioctl-type request (one of the SPEEX_ECHO_* macros) |
||||
* @param ptr Data exchanged to-from function |
||||
* @return 0 if no error, -1 if request in unknown |
||||
*/ |
||||
int speex_echo_ctl(SpeexEchoState *st, int request, void *ptr); |
||||
|
||||
|
||||
|
||||
struct SpeexDecorrState_; |
||||
|
||||
typedef struct SpeexDecorrState_ SpeexDecorrState; |
||||
|
||||
|
||||
/** Create a state for the channel decorrelation algorithm
|
||||
This is useful for multi-channel echo cancellation only |
||||
* @param rate Sampling rate |
||||
* @param channels Number of channels (it's a bit pointless if you don't have at least 2) |
||||
* @param frame_size Size of the frame to process at ones (counting samples *per* channel) |
||||
*/ |
||||
SpeexDecorrState *speex_decorrelate_new(int rate, int channels, int frame_size); |
||||
|
||||
/** Remove correlation between the channels by modifying the phase and possibly
|
||||
adding noise in a way that is not (or little) perceptible. |
||||
* @param st Decorrelator state |
||||
* @param in Input audio in interleaved format |
||||
* @param out Result of the decorrelation (out *may* alias in) |
||||
* @param strength How much alteration of the audio to apply from 0 to 100. |
||||
*/ |
||||
void speex_decorrelate(SpeexDecorrState *st, const spx_int16_t *in, spx_int16_t *out, int strength); |
||||
|
||||
/** Destroy a Decorrelation state
|
||||
* @param st State to destroy |
||||
*/ |
||||
void speex_decorrelate_destroy(SpeexDecorrState *st); |
||||
|
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
|
||||
/** @}*/ |
||||
#endif |
||||
@ -1,14 +0,0 @@
|
||||
/* speexdsp_config_types.h — статически сгенерировано для POSIX (Linux/FreeBSD/Android).
|
||||
* В оригинале генерируется configure из speexdsp_config_types.h.in. На Windows ветка |
||||
* _WIN32 в speexdsp_types.h использует нативные типы и этот файл не инклудится. */ |
||||
#ifndef __SPEEX_TYPES_H__ |
||||
#define __SPEEX_TYPES_H__ |
||||
|
||||
#include <stdint.h> |
||||
|
||||
typedef int16_t spx_int16_t; |
||||
typedef uint16_t spx_uint16_t; |
||||
typedef int32_t spx_int32_t; |
||||
typedef uint32_t spx_uint32_t; |
||||
|
||||
#endif |
||||
@ -1,126 +0,0 @@
|
||||
/* speexdsp_types.h taken from libogg */ |
||||
/********************************************************************
|
||||
* * |
||||
* THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * |
||||
* USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * |
||||
* GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * |
||||
* IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * |
||||
* * |
||||
* THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002 * |
||||
* by the Xiph.Org Foundation http://www.xiph.org/ *
|
||||
* * |
||||
******************************************************************** |
||||
|
||||
function: #ifdef jail to whip a few platforms into the UNIX ideal. |
||||
last mod: $Id: os_types.h 7524 2004-08-11 04:20:36Z conrad $ |
||||
|
||||
********************************************************************/ |
||||
/**
|
||||
@file speexdsp_types.h |
||||
@brief Speex types |
||||
*/ |
||||
#ifndef _SPEEX_TYPES_H |
||||
#define _SPEEX_TYPES_H |
||||
|
||||
#if defined(_WIN32) |
||||
|
||||
# if defined(__CYGWIN__) |
||||
# include <_G_config.h> |
||||
typedef _G_int32_t spx_int32_t; |
||||
typedef _G_uint32_t spx_uint32_t; |
||||
typedef _G_int16_t spx_int16_t; |
||||
typedef _G_uint16_t spx_uint16_t; |
||||
# elif defined(__MINGW32__) |
||||
typedef short spx_int16_t; |
||||
typedef unsigned short spx_uint16_t; |
||||
typedef int spx_int32_t; |
||||
typedef unsigned int spx_uint32_t; |
||||
# elif defined(__MWERKS__) |
||||
typedef int spx_int32_t; |
||||
typedef unsigned int spx_uint32_t; |
||||
typedef short spx_int16_t; |
||||
typedef unsigned short spx_uint16_t; |
||||
# else |
||||
/* MSVC/Borland */ |
||||
typedef __int32 spx_int32_t; |
||||
typedef unsigned __int32 spx_uint32_t; |
||||
typedef __int16 spx_int16_t; |
||||
typedef unsigned __int16 spx_uint16_t; |
||||
# endif |
||||
|
||||
#elif defined(__MACOS__) |
||||
|
||||
# include <sys/types.h> |
||||
typedef SInt16 spx_int16_t; |
||||
typedef UInt16 spx_uint16_t; |
||||
typedef SInt32 spx_int32_t; |
||||
typedef UInt32 spx_uint32_t; |
||||
|
||||
#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */ |
||||
|
||||
# include <sys/types.h> |
||||
typedef int16_t spx_int16_t; |
||||
typedef u_int16_t spx_uint16_t; |
||||
typedef int32_t spx_int32_t; |
||||
typedef u_int32_t spx_uint32_t; |
||||
|
||||
#elif defined(__BEOS__) |
||||
|
||||
/* Be */ |
||||
# include <inttypes.h> |
||||
typedef int16_t spx_int16_t; |
||||
typedef u_int16_t spx_uint16_t; |
||||
typedef int32_t spx_int32_t; |
||||
typedef u_int32_t spx_uint32_t; |
||||
|
||||
#elif defined (__EMX__) |
||||
|
||||
/* OS/2 GCC */ |
||||
typedef short spx_int16_t; |
||||
typedef unsigned short spx_uint16_t; |
||||
typedef int spx_int32_t; |
||||
typedef unsigned int spx_uint32_t; |
||||
|
||||
#elif defined (DJGPP) |
||||
|
||||
/* DJGPP */ |
||||
typedef short spx_int16_t; |
||||
typedef int spx_int32_t; |
||||
typedef unsigned int spx_uint32_t; |
||||
|
||||
#elif defined(R5900) |
||||
|
||||
/* PS2 EE */ |
||||
typedef int spx_int32_t; |
||||
typedef unsigned spx_uint32_t; |
||||
typedef short spx_int16_t; |
||||
|
||||
#elif defined(__SYMBIAN32__) |
||||
|
||||
/* Symbian GCC */ |
||||
typedef signed short spx_int16_t; |
||||
typedef unsigned short spx_uint16_t; |
||||
typedef signed int spx_int32_t; |
||||
typedef unsigned int spx_uint32_t; |
||||
|
||||
#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) |
||||
|
||||
typedef short spx_int16_t; |
||||
typedef unsigned short spx_uint16_t; |
||||
typedef long spx_int32_t; |
||||
typedef unsigned long spx_uint32_t; |
||||
|
||||
#elif defined(CONFIG_TI_C6X) |
||||
|
||||
typedef short spx_int16_t; |
||||
typedef unsigned short spx_uint16_t; |
||||
typedef int spx_int32_t; |
||||
typedef unsigned int spx_uint32_t; |
||||
|
||||
#else |
||||
|
||||
#include "speexdsp_config_types.h" |
||||
|
||||
#endif |
||||
|
||||
#endif /* _SPEEX_TYPES_H */ |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue