Дорисовал

Таки доделал програмку на Питоне, рисующую спектр сигнала и автоматически считающую КНИ+шум и ОСШ. Попутно узнал, как водится, много нового. За что люблю Питон — так это за то, что программа занимает менее 40 строк. На тех же Сях я бы усрался это рисовать. Даже на Шарпах бы усрался.

Программе скармливается звуковой файл с сигналом частотой в 1 kHz, сгенерированный программой Adobe Audition (в девичестве Syntrillium CoolEdit). Но можно взять и бесплатный Audacity, результат будет точно такой же. Программа читает файл, берёт значение с наибольшим пиком и даёт ему обозначение в 0 децибел. Остальное, соответственно, отрицательные величины. Подсчитывается среднеквадратичное значение всего, что не сигнал, и делится на уровень сигнала. Получается КНИ+шум (THD+N). Потом считаем ОСШ (отношение сигнал/шум, SNR) в децибелах: 20log10(сигнал / шум)

Вот так выглядит анализ звукового файла с сигналом 1 kHz, разрешением 16-бит, частота дискретизации — 48 kHz:

Это весьма близко к теоретическому идеалу — в идеале, разрешение 16 бит может дать ОСШ в 96.3 dB. Но у меня не идеал, так как я использую чуть менее, чем 16 бит — ибо если генерировать синусоиду с уровнем в 0 dB (т.е. по-максимуму), то почему-то уже лезут нелинейные искажения. Так что я создаю её с уровнем в -0.1 dB, минимальным отступлением от максимума, которое мне даёт делать Audition. В любом случае, 94 dB — это дохрена.

КНИ в 2 тысячных процента это тоже прекрасно. Без приборов этого никто никогда не увидит, искажения начинают быть слышимыми, когда уже вплотную приближаются к 1%, хотя это сильно зависит от того, что именно слушаем: если чистые синусоиды, то искажения начинают быть заметными гораздо раньше, а если в качестве тестового материала брать альбомы фифтисентов и прочих, то там можно и 10% искажений не услышать. Что не означает, что аппаратура, дающая КНИ в 0.05%, ничем не лучше аппаратуры, дающей 0.1% — она лучше; просто в реальности ушами этого ни один живой человек не услышит.

А теперь — снова пнём формат MP3 🙂 Никто как-то вот не задумывается о том, что они слушают в тысячедолларовых деревянных наушниках, подключённых к внешним усилителям класса А за семьсот долларов, обещающим КНИ в 0.00045%

А между тем это реалии MP3 с битрейтом в 192 килобита/сек:

А это — 320 килобит/сек:

Получше, конечно, чем 192 kbps, но всё равно проседание качества очень налицо — происходит серьёзное ужимание динамического диапазона (я в курсе, что ДД и ОСШ это не вполне одно и то же, но они связаны). На некотором материале (например, классическая музыка, обладающая большим динамическим диапазоном) это может быть очень заметно. На 192 килобитах так это точно заметно, тихая партия скрипки сопровождается скрежетом артефактов сжатия с потерями — собственноушно, так сказать, слышал. Дальнейшее увеличение битрейта после 320 килобит/с, кстати, уже ничего не даёт — ОСШ так и остаётся в районе 55 децибел.

Ещё надо будет попинать винилофильство и прочее плёнколожество, но это в другой раз 🙂

А что-то в этом есть

По совету ув. ny-quant попробовал использовать формулу x = r * x (1 – x) в качестве генератора случайных величин. Одного преобразования мне показалось мало, так что делал два кряду.

“В домашних условиях” проверить, качественная ли случайность не очень просто, надо вспоминать основы статистики и правильное применения хи-квадрата. Но есть неплохой способ — представить полученные значения в виде изображения. Если картинка выглядит шумом, то шансы на то, что значения действительно случайны, неплохи. Человеческий глаз очень неплохо натренирован на то, чтобы различать неслучайные узоры — миллиарды лет эволюции, чтобы издалека узнавать хищников или отличать ядовитых змей, даром не проходят.

Ну, как грица, pics or it didn’t happen.

Результат использования функции NumPy.random.random():

И результаты, полученные из двойного применения x = r * x (1 – x) с двумя разными значениями r:

Вообще — неплохо, должен признать.

Но если приблизить и рассмотреть детальнее, становится заметной разница. Справа — x = r * x (1 – x), слева — NumPy.random.random()

Как видно, x = r * x (1 – x) чаще принимает граничные значения, там много белого и много чёрного, и мало серого. Из-за этого, кажется, что есть узоры, как на булатной стали. Я не математик, но мне кажется, что большая равномерность является желательной.

Но вообще — для такой простой формулы должен признать, впечатляет.

Отрицательная частота

Всё же как много я не знаю и не понимаю. Последний месяц-два изучаю методы измерения параметров качественности усиления звукового (и не только) сигнала.

Методов есть несколько, но про это в другой раз.

