Нарисуйте звуковую волну с возможностью увеличения / уменьшения - PullRequest
1 голос
/ 21 февраля 2010

Я пишу звуковой редактор для моего выпуска. Я использую BASS для извлечения сэмплов из файлов MP3, WAV, OGG и т. Д., А также для добавления эффектов DSP, таких как эхо, фленджер и т. Д. Просто в своей речи я создал свою структуру, которая применяет эффект от position1 к position2, вырезает / вставляет управление.

Теперь моя проблема в том, что я хочу создать элемент управления, похожий на этот, из Cool Edit Pro , который рисует представление песни в форме волны и имеет возможность увеличивать / уменьшать выбранные части форма волны и т. д. После выбора я могу сделать что-то вроде:

TInterval EditZone = WaveForm->GetSelection();

где TInterval имеет эту форму:

struct TInterval
{
    long Start;
    long End;
}

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

Я пишу свой проект на C ++, но я понимаю C #, код Delphi, поэтому, если вы хотите, вы можете публиковать фрагменты и на последних двух языках :)

Спасибо DrOptix

Ответы [ 4 ]

5 голосов
/ 21 февраля 2010

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

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

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

Стоит написать код, который обрабатывает все каналы файла за один проход при выполнении этого сканирования, медлительность здесь заставит всю вашу программу чувствовать себя вялой, здесь важен дисковый ввод-вывод, процессор не имеет проблем с сохранением Итак, простой код C ++ подходит для построения таблиц min / max, но вы не хотите просматривать файл более одного раза и хотите делать это последовательно.

Как только у вас будут минимальные / максимальные столы, держите их рядом. Вы хотите вернуться к диску как можно меньше, и многие причины, по которым вы хотите перекрасить окно, не требуют повторного сканирования ваших таблиц min / max. Стоимость памяти для их хранения не так высока по сравнению с дисками, в первую очередь для их создания.

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

2 голосов
/ 21 февраля 2010

Я недавно сделал это сам. Как предлагает Мариус, вам нужно определить, сколько сэмплов в каждом столбце пикселей. Затем вы определяете минимум и максимум, а затем строите вертикальную линию от максимума до минимума.

В качестве первого прохода это, похоже, работает нормально. Проблема, с которой вы столкнетесь, заключается в том, что при уменьшении масштаба вы начнете слишком долго извлекать сэмплы с диска. В качестве решения этой проблемы я создал «пиковый» файл вместе с аудиофайлом. Пиковый файл хранит минимальные / максимальные пары для групп из n выборок. Играть с n, пока вы не получите нужную сумму, зависит от вас. Лично я нашел 128 сэмплов хорошим компромиссом между размером и скоростью. Также стоит помнить, что, если вы не рисуете элемент управления размером больше 65536 пикселей, вам не нужно хранить эту пиковую информацию как что-то большее, чем 16-битные значения, что экономит место.

0 голосов
/ 16 февраля 2012

Использование пакета NAudio с открытым исходным кодом -

public class WavReader2
{
    private readonly WaveFileReader _objStream;

    public WavReader2(String sPath)
    {
        _objStream = new WaveFileReader(sPath);
    }

    public List<SampleRangeValue> GetPixelGraph(int iSamplesPerPixel)
    {
        List<SampleRangeValue> colOutputValues = new List<SampleRangeValue>();

        if (_objStream != null)
        {
            _objStream.Position = 0;
            int iBytesPerSample = (_objStream.WaveFormat.BitsPerSample / 8) * _objStream.WaveFormat.Channels;
            int iNumPixels = (int)Math.Ceiling(_objStream.SampleCount/(double)iSamplesPerPixel);

            byte[] aryWaveData = new byte[iSamplesPerPixel * iBytesPerSample];
            _objStream.Position = 0; // startPosition + (e.ClipRectangle.Left * iBytesPerSample * iSamplesPerPixel);

            for (float iPixelNum = 0; iPixelNum < iNumPixels; iPixelNum += 1)
            {
                short iCurrentLowValue = 0;
                short iCurrentHighValue = 0;
                int iBytesRead = _objStream.Read(aryWaveData, 0, iSamplesPerPixel * iBytesPerSample);
                if (iBytesRead == 0)
                    break;

                List<short> colValues = new List<short>();
                for (int n = 0; n < iBytesRead; n += 2)
                {
                    short iSampleValue = BitConverter.ToInt16(aryWaveData, n);
                    colValues.Add(iSampleValue);
                }

                float fLowPercent =  (float)((float)colValues.Min() /ushort.MaxValue);
                float fHighPercent = (float)((float)colValues.Max() / ushort.MaxValue);

                colOutputValues.Add(new SampleRangeValue(fHighPercent, fLowPercent));
            }
        }

        return colOutputValues;
    }
}

public struct SampleRangeValue
{
    public float HighPercent;
    public float LowPercent;
    public SampleRangeValue(float fHigh, float fLow)
    {
        HighPercent = fHigh;
        LowPercent = fLow;
    }
}
0 голосов
/ 21 февраля 2010

Не могли бы вы просто нанести точки выборки на 2 холста? Вы должны знать, сколько сэмплов в секунду для файла (прочитайте его из заголовка), а затем отобразить значение на оси y. Поскольку вы хотите иметь возможность увеличивать и уменьшать масштаб, вам необходимо контролировать количество выборок на пиксель (уровень масштабирования). Затем вы берете среднее значение этих точек выборки на пиксель (например, берете среднее значение каждых 5 точек, если у вас есть 5 выборок на пиксель. Затем вы можете использовать 2d API рисования для рисования линий между точками.

...