Алгоритм микширования звука

У меня есть два необработанных звуковых потока, которые мне нужно сложить. Для целей этого вопроса мы можем предположить, что они имеют одинаковый битрейт и битовую глубину (скажем, 16-битная выборка, частота дискретизации 44,1 кГц).

Очевидно, что если я просто сложу их вместе, я переполню и опустошу свое 16-битное пространство. Если я сложу их вместе и разделю на два, то громкость каждого уменьшится вдвое, что не правильно с точки зрения звука - если два человека говорят в комнате, их голоса не становятся тише наполовину, и микрофон может их уловить. оба вверх, не задев ограничитель.

  • Так как же правильно сложить эти звуки в моем программном микшере?
  • Я ошибаюсь и правильный метод - уменьшить громкость каждого наполовину?
  • Нужно ли мне добавлять компрессор / лимитер или какой-либо другой этап обработки, чтобы получить нужный объем и эффект микширования?

-Адам

Тот же вопрос, но более точные ответы: dsp.stackexchange.com/questions/3581/…

sethcall 25.10.2013 01:39

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

Alba Mendez 31.05.2014 00:56

@jmendeth Отмена фазы реальна. Поместите два динамика рядом друг с другом и поменяйте фазу с одного (поменяйте местами провода). Ваш бас испорчен. Причина, по которой вы не получаете полной отмены, заключается в том, что ваши динамики не являются точечными источниками и у вас два уха.

Roddy 31.05.2014 01:11

Я знаю, я знаю ... тем не менее, когда люди слышат «микширование звука», они не ожидают, что два звука нейтрализуют друг друга в зависимости от фазы, что приведет к тишине.

Alba Mendez 31.05.2014 11:44

И я не хочу, чтобы у двух инструментов были отменены частоты в зависимости от "удачи", чтобы они были инвертированы по фазе.

Alba Mendez 31.05.2014 11:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
63
5
47 830
20
Перейти к ответу Данный вопрос помечен как решенный

Ответы 20

Если вам нужно сделать это правильно, я бы посоветовал взглянуть на реализации программного микшера с открытым исходным кодом, по крайней мере, для теории.

Некоторые ссылки:

Мужество

GStreamer

На самом деле вам, вероятно, следует использовать библиотеку.

Audacity просто добавит сэмплы, в результате получится клип (если сэмплы будут высокими). Вы должны вручную отрегулировать усиление каждой дорожки, чтобы предотвратить клиппирование.

olafure 19.11.2011 22:09

Я бы сказал, просто сложите их вместе. Если вы переполняете свое 16-битное пространство PCM, то звуки, которые вы используете, уже для начала невероятно громкие, и вам следует их приглушить. Если это приведет к тому, что они сами по себе станут слишком мягкими, поищите другой способ увеличения общей выходной громкости, например настройку ОС или поворот ручки на динамиках.

Я думаю, что до тех пор, пока потоки не коррелированы, вам не о чем беспокоиться, вы сможете обойтись отсечением. Если вас действительно беспокоят искажения в точках отсечения, мягкий лимитер, вероятно, подойдет.

Ответ принят как подходящий

Вы должны сложить их вместе, но обрезать результат до допустимого диапазона, чтобы предотвратить переполнение / потерю значимости.

В случае клиппирования вы будут вносите искажение в звук, но это неизбежно. Вы можете использовать свой код отсечения, чтобы «обнаружить» это условие и сообщить об этом пользователю / оператору (эквивалент красного индикатора «клипа» на микшере ...)

Вы могли бы реализовать более «правильный» компрессор / ограничитель, но, не зная точного приложения, трудно сказать, стоит ли оно того.

Если вы выполняете много обработки звука, вы можете представить свои уровни звука в виде значений с плавающей запятой и вернуться к 16-битному пространству только в конце процесса. Так часто работают высококачественные цифровые аудиосистемы.

Это правильный ответ, но я приукрасил его некоторыми примечаниями о том, как реализовать автоматическое управление уровнем ниже (написано до того, как у меня были права на комментарии).

podperson 05.04.2012 21:26

@Kyberias Это не имеет смысла; первое предложение буквально объясняет, что делать.

user1881400 26.09.2014 08:40

