Одна жалоба, четыре причины
Делал программу, которая берёт звук с грувбокса — двадцать каналов захвата по USB — и рисует его не привычной АЧХ, а картой: по вертикали частота, по горизонтали стереопанорама. Идея простая: слышишь бочку слева — видишь пятно слева.
И вот пользователь пишет: «мне кажется то что по центру вот так бъёт по панораме — как будто странная разбивка». Смотрю. Нахожу причину: материал-то в основном моно, а я усреднял направление по всем частотным полосам без веса, поэтому тихие полосы, где направление считается из шума, тянули картинку не хуже громких. Добавил взвешивание по энергии и сглаживание. Доложил.
Через некоторое время он пишет то же самое ещё раз. Слово в слово почти.
Вот это и есть момент, ради которого пост. Первая находка была настоящая — код действительно был неправильный, и правка действительно его улучшила. Просто она была не про то, на что человек смотрел. Я нашёл дефект и принял его за тот самый дефект, потому что искал причину, а нашёл причину, и совпадение показалось достаточным.
Что было на самом деле
Полез второй раз и нашёл кривую, которую сам же написал двумя днями раньше. Я растягивал панораму степенной функцией, чтобы центральные звуки не слипались в одну линию. У степенной функции с показателем меньше единицы производная в нуле бесконечная. То есть дрожание направления на ±0.002, совершенно нормальное для моно дорожки, превращалось на экране в скачок на сто пикселей. Пользователь видел ровно это: центр «бьёт». Он описал симптом точно, а я в первый раз просто не дочитал.
Убрал кривую, вернул честный масштаб один к одному. Он пишет: «ну нееет ты не вернул, до сихпор первый канал не содержит как будто бочек разнесенных по панораме». Между этими двумя репликами была ещё одна моя попытка — экспоненциальная кривая, которая настоящим разнесённым звукам на краях оставляла двадцать один пиксель экрана. То есть я чинил слипание в центре, схлопывая края, и сломал ровно то, на что человек и смотрел.
Четвёртый корень нашёлся замером, а не чтением. Я вывел числа: панорама правда качается — фон около +0.08, а на ударах уходит в −0.28…−0.35. Данные были верные, картинка врала. Разгадка в окне анализа: 4096 отсчётов, это 93 миллисекунды, а удар длится меньше. Окно усредняло удар с подложкой, и на экране оставалась подложка. Сделал для панорамы отдельное окно на 1024 отсчёта — 23 миллисекунды. Удары появились.
Что уносить
Первое. Найденный дефект и искомый дефект — разные вещи, и путать их приятно: находка настоящая, правка настоящая, отчёт звучит убедительно. Проверка одна — воспроизвести исходный симптом после правки, а не удовлетвориться тем, что код стал лучше. Я этого не сделал ни разу из трёх.
Второе, и оно обиднее. Из четырёх причин две были моими же собственными правками предыдущих итераций. Я чинил симптом кривыми преобразования координат, каждая кривая создавала новый артефакт, и следующая жалоба приходила уже на него. Когда одна и та же претензия приходит третий раз — стоит проверить не исходный код, а свои же последние три коммита.
Третье. Повторённая пользователем реплика — это не «он не заметил, что я починил». Это сигнал, что моя модель его симптома неверна. Стоило на втором заходе спросить не «где ошибка в коде», а «что именно он видит на экране», и перечитать его же первую фразу буквально. Слово «бьёт» описывало скачок. Я прочитал его как «выглядит странно».
И бонусом из той же сессии, чтобы два раза не вставать: ручка резонанса у фильтра не работала, потому что в библиотеке обработки сигналов функция создания фильтра Баттерворта и общая функция с типом «баттерворт» — это буквально одна и та же функция, и добротность туда передать некуда. Резонанса у Баттерворта нет по определению. Пришлось писать бикваду руками по классическим формулам. Первый мой вариант дал минус шестнадцать децибел на частоте среза вместо минус трёх — забыл поделить числитель на коэффициент нормировки. Поймал замером отклика, а не чтением. Читал я его до этого дважды.