вторник, 28 февраля 2017 г.

Картинка из музыки и музыка из картинки

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

Почему именно эта идея


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

Как устроен цифровой звук


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

Как устроен цифровой цвет


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

Как превратить звук в цвет


Всё довольно просто. Обе сущности представляются массивами чисел. Нам всего лишь нужно забрать массив из звукового файла и сохранить его в файл изображения. Здесь важен только один момент: и звуковые, и растровые файлы имеют параметр разрядности, который указывает, сколько бит информации должно храниться в каждом из значений. Согласно стандартам, это число кратно 4 и варьируется от 8 до 64. Схема будет правильно работать только в том случае, когда диапазоны значений амплитуды звука и количества цветов палитры будут одинаковыми.

Какой формат звука подходит


Проще всего извлечь данные из обычного формата WAV без сжатия. В первую очередь нам нужен конвертер, с помощью которого мы будем переводить музыку из MP3 в WAV. Что касается самого формата, удобнее всего будет использовать WAV с разрядностью в 24 бита, потому как стандартная палитра RGB тоже 24-битная. Каналы звука связываем в единственный (моно), частоту развёртки указываем максимальной (48 КГц).
Теперь, когда у нас есть звуковой файл необходимого формата, можно начинать писать программу. Открываем Visual Studio и создаём новое приложение на пресете Windows Forms. Для работы со звуком нам понадобится библиотека NAudio - сразу заходим в NuGet и устанавливаем её:


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

//Метод для извлечения массива байтов из указанного файла WAV 
public static byte[] Load24bitWaveFormFromFile(String filePath)
{
    WaveFileReader waveFileReader = null; //Из библиотеки NAudio
    try
    {
        waveFileReader = new WaveFileReader(filePath); //Пробуем открыть файл
        if (waveFileReader.WaveFormat.BitsPerSample == 24) //Проверяем разрядность
        {
            byte[] byteBuffer = new byte[waveFileReader.Length]; //Создаём массив
            waveFileReader.Read(byteBuffer, 0, byteBuffer.Length); //Считываем туда файл
            return byteBuffer; //Передаём готовый массив наружу
        }
        else
        {
            throw new Exception("Параметры данного звукового файла не подходят");
        }
    }
    catch(IOException)
    {
        throw new Exception("Программа не может открыть данный файл");
    }
    finally
    {
        if (waveFileReader != null) waveFileReader.Close();
    }
}

Дальше необходимо выяснить, в каком порядке мы расположим пиксели, соответствующие ступеням звуковой волны. Если поставить их в одну линейку, это будет строчка из ~10 миллионов точек. Ширина монитора, к сожалению, поменьше. Другое дело, если нарисовать из них квадрат. Если расположить точки в квадрате построчно (как текст в книге), мы получим хаотичную кашу по типу белого шума. Я решил, что логичнее всего было бы нарисовать из них спираль. Это обеспечит картинке композицию и позволит обнаружить в ней симметрию. Чтобы у нас получилось это сделать, количество 24-битных фрагментов в массиве из файла WAV должно обладать значением, из которого извлекается целочисленный квадратный корень, делимый на 4 без остатка:

//Метод для выравнивания массива до размера, который подойдёт для алгоритма спирали
public static byte[] GetAligned24bitAudioBufferToQuartersSquare(byte[] byteBuffer)
{
    //В 1 значении 24 бита (3 байта) - найдем количество пикселей, разделив длину массива на 3
    Int32 rgbPixelsQuantity = byteBuffer.Length / 3;
    //Найдём квадратный корень из количества пикселей
    Double pixelsQuantitySquareRoot = Math.Sqrt(rgbPixelsQuantity);
    //Округлим корень в сторону увеличения 
    Int32 rgbSquareSide = Convert.ToInt32(Math.Ceiling(pixelsQuantitySquareRoot));
    //Чтобы алгоритм спирали работал правильно, увеличим сторону до ближайшего делимого на 4
    while (rgbSquareSide % 4 != 0) rgbSquareSide++;
    //Умножим "выоровненную" сторону саму на себя, и потом на 3. Это новое количество пикселей
    Int32 resultingRgbPixelsQuantity = rgbSquareSide * rgbSquareSide * 3;
    //Создадим соответствующий новый массив
    byte[] newByteBuffer = new byte[resultingRgbPixelsQuantity];
    //Скопируем в него старый
    Array.Copy(byteBuffer, 0, newByteBuffer, newByteBuffer.Length - byteBuffer.Length, byteBuffer.Length);
    return newByteBuffer; //Отдаём готовые данные
}