OP уже, что предлагает этот ответ и каков недостаток для этого, из вопроса «Очевидно, что если я просто сложу их вместе, я переполну и опустошу мое 16-битное пространство». @ user1881400

Ratul Sharker 06.09.2019 10:30

Вы правы, складывая их вместе. Вы всегда можете сканировать сумму двух файлов на предмет пиковых точек и масштабировать весь файл вниз, если они достигают какого-то порогового значения (или если среднее значение этого файла и окружающих его пятен достигает порогового значения)

Я согласен с вами, но это не практично для звукового потока, потому что вы не можете посмотреть звук, может быть, подойдет оконная регулировка динамического усиления?

SuperLucky 14.06.2014 11:01

"Вдвое тише" не совсем правильно. Из-за логарифмического отклика уха разделение выборок пополам сделает его тише на 6 дБ - конечно, заметно, но не катастрофично.

Вы можете пойти на компромисс, умножив на 0,75. Это сделает его тише на 3 дБ, но уменьшит вероятность переполнения, а также уменьшит искажения, когда это произойдет.

На 3 дБ тише вдвое уменьшается мощность, поэтому значения выборки делятся на sqrt (2). Это умножение на 0,707 (1 / sqrt (2)), а не на 0,75. Я согласен с тем, что умножение на 0,75 легче добиться с помощью битового сдвига.

Gauthier 22.10.2012 16:56

@Gauthier, я был приблизительно.

Mark Ransom 22.10.2012 17:25

0,707 происходит от 10 ^ (- 3/20) и не имеет ничего общего с 1 / sqrt (2), кроме того, что десятичное расширение разделяет некоторые ведущие цифры (если вы хотите быть точными :)

Joris Weimar 28.10.2016 23:52

@JorisWeimar, он абсолютно прав в том, что уменьшение мощности вдвое потребует деления на квадратный корень из 2. Это принято называть -3 дБ, хотя технически это -3,0103 дБ. Опять приближения.

Mark Ransom 29.10.2016 18:45

@MarkRansom да, я спорил в вашу пользу. это приближение. но это не имеет ничего общего с sqrt (2).

Joris Weimar 30.10.2016 16:31

Но @JorisWeimar имеет отношение все к sqrt (2)! Это значение -3 дБ, которое является приближением к sqrt (2), а не наоборот - я думал, что ясно дал это понять. Мощность пропорциональна квадрату напряжения, поэтому для уменьшения мощности вдвое необходимо уменьшить напряжение (сигнал) на sqrt (2). Это полное совпадение, что это примерно -3 дБ по той же причине, что 2 ^ 10 (1024) очень близко к 10 ^ 3 (1000).

Mark Ransom 30.10.2016 17:50

мы говорим об уменьшении амплитуды с помощью коэффициента умножения. Я думал, что можно преобразовать между амплитудой и dbfs по формуле dbfs = 20 * log (амплитуда). или я ошибаюсь?

Joris Weimar 30.10.2016 22:19

@JorisWeimar db - это измерение соотношение, в случае dbfs это отношение полная амплитуда к рассматриваемому сигналу. Ваша формула будет абсолютно правильной, если вы примете это во внимание, с коэффициентом умножения. Вот как я получил цифру, которую цитировал выше: 20 * log(1/sqrt(2)) = -3.0103.

Mark Ransom 01.11.2016 05:30

Большинство приложений для микширования звука используют числа с плавающей запятой (32 бита достаточно для микширования небольшого количества потоков). Преобразуйте 16-битные отсчеты в числа с плавающей запятой в диапазоне от -1,0 до 1,0, представляющем полную шкалу в 16-битном мире. Затем сложите образцы вместе - теперь у вас есть много места. Наконец, если вы получите какие-либо сэмплы, значение которых выходит за пределы полной шкалы, вы можете либо ослабить весь сигнал, либо использовать жесткое ограничение (ограничение значения до 1.0).

Это даст намного лучший результат звучания, чем сложение 16-битных семплов вместе и их переполнение. Вот очень простой пример кода, показывающий, как можно суммировать два 16-битных отсчета вместе:

