@Joysi

Как сложить оцифрованные аудиосигналы?

День добрый!
Генерирую звук на определенной частоте (неважно, в виде синусоиды, пилы, треугольника) в виде массива значений double (1-максимальная амплитуда). Проигрывается норм.
Если сгенерировать несколько таких звуков на разной частоте, сложить их, обработать , то:
1) сложение с дальнейшей линейной апроксимацией в максимальную амплитуду 1 (combineWithNormalize) будет звучать корректно, но очень тихо....
2) сложение с линейной (combineWithLinearDynaRangeCompression) компрессией или логарифмической(combineWithLnDynaRangeCompression) приводят к хрипам (игрался с пороговым значением threshold).

Собственно вопрос - возможно я последующие шаги пропустил, или еще что. Что я делаю не так?
Пробовал и стартовые минисмещения добавлять при генерации исходных сигналов, чтобы минизировать появление пиков при кратных частотах и т.п.
Какие вообще существуют приемлимые алгоритмы сложения аудиосигналов из нескольких исходных с формированием итогового файла (а не онлайн игра громкостью), который например в синтезаторах используется?
Чтобы без хрипов, и в то же время не очень тихо. Может порекомендуете хорошие статьи/книги (можно англоязычные)
Спасибо заранее.

Код (неоптимизированная Java):
public class Combines {

    /**
     * Складывает аудиосигналы + проводит постнормализацию в [-1;1]
     * @param audio входные аудиосигналы
     * @return сложенный аудиосигнал
     */
    public static double[] combineWithNormalize( double[]... audio) {
        if (audio.length == 0) return null;
        if (audio.length == 1) return audio[0];

        int maxIdx = 0;
        // Найдем самый длинный семпл
        for(double[] arr: audio)
            if (arr.length > maxIdx)
                maxIdx = arr.length;

        // Приведем все входные семплы к максимальной длине
        for(int i=0; i < audio.length; i++)
            if (audio[i].length < maxIdx)
                audio[i] = Arrays.copyOf(audio[i], maxIdx);

        // Сложим все аудиосемплы (+ выделим пиковый аудиосигнал)
        double[] result = new double[maxIdx];
        double normalizer  = 1.0;
        for (int i = 0; i < maxIdx; i++) {
            for (int j = 0; j < audio.length; j++)
                result[i] += audio[j][i];
            double res = Math.abs(result[i]);
            if (res > normalizer)
                normalizer = res;
        }

        double coeff = 1.0/ normalizer;
        if (normalizer !=1.0)
            for (int i = 0; i < maxIdx; i++)
                result[i] *= coeff;
        return result;
    }

    /**
     *  Складывает аудиосигналы c использование линейной компрессии диапазона
     * @param threshold пороговый уровень компрессии
     * @param audio входные аудиосигналы (должны быть нормализованы в [-1;1] !)
     * @return сложенный аудиосигнал
     */
    public static double[] combineWithLinearDynaRangeCompression(double threshold, double[]... audio) {
        if (audio.length == 0 || threshold >= 1 || threshold < 0) return null;
        if (audio.length == 1) return audio[1];
        int maxIdx = 0;

        // Найдем самый длинный семпл
        for(double[] arr: audio)
            if (arr.length > maxIdx)
                maxIdx = arr.length;

        // Приведем все входные семплы к максимальной длине
        for(int i=0; i < audio.length; i++)
            if (audio[i].length < maxIdx)
                audio[i] = Arrays.copyOf(audio[i], maxIdx);

        double[] result = Arrays.copyOf(audio[0], maxIdx); // Нормализованный результируюший массив.
        double linearCoeff  = (1-threshold)/(2-threshold);

        // Сложим все аудиосемплы по принципу
        for (int i = 1; i < audio.length; i++)
            for (int j = 0; j < maxIdx; j++) {
                double res = result[j] + audio[i][j];
                double absRes = Math.abs(result[j] + audio[i][j]);
                if (absRes <= threshold)
                    result[j] = result[j] + audio[i][j];
                else
                    result[j] = Math.signum(res) * (threshold + linearCoeff * (absRes - threshold));
            }
        return result;
    }