Основа в том, что чистый сигнал определённой частоты (например, 1 КГц) представляет собой синусоиду. Всё, что немного не синусоида, уже есть не чистый сигнал, а сумма сигналов, сумма синусоид. Даже меандр можно описать синусоидами — как функцию y(x) = sin(x) + sin (3x) / 3 + sin (5x) / 5 + .. + sin (nx) / n. Потому что меандр — это сумма синусоид основного сигнала и синусоид нечётных гармоник — то-есть, сигналов с частотой в 3, 5, 7 и так далее до бесконечности раз выше основной. Просто если речь идёт о звуковом сигнале, гармоники выше 19й слышны (как считается) уже не будут, так что меандр там получается не совсем полный, приблизительный.

Так вот если есть источник звукового сигнала, как узнать, насколько чистый там тон? Можно записать это в обыкновенный wav файл, а потом провести над полученным массивом данных математическое издевательство, называемое преобразованием Фурье. Пакетов для этого существует масса, можно взять бесплатный редактор Audacity, в нём есть спектральный анализ (преобразование Фурье это оно и есть).

Но, во-первых, я не ищу лёгкого пути, а во-вторых, мерять гармоники, долго их складывать и вставлять в формулу для расчёта КНИ я не хочу. Нехай электронный болван всё за меня считает, он на то и был куплен.

Так что нарисовал свою программу. Разумеется, на Питоне (на чём же ещё, не на Сях же рисовать). Благо есть модуль SciPy, в котором уже всё придумано, в том числе алгоритм быстрого преобразования Фурье (сиречь FFT).

Алгоритм FFT выдаёт гистограмму. По горизонтали — частота сигнала, по вертикали — его громкость. Примерно так слышит музыку человеческое ухо. Так вот выяснилось, что по умолчанию алгоритм выдаёт симметричную вокруг нуля герц картинку, то-есть, есть как положительная частота, так и отрицательная %) На этом месте я залип — как это, минус один килогерц?

Анализ файла с синусоидой 1 КГц выглядит так:

В принципе оно ведь логично — у синусоиды одна половинка имеет положительные значения от 0 до 1 (в военное время — до 4 =)), а другая — отрицательные, от 0 до -1. Соответственно, положительная частота — для того, что выше нуля, а отрицательная частота — для того, что ниже нуля. Правда, не совсем понятно, почему именно вот так — не было бы логичнее делать положительную и отрицательную амплитуды (громкости)?

Ещё менее понятным стало, когда я силой сгенерировал синусоиду с отрезанной верхушкой и сунул её в анализатор. Вот такую:

Я ожидал, что анализатор увидит туеву хучу гармоник с положительными частотами, и чистейший тон с отрицательными. Ан фиг — одни и те же гармоники были и слева и справа от нуля. Совсем непонятно, почему так. Вернее, конечно, понятно — сигнал это ВСЯ синусоида, от нуля до двух пи радиан, а не только её часть. Но тогда уже непонятно, зачем вообще городить огород с отрицательными и положительными частотами — не всё ли равно?

Плюнул, сменил алгоритм scipy.fftpack.fft на scipy.fftpack.rfft. rfft — это real fft, и отрицательных частот не выдаёт. Так намного понятнее.

Продолжаю изучать.

Про изучение языков программирования

Да, именно так оно и работает.

Именно поэтому когда знакомые меня спрашивают, с какого языка начать знакомство с программированием, я никогда не советую Джаваскрипт, Перл, Питон и прочие языки с динамической типизацией. Совсем не потому, что они “хуже”, а потому что понять, что происходит, при отсутствии понимания типов переменных, невозможно.

Девочка пытается совершить арифметические действия с переменной типа char — действие, не вполне стандартное, так что компилятор действует “по наитию” — так что char в зависимости от контекста становится числом или строковой переменной.

При этом на низком уровне процессору глубоко до лампочки, что с чем складывать — он вполне может сложить яблоки с грушами, а финики с морковкой — по сути ведь, не всё ли равно, какие единички и нолики складывать? Ну, а если при этом возникнут неожиданные эффекты — ну это уже проблема программиста. На ассемблере ведь даже типов переменных нет. Да, надо указывать, сколько бит резервировать, но и всё тут. А уж как это интерпретировать — это вопрос к более высокому уровню абстракции.

Проблемы с интерпретацией никуда не исчезают, но если мы попробуем повторить девочкины действия на том же C# или на Джаве, в рыло прилетит привет от компилятора — “ты чо с чем складываешь, совсем охренел, что ли?” И предложит cast в другой тип данных. Поэтому подобных необъяснимых эффектов там просто не будет, а у программиста вырабатывается привычка руками кастовать переменные при любых неявных действиях с разными типами.

Сам я, кстати, пошёл сначала по неправильному пути, начав изучать программирование с одного из самых запутанных языков — с Перла. Ну, того самого, по которому даже проводилось соревнование “Кто непонятнее напишет” (https://en.wikipedia.org/wiki/Obfuscated_Perl_Contest). Не то, чтобы Перловый код обязательно получается кривой и непонятный, но там очень просто сделать именно так. Для начинающего — не то.