short sample1 = ...;
short sample2 = ...;
float samplef1 = sample1 / 32768.0f;
float samplef2 = sample2 / 32768.0f;
float mixed = samplef1 + sample2f;
// reduce the volume a bit:
mixed *= 0.8;
// hard clipping
if (mixed > 1.0f) mixed = 1.0f;
if (mixed < -1.0f) mixed = -1.0f;
short outputSample = (short)(mixed * 32768.0f)

конечно, но это увеличит вероятность обрезки, поэтому отрегулируйте громкость соответствующим образом

Mark Heath 22.02.2018 12:31

Это принесло вам белый шум @MarkHeath?

Jeremy 09.05.2018 00:42

Умножая микшированный на 0,8 ... разве вы просто не приближаете уровень шума к «среднему»? Если вы умножите отрицательное значение для смешанного (скажем -0,5) на 0,8, оно приблизится к 0, другими словами, оно станет ВЫШЕ ... так что либо вам нужно преобразовать в диапазон 0+ перед умножением, либо комментарии «немного уменьшить громкость» - это не совсем точно.

Bram Vaessen 21.11.2019 15:29

Есть статья о смешивании здесь. Мне было бы интересно узнать, что другие думают по этому поводу.

Это интересно. Обычно он выполняет сложение, а затем применяет очень простое «сжатие» сигнала, чтобы избежать клиппирования. Проблема в том, что это значительно изменит значения выборки, даже если нет необходимости обрезать. Для некоторых приложений (например, для телефонии, игр) такой подход, вероятно, подойдет. Но для высококачественной обработки звука это можно рассматривать как ухудшающее качество сигнала ...

Roddy 09.11.2011 21:17

Эта статья вводит в заблуждение (см. Мой ответ ниже). Если вы введете примерные значения в его окончательные формулы, вы получите плохие результаты (его алгебра плохая). Например. вход тишины дает вам -1 выход. В любом случае он не масштабируется более чем на два входа, и это алгоритм вуду, не имеющий реальной основы.

podperson 05.04.2012 21:28

Неразумно изменять объем для каждого образца. И алгоритм не правильный, потому что, если у вас есть два канала с одним и тем же сигналом, микширование этих двух каналов должно совпадать с каждым из них. Но этот алгоритм дает сигнал потери.

SuperLucky 14.06.2014 10:54

Эта статья совершенно неверна, как многие предполагали. Пожалуйста, перестаньте голосовать, вы только вводите людей в заблуждение.

Bill Kotsias 12.05.2015 20:51

преобразовать образцы в значения с плавающей запятой в диапазоне от -1,0 до +1,0, затем:

out = (s1 + s2) - (s1 * s2);

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

Adam Davis 08.12.2009 22:10

Также обратите внимание, что в статье указаны входные значения от 0 до 1.

Gauthier 22.10.2012 17:20

Вы также можете купить себе запас места с помощью такого алгоритма, как y = 1,1x - 0,2x ^ 3 для кривой и с ограничением сверху и снизу. Я использовал это в Гексафон, когда игрок играет несколько нот вместе (до 6).

float waveshape_distort( float in ) {
  if (in <= -1.25f) {
    return -0.984375;
  } else if (in >= 1.25f) {
    return 0.984375;
  } else {    
    return 1.1f * in - 0.2f * in * in * in;
  }
}

Он не пуленепробиваемый, но позволит вам подняться до уровня 1,25 и сглаживает клип до приятного изгиба. Создает гармоническое искажение, которое звучит лучше, чем клиппинг, и может быть желательным при некоторых обстоятельствах.

Пробовал это, и это сработало. Хорошее быстрое решение для обрезки.

Ehz 29.05.2012 20:03

Кроме того, в этом ответе подразумевается, что вы должны преобразовать в float перед смешиванием.

Ehz 05.06.2012 00:13

Выглядит интригующе. Откуда у тебя эти волшебные константы? (в частности, 1,25 и 0,984375?)

Cameron 23.08.2012 01:59

1,25 - это потолок, который я был готов принять (уровень 125%). 0,984375 - значение y для x = 1,25 в указанной формуле I.

Glenn Barnett 24.08.2012 17:37

Для справки: это сжатие (и небольшое расширение).

Gauthier 22.10.2012 17:17

Я бы предпочел прокомментировать один из двух высоко оцененных ответов, но из-за моей скудной репутации (полагаю) я не могу.