Теперь нужно узнать, как сделать из линейной последовательности спираль. Мне удалось найти алгоритм, который фактически делает то, что нам нужно. Чтобы он мог оперировать непосредственно пикселями, массив байтов необходимо разгруппировать по частям в 24 бита (RGB):

//Структура, описывающая цвет RGB
public struct RGB24
{
    public byte R;
    public byte G;
    public byte B;
}

//Метод для получения массива цветов RGB из массива байт
public static RGB24[] GetRgb24ArrayFromByteArray(byte[] byteArray)
{
    int pixelsCount = byteArray.Length / 3;
    RGB24[] rgb24Array = new RGB24[pixelsCount];
    for (int i = 0; i < pixelsCount; i++)
    {
        Int32 currentPixelBytePosition = i * 3;
        rgb24Array[i] = new RGB24
        {
            R = byteArray[currentPixelBytePosition],
            G = byteArray[currentPixelBytePosition + 1],
            B = byteArray[currentPixelBytePosition + 2]
        };
    }
    return rgb24Array;
}

Теперь вышеуказанный алгоритм для спирали можно переписать под нашу задачу. Результатом его работы и будет то самое изображение:

//Метод, который делает из линейного массива спираль в матрице, и отдаёт эту матрицу в линейном виде
public static RGB24[] TransformRgb24PlainSequentialMatrixToPlainSpiralMatrix(RGB24[] plainSequentialMatrix)
{
    Int32 matrixSide = Convert.ToInt32(Math.Sqrt(plainSequentialMatrix.Length));
    RGB24[,] spiralMatrix = new RGB24[matrixSide, matrixSide];

    int position = 0;
    int count = matrixSide;
    int value = -matrixSide;
    int sum = -1;

    do
    {
        value = -1 * value / matrixSide;
        for (int i = 0; i < count; i++)
        {
            sum += value;
            spiralMatrix[sum / matrixSide, sum % matrixSide] = plainSequentialMatrix[position++];
        }
        value *= matrixSide;
        count--;
        for (int i = 0; i < count; i++)
        {
            sum += value;
            spiralMatrix[sum / matrixSide, sum % matrixSide] = plainSequentialMatrix[position++];
        }
    } while (count > 0);

    return spiralMatrix.Cast<RGB24>().ToArray();
}

Перед тем, как мы посмотрим, что из этого получилось, оставляю ссылки на архив с программой и код решения в моём репозитории на GitHub.


Из композиции Людвига ван Бетховена "К Элизе" у нас получилось вот такое изображение с разрешением 3000 * 3000 пикселей. Абстрактно.


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

Podval Capella - Renaissance



DJ Krush - Kemuri



Morcheeba - Slowdown



Lana Del Rey - High By The Beach



ATL - Подснежник



Morandi - Angels



Грибы - Минимал



SIDXKICK - Aerials



Oxxxymiron - CCTV



Gesaffelstein - Pursuit



Denzel Curry - Threatz ft. Yung Simmie & Robb Bank$ (Ekali & Gravez Remix)



Fractional - Tess



blessthefall - Hollow Bodies



Увидели в этом опыте что-то интересное? Оставляйте свои впечатления в комментариях и делайте репост этой статьи с моей стены ВКонтакте!

5 комментариев:

  1. http://ekimoff.ru/download/windowlicker/aphex.jpg

    ОтветитьУдалить
  2. Этот комментарий был удален администратором блога.

    ОтветитьУдалить
  3. The Venetian - Casino | Travelocity - Jackson Hole
    The 충주 출장샵 Venetian - Casino. 1 Mohegan Sun 전라북도 출장샵 Boulevard Uncasville, 파주 출장샵 CT 06382. Phone: (860) 962-9247. Book 김천 출장마사지 now. 경상남도 출장샵

    ОтветитьУдалить