16 января 2012

Common Lisp. Работа со звуком. CL-PortAudio.

Сразу код: караоке.

Установите PortAudio.
Включите музыку (желательно, с вокалом), и выполните следующий код:

sudo pacman -S portaudio
git clone --depth 1 https://github.com/filonenko-mikhail/cl-portaudio.git
emacs
M+x slime
  (pushnew "path/to/cl-portaudio/" asdf:*central-registry*)
  (ql:quickload :cl-portaudio)
  (ql:quickload :cl-portaudio-tests)
  (portaudio-tests:test-read-write-echo)

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

CL-PortAudio

CL-PortAudio cffi-биндинги к PortAudio. PortAudio небольшая кроссплатформенная библиотека для работы со звуком. Она позволяет:

  • записывать звук;
  • воспроизводить звук.

CL-PortAudio соответственно позволяет делать эти вещи из лиспа.

Документация скопирована и переработана.

API

PortAudio предоставляет очень простое API. Для передачи данных в обоих направлениях можно использовать функцию обратного вызова (callback) или блокирующие функции чтения/записи. Блокирующие функции, надо сказать, можно "разблокировать", с помощью вызова *available функций, которые возвращают возможность чтения/записи данных.

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

Common Lisp позволил уменьшить код основной работы со звуком до 4 функций/макросов. Вот код караоке, которое было вначале.

 (use-package :portaudio)

 (defconstant +frames-per-buffer+ 1024)
 (defconstant +sample-rate+ 44100d0)
 (defconstant +seconds+ 15)
 (defconstant +sample-format+ :float)
 (defconstant +num-channels+ 2)


 (defun test-read-write-converted-echo ()
  "Record input into an array; Separate array to channels; Merge channels into array; Play last array." 
  (with-audio
    (format t "~%=== Wire on. Will run ~D seconds . ===~%" +seconds+) 
    (with-default-audio-stream (astream +num-channels+ +num-channels+ :sample-format +sample-format+ :sample-rate +sample-rate+ :frames-per-buffer +frames-per-buffer+) 
      (dotimes (i (round (/ (* +seconds+ +sample-rate+) +frames-per-buffer+)))
        (ignore-errors (write-stream astream
                                    (merge-channels-into-array astream
                                                               (separate-array-to-channels astream
                                                                                           (read-stream astream)))))))))

  • with-audio (&body body)

    Макрос инициализирует в начале и деинициализирует в конце body библиотеку portaudio.

  • with-default-audio-stream ((var num-input num-output &key (sample-format :float) (sample-rate 44100.0d0) (frames-per-buffer 1024)) &body body)

    Макрос открывает в начале и закрывает в конце поток для ввода/вывода с заданным количеством каналов, форматом данных (только :float), частотой и количеством данных за один вызов записи/чтения. Макрос также запускает и останавливает открытый поток.

  • read-stream (pa-stream)

    Читает данные из потока запущенного ранее. Возвращает одномерные массив данных типа sample-format (только 'single-float) размером (* frames-per-buffer num-input).

  • write-stream (pa-stream buffer)

    Записывает данные в поток запущенный ранее. Массив должен быть одномерным типа sample-format (только 'single-float) размером (* frames-per-buffer num-output).

  • separate-array-to-channels (pa-stream array)

    Преобразует одномерный массив в (кол-во_каналов)-мерный массив.

  • merge-channels-into-array (pa-stream channels)

    Преобразует (кол-во_каналов)-мерный массив в одномерный массив.

Бинарные сборки библиотеки PortAudio можно скачать вот здесь http://planet.plt-scheme.org/package-source/clements/portaudio.plt/2/3/lib/. Очень большое спасибо ракетчикам.

Протестировано:

  • SBCL 1.0.54, archlinux x86_64
  • SBCL 1.0.53.74.mswinmt.1092-207a13d, windows 7 x86_64 (vbox).
  • SBCL 1.0.55 mt, Mac OS X 10.6.8 x86 (thanks Žiga Lenarčič).

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

  1. URL в git clone подправьте, пожалуйста. Имя пользователя там не нужно.

    ОтветитьУдалить
  2. Спасибо, поправил.
    Добавил ссылку на бинарные сборки PortAudio.

    ОтветитьУдалить
  3. Оффтопик.
    К вопросу о хостинге лисп-проектов. Если ещё нужно:
    Могу предоставить ВМ по-минимуму - нахаляву. При повышении требований, договоримся - не выше средней цены по инету.
    Что есть "по-минимуму" надо подумать. А какие ресурсы собственно нужны?

    ОтветитьУдалить
  4. Хотел было сделать multiuser web interface for maxima, но задача изоляции потока сложно решаема, поэтому пока остановился на цели протолкнуть embeddable-maxima в quicklisp.
    Но у меня тут уже еще одна идейка появилась, поэтому ради экспериментов я бы не отказался от ВМ.
    Относительные требования:
    мощность для sbcl hunchentoot restas swank + запас.
    статический IP + открытые tcp/udp порты 8080 8081
    ssh доступ

    ОтветитьУдалить
  5. Сделаю.
    Под мощностью я имел в виду конкретные значение. Короче, посмотрим по ситуации - сколько надо и сколько я смогу предоставить. По вопросам пиши на linkfly1 at newmail.ru

    ОтветитьУдалить
    Ответы
    1. Отправил письмо с конкретными значениями.

      Удалить