Отмеченный ответ: сложите вместе, и клип будет правильным, но не в том случае, если вы хотите избежать отсечения.

Ответ со ссылкой начинается с работающего алгоритма вуду для двух положительных сигналов в [0,1], но затем применяется некоторая очень ошибочная алгебра, чтобы получить совершенно неверный алгоритм для значений со знаком и 8-битных значений. Алгоритм также не масштабируется до трех или более входов (произведение сигналов будет уменьшаться, а сумма увеличивается).

Итак - преобразуйте входные сигналы в плавающие, масштабируйте их до [0,1] (например, 16-битное значение со знаком станет
float v = ( s + 32767.0 ) / 65536.0 (close enough...))
, а затем просуммируйте их.

Чтобы масштабировать входные сигналы, вам, вероятно, следует проделать некоторую реальную работу, а не умножать или вычитать значение вуду. Я бы посоветовал сохранить текущую среднюю громкость, а затем, если она начнет дрейфовать до высокого (скажем, выше 0,25) или низкого (скажем, ниже 0,01), начните применять значение масштабирования в зависимости от громкости. По сути, это становится реализацией автоматического уровня и масштабируется с любым количеством входов. Лучше всего то, что в большинстве случаев он вообще не испортит ваш сигнал.

Спасибо за заметки! Я думаю, это заслуживает ответа, но теперь у вас есть 50 представителей, так что вы можете комментировать сайт прямо сейчас.

Adam Davis 05.04.2012 18:25

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

> So what's the correct method to add these sounds together in my software mixer?

Как вы уже догадались, добавление и отсечение - правильный путь, если вы не хотите терять громкость источников. Для сэмплов int16_t необходимо, чтобы сумма была int32_t, а затем ограничить и преобразовать обратно в int16_t.

> Am I wrong and the correct method is to lower the volume of each by half?

Да. Уменьшение громкости вдвое - это несколько субъективно, но то, что вы можете видеть здесь и там, - это уменьшение громкости (громкости) вдвое - это уменьшение примерно на 10 дБ (деление мощности на 10 или значений выборки на 3,16). Но вы, очевидно, имеете в виду снизить примерные значения вдвое. Это уменьшение на 6 дБ, заметное уменьшение, но не такое большое, как уменьшение громкости вдвое (очень полезна таблица громкости там).

С помощью этого снижения на 6 дБ вы избежите любого клиппирования. Но что происходит, когда вам нужно больше входных каналов? Для четырех каналов вам нужно будет разделить входные значения на 4, что снизится на 12 дБ и, таким образом, станет меньше половины громкости для каждого канала.

> Do I need to add a compressor/limiter or some other processing stage to 
get the volume and mixing effect I'm trying for?

Вы хотите микшировать, а не обрезать, и не терять громкость входных сигналов. Это невозможно без каких-то искажений.

Как было предложено Марком Рэнсомом, решение, позволяющее избежать отсечения, не теряя при этом до 6 дБ на канал, состоит в том, чтобы попасть где-то посередине между «добавлением и отсечением» и «усреднением».

Это для двух источников: сложение, деление где-то между 1 и 2 (уменьшите диапазон от [-65536, 65534] до меньшего), затем ограничение.

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

Я не могу поверить, что никто не знает правильного ответа. Все достаточно близки, но все же чистая философия. Ближайшим, т.е. лучшим было: (s1 + s2) - (s1 * s2). Это отличный подход, особенно для микроконтроллеров.

Итак, алгоритм таков:

  1. Определите громкость, на которой вы хотите, чтобы звук был на выходе. Это может быть среднее или максимальное значение одного из сигналов.
    factor = average(s1) Вы предполагаете, что оба сигнала уже в порядке, не переполняется 32767.0
  2. Нормализуйте оба сигнала этим коэффициентом:
    s1 = (s1/max(s1))*factor
    s2 = (s2/max(s2))*factor
  3. Сложите их вместе и нормализуйте результат с тем же коэффициентом
    output = ((s1+s2)/max(s1+s2))*factor

Обратите внимание, что после шага 1. вам действительно не нужно возвращаться к целым числам, вы можете работать с числами с плавающей запятой в интервале от -1,0 до 1,0 и применять возврат к целым числам в конце с ранее выбранным коэффициентом мощности. Надеюсь, я не ошибся сейчас, потому что я очень тороплюсь.