    /**
     *  Складывает аудиосигналы c использование логарифмической компрессии диапазона
     * @param threshold пороговый уровень компрессии
     * @param audio входные аудиосигналы (должны быть нормализованы в [-1;1] !)
     * @return сложенный аудиосигнал
     */
    public static double[] combineWithLnDynaRangeCompression(double threshold, double[]... audio) {
        if (audio.length == 0 || threshold >= 1 || threshold < 0) return null;
        if (audio.length == 1) return audio[1];
        int maxIdx = 0;

        // Найдем самый длинный семпл
        for(double[] arr: audio)
            if (arr.length > maxIdx)
                maxIdx = arr.length;

        // Приведем все входные семплы к максимальной длине
        for(int i=0; i < audio.length; i++)
            if (audio[i].length < maxIdx)
                audio[i] = Arrays.copyOf(audio[i], maxIdx);

        double[] result = Arrays.copyOf(audio[0], maxIdx); // Нормализованный результируюший массив.
        double expCoeff = alphaT[(int) threshold*100];

        for (int j = 1; j < maxIdx; j++) {
            double res = 0;
            for (int i = 0; i < audio.length; i++)
                res = res + audio[i][j];
            double absRes = Math.abs(res);
            if (absRes <= threshold)
                result[j] = res;
            else
                result[j] = Math.signum(res) * (threshold + (1 - threshold) *
                        Math.log(1.0 + expCoeff * (absRes - threshold) / (2 - threshold)) /
                        Math.log(1.0 + expCoeff));
        }

        return result;
    }

    // Решение уравнений pow(1+x,1/x)=exp((1-t)/(2-t)) при t=0, 0.01, 0.02 ... 0.99
    final private static double[] alphaT = {
                    2.51286, 2.54236, 2.57254, 2.60340, 2.63499, 2.66731, 2.70040, 2.73428, 2.76899, 2.80454,
                    2.84098, 2.87833, 2.91663, 2.95592, 2.99622, 3.03758, 3.08005, 3.12366, 3.16845, 3.21449,
                    3.26181, 3.31048, 3.36054, 3.41206, 3.46509, 3.51971, 3.57599, 3.63399, 3.69380, 3.75550,
                    3.81918, 3.88493, 3.95285, 4.02305, 4.09563, 4.17073, 4.24846, 4.32896, 4.41238, 4.49888,
                    4.58862, 4.68178, 4.77856, 4.87916, 4.98380, 5.09272, 5.20619, 5.32448, 5.44790, 5.57676,
                    5.71144, 5.85231, 5.99980, 6.15437, 6.31651, 6.48678, 6.66578, 6.85417, 7.05269, 7.26213,
                    7.48338, 7.71744, 7.96541, 8.22851, 8.50810, 8.80573, 9.12312, 9.46223, 9.82527, 10.21474,
                    10.63353, 11.08492, 11.57270, 12.10126, 12.67570, 13.30200, 13.98717, 14.73956, 15.56907, 16.48767,
                    17.50980, 18.65318, 19.93968, 21.39661, 23.05856, 24.96984, 27.18822, 29.79026, 32.87958, 36.59968,
                    41.15485, 46.84550, 54.13115, 63.74946, 76.95930, 96.08797, 125.93570, 178.12403, 289.19889, 655.12084
            };
}
  • Вопрос задан
  • 318 просмотров
Пригласить эксперта
Ответы на вопрос 1
alexsandr0000
@alexsandr0000
Программист C#/C++/C
Вы бы уточнили с какой целью вы суммируете сигналы. По идее надо перед суммированием нормализовать сигналы относительно того, у которого максимальная амплитуда, но тут возможен вариант, что последний будет "давить" все остальные (поэтому звук видимо тихий). Еще как вариант указать порог и ограничить амплитуду перед нормализацией или как-то по-другому "подавить" всплески.
Другой путь использовать масштабирующий коэффициент зависящий от важности сигнала в общей сумме, т.е. нормализуем сигналы и умножаем на этот коэффициент.
В общем, попробуйте замоделировать эти процессы в Matlab simulink.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы