Звук на бумаге: новая программа

 

Вступление

Лист A4 держит две минуты звука, и программа читает их обратно со скана. Никакого шифрования там нет: на бумаге просто нарисована звуковая волна.

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

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

Идею гуляющей линии я подсмотрел у Патрика Фистера (Patrick Feaster), который оживляет фонавтограммы и другие старинные картинки звуковых волн. В 2016 году он выложил свою программу Picture Kymophone для GNU Octave: она идёт по картинке столбец за столбцом, находит «центр яркости» светлой линии и превращает её положение в отсчёт звука. Этим проектом я в своё время и вдохновлялся, только у Фистера картинка — найденная старая запись, а у меня — лист, который я сам печатаю, чтобы потом прочитать.

Проект лежит на GitHub: Lumenman/paper-sound. Писали мы его вдвоём с Claude. Я ставил опыты, печатал и сканировал, он писал код и вёл лабораторный журнал. Журнал разросся до четырёх тысяч строк, и почти в каждой записи цифра в децибелах.

Как устроен лист

Правило одно: одна полоса — одна секунда, одна строка пикселей — один отсчёт. Лист печатается при 600 dpi, высота печатной области A4 — 6780 пикселей, значит и частота дискретизации 6780 Гц. В ширину влезает 120 полос, то есть 120 секунд. Не Хай-Фай, телефонное качество, но речь и мелодию разобрать можно.

Это правило по умолчанию, а не закон. Секунду можно уложить в меньше строк: на лист влезет больше, но звук станет глуше. А можно растянуть на большее число строк: --rows 2 отдаёт каждому отсчёту две строки, то есть секунда занимает две полосы. Качество растёт, а ёмкость листа падает вдвое (цифры ниже).

Внутри каждой полосы идёт штрих постоянной ширины и гуляет влево-вправо вместе со звуком. При чтении программа смотрит не на то, сколько чернил, а на то, где они лежат (центр тяжести штриха). В этом и вся разница с дорожкой переменной площади: там принтер напечатал чуть бледнее — и звук уже другой, а здесь плотность печати и экспозиция сканера на ответ почти не влияют. На листах, напечатанных из одного звука, гуляющая линия оказалась чище полос на 3,7 дБ.

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

По краям стоят две служебные полосы — «часы», ровная синусоида известной частоты. Каретка сканера едет неравномерно, и по часам программа выравнивает темп каждой строки. Часов двое и они разной частоты, поэтому заодно видно, какой стороной лист лёг на стекло. Положил вверх ногами — программа сама перевернёт.

Работа выглядит так:

python paper-sound/paper_sound.py print song.wav -o sheet
python paper-sound/paper_sound.py read sheet_01.png -o back.wav

Первая команда делает из wav картинки для печати, вторая читает скан обратно в wav. Нужны Python, numpy и pillow. Есть ещё holds — посчитать, сколько листов уйдёт на запись, до того как печатать.

Печать и сканирование

Главное правило: в сканере выключить все улучшалки. Автоконтраст, уровни, точка белого, резкость, выравнивание, чёрно-белый режим — всё выключено. Печатать в масштабе 100%, без «подогнать под страницу», сканировать тоже при 600 dpi.

Почему так строго. Штрих шириной 4 пикселя, и вся точность сидит в двух серых пикселях по его краям. Стоит в фотошопе поставить точку белого — и эти пиксели становятся просто белыми. Измерили: минус 8,6 дБ, и вернуть их уже ничем нельзя. С резкостью то же самое: два скана с одного моего сканера прочитались очень плохо, потому что сканер сам, молча, их «улучшал».

Теперь программа такое замечает сама. Чернила на бумаге всегда расплываются, штрих в 4 пикселя приходит со скана шириной 4,3–4,6. Если он вдруг стал уже (у меня было 3,2), значит кто-то включил резкость, и read прямо об этом пишет. По дыркам в гистограмме он же замечает, что скан правили уровнями.

А вот криво положить лист теперь не страшно. В прошлой статье я жаловался на «небольшую проблемку с точностью разрезки», теперь наклон меряется по краю чернил. Синтетический лист, скошенный на 16 градусов, читается идеально. Раньше кончится стекло: A4 на сканере формата A4 перестаёт помещаться уже при 1,2 градуса.

Ручек у чтения почти нет, и это намеренно. Мы перепробовали кучу фильтров, и почти все оказались бесполезны — сдвигали результат в третьем знаке. Остались две:

  • --aperture — сколько пикселей вокруг штриха смотреть. На грязном скане стоит попробовать --aperture 3, на чистом не трогать.
  • --despeckle — для листа, по которому топтались. На чистом листе он только вредит, на запачканном — единственное, что помогает.

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

Что получилось

С бумаги возвращается 12–15 дБ, а формат сам по себе держит 32,6. Честно скажу, до качества исходника далеко, но на слух всё разбирается. Все цифры ниже — сравнение прочитанного с исходным файлом (bench.py).

Лист

Результат, дБ

Не покидал компьютера (картинка сразу в чтение)

32,6

Напечатан и отсканирован

11,8–12,3

То же с --rows 2 (две строки на отсчёт, половина емкости)

до 14,9

Куда деваются двадцать децибел. Самое большое — размытие вдоль листа: принтер и сканер вместе размазывают каждую строку на соседние, а строки здесь — это время. Это около 17 дБ, и программой их не вернуть. Обойти можно только натурой: --rows 2 даёт +2,1 дБ ценой половины листа. Потому же не работает и задирание верхов перед печатью: первые пару децибел оно даёт, дальше линия просто не успевает за звуком.

Что программа уже отвоёвывает: часы — от 1,4 до 4,9 дБ, поправка на наклон — от 0,1 до 2,3 дБ. Разброс большой, потому что зависит от листа: один лёг криво, но каретка шла ровно, другой наоборот. Так что это скорее страховка, чем выигрыш.

И ещё одна находка: во всех чтениях сидел писк на 823 Гц. Оказалось, это мой сканер — у него полосы с периодом ровно 8 строк. На другом сканере будет свой писк ))

Вместо заключения

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

Что осталось открытым:

  • размытие вдоль листа — самая большая потеря, и против неё пока помогает только лишняя бумага;
  • писк сканера, который чтение пока не давит;
  • на сканере поточнее цифры могут быть другими — буду рад, если кто-нибудь проверит и напишет.

Подробное руководство по печати и сканированию лежит в репозитории (GUIDE.ru.md), а все цифры и тупики — в лабораторном журнале (LAB.ru.md). Пример звука до и после бумаги: ... попробуйте сами :)

На этом всё.

Комментарии

Популярные сообщения из этого блога

Поиграем на микрокалькуляторе

Математика Люшера

Погода по барометру