Это не верно. Например. считайте, что s1 и s2 равны 0,5, s1 + s2 => 1, max (s1, s2) равно 0,5, поэтому на выходе будет 2. Вы прошли путь отсечения, и наивное добавление не имело бы. Кроме того, 0,25 и 0,25 дают тот же результат.

podperson 09.11.2020 22:36

Однажды я сделал это так: я использовал числа с плавающей запятой (выборки от -1 до 1) и инициализировал переменную autoGain значением 1. Затем я складывал все образцы вместе (также могло быть больше 2). Тогда я бы умножил исходящий сигнал на autoGain. Если абсолютное значение суммы сигналов до умножения будет больше 1, я бы назначил 1 / это значение суммы. Это фактически сделало бы автоматическое усиление меньше 1, скажем 0,7, и было бы эквивалентно тому, что какой-нибудь оператор быстро уменьшит основную громкость, как только увидит, что общий звук становится слишком громким. Затем в течение регулируемого периода времени я бы добавил к автоусилению, пока оно, наконец, не вернулось к «1» (наш оператор оправился от удара и медленно увеличивает громкость :-)).

// #include <algorithm>
// short ileft, nleft; ...
// short iright, nright; ...

// Mix
float hiL = ileft + nleft;
float hiR = iright + nright;

// Clipping
short left = std::max(-32768.0f, std::min(hiL, 32767.0f));
short right = std::max(-32768.0f, std::min(hiR, 32767.0f));

Спасибо всем за то, что поделились своими идеями, в последнее время я также занимаюсь микшированием звука. Я тоже поэкспериментировал с этим вопросом, ребята, может это вам поможет :).

Обратите внимание, что я использую частоту дискретизации 8 кГц и образец звука 16 бит (SInt16) в ios RemoteIO AudioUnit.

Во время моих экспериментов лучший результат, который я нашел, отличался от всего этого ответа, но основной тот же (как предлагает Родди)

«Вы должны сложить их вместе, но обрезать результат до допустимого диапазона, чтобы предотвратить переполнение / потерю значимости.».

Но каким должен быть лучший способ добавления без переполнения / потери значимости?

Ключевая идея :: У вас есть две звуковые волны, например A и B, и результирующая волна C будет суперпозиция из двух волн A и B. Сэмпл в ограниченном битовом диапазоне может вызвать его переполнение. Итак, теперь мы можем вычислить пересечение максимального лимита на верхней стороне и пересечение минимального лимита на нижней стороне формы волны суперпозиции. Теперь мы вычтем пересечение максимального верхнего предела из верхней части формы волны суперпозиции и добавим пересечение минимального нижнего лимита к нижней части формы волны суперпозиции. ВОЙЛА ... все готово.

Шаги:

  1. Сначала пройдитесь по циклу данных один раз для максимальное значение пересечения верхнего предела и минимальное значение пересечения нижнего предела.
  2. Сделайте еще один обход аудиоданных, вычтите максимальное значение из положительной части аудиоданных и добавьте минимальное значение к отрицательной части аудиоданных.

следующий код покажет реализацию.

static unsigned long upSideDownValue = 0;
static unsigned long downSideUpValue = 0;
#define SINT16_MIN -32768
#define SINT16_MAX 32767
SInt16* mixTwoVoice (SInt16* RecordedVoiceData, SInt16* RealTimeData, SInt16 *OutputData, unsigned int dataLength){

unsigned long tempDownUpSideValue = 0;
unsigned long tempUpSideDownValue = 0;
//calibrate maker loop
for(unsigned int i=0;i<dataLength ; i++)
{
    SInt32 summedValue = RecordedVoiceData[i] + RealTimeData[i];

    if (SINT16_MIN < summedValue && summedValue < SINT16_MAX)
    {
        //the value is within range -- good boy
    }
    else
    {
       //nasty calibration needed
        unsigned long tempCalibrateValue;
        tempCalibrateValue = ABS(summedValue) - SINT16_MIN; // here an optimization comes ;)

        if (summedValue < 0)
        {
            //check the downside -- to calibrate
            if (tempDownUpSideValue < tempCalibrateValue)
                tempDownUpSideValue = tempCalibrateValue;
        }
        else
        {
            //check the upside ---- to calibrate
            if (tempUpSideDownValue < tempCalibrateValue)
                tempUpSideDownValue = tempCalibrateValue;
        }
    }
}

//here we need some function which will gradually set the value
downSideUpValue = tempUpSideDownValue;
upSideDownValue = tempUpSideDownValue;

//real mixer loop
for(unsigned int i=0;i<dataLength;i++)
{
    SInt32 summedValue = RecordedVoiceData[i] + RealTimeData[i];

    if (summedValue < 0)
    {
        OutputData[i] = summedValue + downSideUpValue;
    }
    else if (summedValue > 0)
    {
        OutputData[i] = summedValue - upSideDownValue;
    }
    else
    {
        OutputData[i] = summedValue;
    }
}

return OutputData;
}

у меня он работает нормально, позже я намерен постепенно изменить значение upSideDownValue & downSideUpValue, чтобы получить более плавный вывод.

насколько я пытался использовать значения 4 pcm из отдельных источников, для меня это было нормально. Больше не пробовал.

Ratul Sharker 21.02.2018 21:16

convert the samples to floating point values ranging from -1.0 to +1.0, then:

out = (s1 + s2) - (s1 * s2);

Будет вносить сильные искажения, когда | s1 + s2 | подход 1.0 (по крайней мере, когда я пробовал при смешивании простых синусоид). Я прочитал эту рекомендацию в нескольких местах, но, по моему скромному мнению, это бесполезный подход.

Что происходит физически, когда волны «смешиваются», так это то, что их усилители складываются, как уже было предложено на многих плакатах. Либо

  • клип (тоже искажает результат) или
  • суммируйте свои 16-битные значения в 32-битное число, а затем разделите на количество ваших источников (это то, что я предлагаю, поскольку это единственный известный мне способ избежать искажений)

Я сделал следующее:

MAX_VAL = Full 8 or 16 or whatever value
dst_val = your base audio sample
src_val = sample to add to base

Res = (((MAX_VAL - dst_val) * src_val) / MAX_VAL) + dst_val

Умножьте левый запас src на нормализованное целевое значение MAX_VAL и добавьте его. Он никогда не будет клипировать, никогда не будет менее громким и абсолютно естественным.

Пример:

250.5882 = (((255 - 180) * 240) / 255) + 180

И это неплохо звучит :)

Можете ли вы дать объяснение, используя, возможно, четыре примера, в которых каждый из dst и src имеет высокое значение и низкое значение, поэтому легко понять, что делает этот алгоритм и почему?

Adam Davis 18.07.2017 16:05

Я нашел новый способ добавлять образцы таким образом, чтобы они никогда не превышали заданный диапазон. Основная идея состоит в том, чтобы преобразовать значения в диапазоне от -1 до 1 в диапазон примерно от -Infinity до + Infinity, сложить все вместе и отменить первоначальное преобразование. Я придумал для этого следующие формулы:

f(x)=-\frac{x}{|x|-1}

f'(x)=\frac{x}{|x|+1}

o=f'(\sum f(s))

Я попробовал это, и он действительно работает, но для нескольких громких звуков результирующий звук звучит хуже, чем просто сложение сэмплов и обрезка каждого слишком большого значения. Я использовал следующий код, чтобы проверить это:

#include <math.h>
#include <stdio.h>
#include <float.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <sndfile.h>

// fabs wasn't accurate enough
long double ldabs(long double x){
  return x < 0 ? -x : x;
}

// -Inf<input<+Inf, -1<=output<=+1
long double infiniteToFinite( long double sample ){
  // if the input value was too big, we'll just map it to -1 or 1
  if ( isinf(sample) )
    return sample < 0 ? -1. : 1.;
  long double ret = sample / ( ldabs(sample) + 1 );
  // Just in case of calculation errors
  if ( isnan(ret) )
    ret = sample < 0 ? -1. : 1.;
  if ( ret < -1. )
    ret = -1.;
  if ( ret > 1. )
    ret = 1.;
  return ret;
}

// -1<=input<=+1, -Inf<output<+Inf
long double finiteToInfinite( long double sample ){
  // if out of range, clamp to 1 or -1
  if ( sample > 1. )
    sample = 1.;
  if ( sample < -1. )
    sample = -1.;
  long double res = -( sample / ( ldabs(sample) - 1. ) );
  // sample was too close to 1 or -1, return largest long double
  if ( isinf(res) )
    return sample < 0 ? -LDBL_MAX : LDBL_MAX;
  return res;
}

// -1<input<1, -1<=output<=1 | Try to avoid input values too close to 1 or -1
long double addSamples( size_t count, long double sample[] ){
  long double sum = 0;
  while( count-- ){
    sum += finiteToInfinite( sample[count] );
    if ( isinf(sum) )
      sum = sum < 0 ? -LDBL_MAX : LDBL_MAX;
  }
  return infiniteToFinite( sum );
}

#define BUFFER_LEN 256

int main( int argc, char* argv[] ){

  if ( argc < 3 ){
    fprintf(stderr,"Usage: %s output.wav input1.wav [input2.wav...]\n",*argv);
    return 1;
  }

  {
    SNDFILE *outfile, *infiles[argc-2];
    SF_INFO sfinfo;
    SF_INFO sfinfo_tmp;

    memset( &sfinfo, 0, sizeof(sfinfo) );

    for( int i=0; i<argc-2; i++ ){
      memset( &sfinfo_tmp, 0, sizeof(sfinfo_tmp) );
      if (!( infiles[i] = sf_open( argv[i+2], SFM_READ, &sfinfo_tmp ) )){
        fprintf(stderr,"Could not open file: %s\n",argv[i+2]);
        puts(sf_strerror(0));
        goto cleanup;
      }
      printf("Sample rate %d, channel count %d\n",sfinfo_tmp.samplerate,sfinfo_tmp.channels);
      if ( i ){
        if ( sfinfo_tmp.samplerate != sfinfo.samplerate
         || sfinfo_tmp.channels != sfinfo.channels
        ){
          fprintf(stderr,"Mismatching sample rate or channel count\n");
          goto cleanup;
        }
      }else{
        sfinfo = sfinfo_tmp;
      }
      continue;
      cleanup: {
        while(i--)
          sf_close(infiles[i]);
        return 2;
      }
    }

    if (!( outfile = sf_open(argv[1], SFM_WRITE, &sfinfo) )){
      fprintf(stderr,"Could not open file: %s\n",argv[1]);
      puts(sf_strerror(0));
      for( int i=0; i<argc-2; i++ )
        sf_close(infiles[i]);
      return 3;
    }

    double inbuffer[argc-2][BUFFER_LEN];
    double outbuffer[BUFFER_LEN];

    size_t max_read;
    do {
      max_read = 0;
      memset(outbuffer,0,BUFFER_LEN*sizeof(double));
      for( int i=0; i<argc-2; i++ ){
        memset( inbuffer[i], 0, BUFFER_LEN*sizeof(double) );
        size_t read_count = sf_read_double( infiles[i], inbuffer[i], BUFFER_LEN );
        if ( read_count > max_read )
          max_read = read_count;
      }
      long double insamples[argc-2];
      for( size_t j=0; j<max_read; j++ ){
        for( int i=0; i<argc-2; i++ )
          insamples[i] = inbuffer[i][j];
        outbuffer[j] = addSamples( argc-2, insamples );
      }
      sf_write_double( outfile, outbuffer, max_read );
    } while( max_read );

    sf_close(outfile);
    for( int i=0; i<argc-2; i++ )
      sf_close(infiles[i]);
  }

  return 0;
}

Если я правильно визуализирую это в голове, все, что вы здесь делаете, в любом случае снижает точность при обрезке, что объясняет, почему это звучит плохо. Ограничение ожидаемого диапазона - это именно то, что есть обрезание.

Brad 24.11.2017 20:20

Этот вопрос старый, но вот действительный метод IMO.

  1. Преобразуйте оба образца в мощность.
  2. Добавьте оба образца по мощности.
  3. Нормализовать это. Например, максимальное значение не превышает ваш лимит.
  4. Преобразуйте обратно в амплитуду.

Вы можете выполнить первые 2 шага вместе, но вам понадобится максимум и минимум для нормализации на втором проходе для шагов 3 и 4.

Надеюсь, это кому-то поможет.

Другие вопросы по теме