27 июля 2009 г.

Управляем Gajim из командной строки

Gajim — это популярный и активно развивающийся Jabber-клиент, написанный на Python. К слову, он обладает очень неплохой поддержкой всевозможных вкусностей и полезностей Jabber по сравнению со многими другими клиентами. Одной из таких полезностей является возможность управления им из командной строки при помощи D-Bus — системы межпроцессного взаимодействия.

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

В состав пакета с Gajim входит консольная утилитка gajim-remote, которая как раз и предназначена для отправки команд основной программе. Если вызвать её без параметров, она покажет список поддерживаемых действий. Вот наиболее интересные:
  • check_gajim_running — проверить, запущен ли Gajim (результат — True или False),
  • list_accounts — список учётных записей,
  • list_contacts [учётные записи] — список контактов в учётных записях,
  • contact_info <jid> — информация о пользователе с указанным JID,
  • account_info <учётная запись> — показать информацию об одной из учётных записей Gajim,
  • add_contact <jid> [учётные записи] — добавить контакт в ростер,
  • remove_contact <jid> [учётные записи] — удалить учётную запись из ростера,
  • change_status <статус> [сообщение] [учётные записи] — изменить статус с возможностью указания сообщения и учётной записи (если не указать — будет изменен статус всех учётных записей),
  • get_status [учётные записи] — получить статус указанных учётных записей,
  • get_status_message [учётные записи] — получить сообщение статуса указанных учётных записей,
  • get_unread_msgs_number — количество непрочитанных сообщений,
  • handle_uri <uri> [учётные записи] — обработать ссылку вида «xmpp://»,
  • join_room <комната> [ник] [пароль:] [учётные записи] — присоединиться к конференции,
  • send_chat_message <jid> <сообщение> [PGP-ключ] [учётные записи] — отправить сообщение в чат,
  • send_file <файл> <jid> [учётные записи] — отправить файл,
  • send_groupchat_message <jid> <сообщение> [учётные записи] — отправить сообщение в конференцию,
  • send_single_message <jid> <тема> <сообщение> [PGP-ключ] [учётные записи] — отправить одиночное сообщение,
  • open_chat <jid> [учётные записи] — открыть чат с указанным контактом,
  • start_chat <учётные записи> — открыть чат для заданной учётной записи (появится окно ввода JID),
  • toggle_roster_appearance — показать/скрыть окошко ростера.
Попробуем теперь применить это на практике. Напишем скрипт, меняющий сообщение статуса на случайную цитату из fortunes. (Для тех, кто ещё не знает, fortunes — это консольная программа, выдающая случайную цитату из довольно объемной базы.)

Для этого в бесконечном цикле будем вызывать gajim-remote, передавая ему при помощи обратных кавычек результат выполнения команды fortunes. Между вызовами поставим паузу 5 минут (5×60=300 с).
while true; do gajim-remote change_status "online" "`fortune`"; sleep 300; done
У этой команды есть небольшой недостаток — нужно всё время держать терминал открытым. Для того, чтобы решить эту проблему, можно просто записать команду в текстовый файл, а потом запустить следующим образом:
bash changestatus.sh &
Амперсанд в конце запустит выполнение скрипта в фоне. Можно также создать правило для cron — тогда вообще не придется что-либо запускать.

Другое возможное применение — отправка кому-то сообщения по расписанию. Например, при помощи такой команды:
echo "gajim-remote send_chat_message user@example.com 'Hello'" | at 10:00
Ровно в 10:00 пользователю будет отправлено сообщение «Hello» (если, конечно, Gajim будет в этот момент запущен).

Итак, даже этот небольшой набор команд в сочетании с инструментами командной строки позволяет сделать что-то полезное.

Если работа gajim-remote покажется слишком медленной, можно воспользоваться, например, командой dbus-send или qdbus для отправки сообщения напрямую через D-Bus.

Список предоставляемых функций можно посмотреть при помощи команды
qdbus org.gajim.dbus /org/gajim/dbus/RemoteObject
Вызвать же функцию можно так:
qdbus org.gajim.dbus /org/gajim/dbus/RemoteObject toggle_roster_appearance
Работает намного быстрее gajim-remote, но приходится явно указывать, кому мы шлём команды.


Читать дальше…

20 июля 2009 г.

Построение графиков с помощью PyXPlot

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

Среди программ таких можно отметить, например, asymptote, популярный gnuplot, metapost и другие. Достойное место в этом ряду занимает PyXPlot (её команды, кстати, во многом совпадают с командами gnuplot, так что изучив одну из этих программ, легко разобраться в другой). Отличительными особенностями программы являются простота и высокое качество результата. Ей и посвящен этот обзор.

Запуск программы
PyXPlot есть в дистрибутиве Ubuntu и Debian, так что установка должна пройти без проблем. В крайнем случае, всегда можно скачать программу с родного сайта.

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

Запускается PyXPlot как обычно:
pyxplot СКРИПТЫ

Если вместо списка файлов поставить дефис, то программа будет читать команды со стандартного ввода, что очень удобно при автоматизации. Например, так можно нарисовать синусоиду:
echo 'plot sin(x)' | pyxplot -

Простейшие операции
Главная команда для рисования графиков — plot. Она позволяет не только рисовать графики функции, но и строить «по точкам» графики данных, записанных в файл.

Чтобы построить данные из файла, нужно просто указать его имя в апострофах или двойных кавычках после команды plot. Данные в файле должны быть обычным текстом с одним или несколькими столбцами чисел, разделенных пробелами или запятыми. Если имя заканчивается на «.gz», он будет автоматически распакован перед построением.
plot 'data.gz'
Так как в файле указаны отдельные значения, то и отображаются они по отдельности, маркерами.

По умолчанию результаты сохраняются в файл «pyxplot.eps».

Построения графика функции полностью аналогична. Нужно просто записать её после команды plot. В качестве аргумента нужно использовать x.
plot sin(x)/x
Можно построить сразу несколько графиков в одной системе координат, просто перечислив функции (или имена файлов) через запятую.
plot sin(x)/x, sin(x)
Переменные и функции
В теле файла с командами можно объявить переменные и пользовательские функции при помощи операции «=». Всё достаточно интуитивно:
a = 2
b = 3
plot sqrt(a*pow(x,2) + b)
В PyXPlot встроены самые распространенные математические функции, такие как: sin, cos, tan, exp, min, log, acos, asin, atan, sqrt, pow и т. д.
f(x) = sqrt(x)*sin(x)
plot f(x)
Настройки графика
При помощи команды set можно задать дополнительные параметры графика, такие как подписи осей (x label и ylabel) и заголовок графика (title). В подписях можно использовать формат формулы в формате TeX (их нужно обязательно заключить в знаки «$»). Использование TeX делает график намного симпатичнее и презентабельнее.

Пример:
set xlabel '$x$'
set ylabel '$y$'
set title 'Plot of $\sin(x)\over x$'
plot sin(x)/x
Можно также задать имя выходного файла (set output) и формат выходных данных (set terminal). Поддерживаются распространённые форматы: postscript, pdf, png, jpeg.

Пример:
set terminal png
set output 'plot.png'
plot cos(x)*exp(x)
Работа с файлами данных
Данные в файле можно использовать как независимые координаты при построении. Для этого нужно добавить парметр using к команде plot.

Например, команда
plot 'data.gz' using 1:2
строит график, используя первый столбец как координату x точек, а второй — как координату y.

Стили графика
Стиль построения графика настраивается при помощи параметра with.

Команда
plot 'data.gz' with points
строит точечный график, а команда
plot 'data.gz' with lines
соединяет точки отрезками.

Можно также откладывать ошибку на графике (with yerrorbars), но для этого потребуется третий столбец.

Тип маркеров и линий также настраивается при помощи модификаторов pointtype и linetype. Подробности — в документации.
plot sin(x) with lines linetype 3
Диапазоны по осям
При построении можно указать размеры осей. Для этого нужно указать минимально и максимальное значения по каждой из осей в квадратных скобках через двоеточие. Если нужно указать параметры только для одной оси, то для другой просто записсывается «[:]» без чисел. Пример:
plot [-1:1][-1:1] sin(x)
Заключение
Разумеется, возможности программы не исчерпываются только перечисленными командами. Утилита имеет ещё очень много разных возможностей: от аппроксимации данных из файла до построения графиков векторных полей.



Читать дальше…

Массовое переименование файлов

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

В этих случаях на помощь приходят регулярные выражения и специальные утилиты для массового переименования.

Для меня наиболее удобна консольная утилита prename (или rename), входящая в состав пакета perl. Но кому-то больше по душе, наверное, придутся программы с графическим интерфейсом. Могу посоветовать программы krename или gprename. Однако, в большинстве случаев вполне хватит и prename.

Введение
Синтаксис команды выглядит очень просто:
prename ШАБЛОН ФАЙЛЫ

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

Параметр ФАЙЛЫ может быть как списком файлов, так и шаблоном в стиле bash, использующим «*» для любой последовательности символов и «?» для одиночного символа. Например, «*.txt» — это все файлы с раширением «txt».

Регулярные выражения
Остановимся поподробнее на регулярных выражениях. Это настолько обширная тема, что о них пишутся целые книги. И настолько сложная, что большую популярность получила известная шутка: «Некоторые люди, когда сталкиваются с какой-то проблемой, думают: “Думаю, я применю регулярные выражения.” Теперь у них две проблемы.» (Jamie Zawinski) Однако, в простых случаях регулярные выражения просты и удобны. Разве что выглядят непривычно. (Честно говоря, иногда они выглядят так, будто по клавиатуре пробежала кошка.)

В утилите prename регулярные выражения обычно имеют следующий вид:
s/ИСХОДНЫЙ/КОНЕЧНЫЙ/
ИСХОДНЫЙ и КОНЕЧНЫЙ — это шаблоны. Первый задает подстроку, которую нужно найти в имени, а второй — на что эту подстроку заменить.

В простейшем случае это могут быть обычные слова. Команда
prename 's/Алиса/Боб/' *.txt
заменит во всех файлах с расширением «txt» слово «Алиса» на слово «Боб».

Нужно заметить, что шаблон применяется к каждому имени файла только один раз. То есть файл с именем «Эх, Алиса, Алиса.txt» будет заменен на «Эх, Боб, Алиса.txt». Чтобы шаблон примянялся столько, сколько нужно, необходимо добавить параметр g к регулярному выражению:
prename 's/Алиса/Боб/g' *.txt
Подстановки
Кроме собственно слов можно использовать специальные подстановочные символы:
«.» — любой одиночный символ,
«\d» — цифра,
«\D» — любой символ кроме цифры,
«\w» — буква, цифра или знак подчёркивания,
«\W» — всё, кроме букв, цифр и знака подчёркивания,
«\s» — любой пробельный символ,
«\S» — любой непробельный символ.

Например, команда
prename 's/\s/_/g' *.txt
заменит пробелы на знаки подчёркивания. Несколько идущих подряд пробелов будут заменены на несколько подчёркиваний.

Начало и конец
Есть также специальные псевдосиволы:
«^» — обозначает начало слова,
«$» — обозначает конец слова.

Например, команда
prename 's/a$/b/' *.txt
заменит «a» на «b» только если «a» — последний символ имени файла.

Экранирование
Если же какой-то из специальных символов нужно использовать в шаблоне как символ обычный, его нужно экранировать обратным слэшем. Например, «.» — одиночный символ, «\.» — просто точка.

Повторы
После любой части регулярного выражения можно поставить количество его повторов:
«?» — ноль или один,
«*» — ноль и более,
«+» — один и более,
«{n}» — ровно n
«{m,n}» — от m до n,
«{m,}» — от m и более,
«{,n}» — от нуля до n.

Символы «?», «+» и «*» по умолчанию являются «жадными», то есть пытаются отхватить как можно большую часть выражения, попадающую под шаблон.

Команда
prename 's/\s+/_/g' *.txt
заменит пробелы на знаки подчёркивания. Несколько идущих подряд пробелов будут заменены на одно подчёркивание.

Группировка и ссылки
Части выражения можно группировать при помощи круглых скобок. Также можно ссылаться на скобки при помощи выражения вида «$номер». Здесь — номер — это номер пары скобок в порядке их вхождения в выражение.

Например, команда
prename 's/(\d)+/$1/g' *.txt
заменит несколько подряд идущих одинаковых цифр в имени на одну (эту же!) цифру. То есть файл с именем «11122233333.txt» будет переименован в «123.txt».

А команда
prename 's/(.*) - (.*)\.mp3/$2 - $1\.mp3/' *.mp3
переименует файл «Ария Филиппа - Шаляпин.mp3» в «Шаляпин - Ария Филиппа.mp3».

Что за там, за горизонтом?
Есть также полезная возможность заглянуть вперед или назад, но не включая определенную часть шаблона в найденное:
«(?=ШАБЛОН)» — просмотр вперёд,
«(?!ШАБЛОН)» — просмотр вперёд (с отрицанием),
«(?<=ШАБЛОН)» — просмотр назад,
«(?<!ШАБЛОН)» — просмотр назад (с отрицанием).

Например, команда
prename 's/(?<!x)a/b/g' *.txt
заменит «a» на «b» в файлах с раширением «txt» только если «a» не идёт после x.

А команда
prename 's/a(?!x)/b/g' *.txt
заменит «a» на «b» в файлах с раширением «txt» только если «a» не идёт перед x.

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

В качестве неплохого руководства по ним могу порекомендовать книгу Дж. Фридла «Регулярные выражения».

У утилиты, конечно, есть и недостатки. Главный очевиден — нужно знать регулярные выражения. Есть и другие. Например, с помощью prename нельзя пронумеровать файлы.

Читать дальше…

17 июля 2009 г.

Идеи для GNOME Shell

Как известно, сейчас полным ходом идет работа над GNOME 3, в котором привычный интерфейс будет заменен на GNOME Shell. И хотя последний будет включен в GNOME 2.28, работа сейчас находится на достаточно ранней стадии. В связи с этим разработчики предлагают каждому предложить свою идею, каким должен быть интерфейс нового GNOME. Сбор идей производится на сайте проекта.

Вот, некоторые из предложений, представленных на сайте, которые мне показались достаточно любопытными (подробности — на сайте GNOME).


Расширенный вид меню
Máirín Duffy предлагает сделать возможность отображения в меню Activities не только приложений, но и связаной с ними информации. Например, недавно открытых файлов.





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



Полка для окон
Довольно интересной представляется возможность складывать неиспользуемые окна на «полку». Особенно удобно это будет с устройствами, оборудованными тачскринами. Тогда окна можно будет просто выделить при помощи «лассо» и свернуть, чтобы не занимали место.



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



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



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





Заголовки окон вместо панели
Не знаю, насколько это удобно, но есть предложение отказаться от панели, где отображались бы запущенные приложения. Вместо этого можно было бы использовать заголовки окон.



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



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



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



Отказ от списка приложений
Есть также идея вообще отказаться от списка приложений в пользу списка контактов и документов (организованного, например, при помощи Zeitgeist). В целом, довольно здравая мысль, так как всё равно люди часто используют приложения только для обработки документов или для свзяи с кем-то.



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



Вот, вкратце, наиболее инновационные предложения. Есть какая-то идея? Присоединяйтесь и вы!

Читать дальше…

16 июля 2009 г.

Информация о медиафайлах

В недавнем посте об отображении тегов mp3-файлов рассказывалось об утилите mp3info. К сожалению, она обладает существенными недостатками: не понимает ID3v2 и работает, как следует из названия, только с mp3-файлам.

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

Естественно, писал я его не на пустом месте. Для работы скрипта потребуется библиотека Kaa.Metadata:
sudo aptitude install python-kaa-metadata

Параметров у скрипта всего два (для простоты): форматная строка и файл (или несколько файлов через пробел):
mediainfo.py ФОРМАТ файл(ы)

При указании файла можно использовать стандартные шаблоны. Так что вполне допустимо имя вида «*.mp3». Можно использовать и более сложные шаблоны: «~/Музыка/*/*.mp3».

Скрипт не умеет рекурсивно обходить каталоги, но это ему и не нужно. Обход можно реализовать, например, средствами bash.

Подробнее о форматной строке. В ней можно использовать следующие шаблоны:
%t — название,
%a — исполнитель,
%r — частота дискретизации,
%l — продолжительность,
%m — продолжительность (только минуты),
%s — продолжительность (только секунды)
%c — кодек,
%b — битрейт,
%n — номер трека,
%A — альбом,
%g — жанр,
%% — символ %.

В ней также можно использовать обычные питоновские экранированные последовательности: \n, \t и т. д.

Вставлять \n в конце форматной строки не нужно, это делается автоматически. Кроме того, вывод скрипта автоматически переконвертируется в кодировку текущей локали.

Если теги файла записаны в однобайтовой кодировке и формат тегов не предусматривает указания кодировки, то скорее всего вместо ожидаемой cp1251 будет latin-1. Впрочем, ответственность за это лежит на библиотеке. Может, это можно настроить, я глубоко не вникал. Во всяком случае, это еще один повод перевести все теги в utf-8.

Утилиту можно использовать как саму по себе, так и в конвейере:
mediainfo.py '%l' ~/Музыка/*/* | awk '{s += $1} END {print "≈",int(s/3600),"hour(s)"}'

А вот, собственно, исходный текст скрипта:
#!/usr/bin/env python
#-*- coding:utf-8 -*-

# Disable all warnings
import warnings
warnings.filterwarnings('ignore')

# Disable log messages
#import logging
#logger = logging.getLogger('metadata')
#logger.setLevel(logging.CRITICAL)

# Get locale encoding
import locale
encoding = locale.getdefaultlocale()[1]

import os
import sys
import glob
import string
import kaa.metadata

class MyTemplate(string.Template): delimiter = '%'

def addItem(infolist, info):
item = {}
item['t'] = info.title # Title
item['a'] = info.artist # Artist
item['r'] = info.samplerate # Samplerate
item['l'] = int(info.length) # Length (seconds)
item['m'] = int(item['l'] / 60) % 60 # Length (minutes only)
item['s'] = format(item['l'] % 60, '02d') # Length (seconds only)
item['c'] = info.codec # Codec
item['b'] = info.bitrate # Bitrate
item['n'] = info.trackno # Track number
item['A'] = info.album # Album title
item['g'] = info.genre # Genre
infolist.append(item)

def process(path):
infolist = []
path = os.path.normpath(path)
for f in glob.glob(os.path.expanduser(path)):
if os.path.isdir(f):
continue
try:
info = kaa.metadata.parse(f)
except:
info = None
if info != None:
addItem(infolist, info)
return infolist

if len(sys.argv) < 3:
print 'Usage:'
print os.path.split(sys.argv[0])[1], 'FORMAT file(s)'
exit(0)
for f in sys.argv[2:]:
infolist = process(f)
for item in infolist:
sys.stdout.write(MyTemplate(sys.argv[1]).safe_substitute(item).encode(encoding) + '\n')

Не забываем разрешить его выполнение:
chmod +x mediainfo.py


Читать дальше…

15 июля 2009 г.

Информация об mp3-файлах

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

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

В этом случае на помощь приходит пакет mp3info.

В Ubuntu устанавливается как обычно:

sudo aptitude install mp3info

У него довольно много возможностей по редактированию и отображению тегов. Подробности можно узнать в man. Для того, чтобы получить список песен в альбоме можно воспользоваться следующей командой:

mp3info -p 'ФОРМАТ' *.mp3

Она выведет данные об mp3-файлах в текущем каталоге в соответствии со строкой ФОРМАТ.

В форматной строке можно использовать как обычные символы (они выведутся как есть), так и специальные шаблоны. Вот список некоторых из них:
%f — имя файла без пути,
%F — имя файла с путём,
%k — размер файла в килобайтах,
%a — исполнитель,
%g — жанр,
%l — альбом,
%n — номер трека,
%t — название трека,
%y — год,
%q — частота сэмплирования (в кГц),
%r — битрейт,
%m — продолжительность (только минуты),
%s — продолжительность (только секунды),
%% — знак процента,
\n — перевод строки,
\t — горизонтальная табуляция,
\\ — обратный слэш.

Например, команда
mp3info -p '%n. %a - %t (%m:%s)\n' *.mp3

выведет на экран что-то вроде:
01. Allison Crowe - It Came Upon the Midnight Clea (0:42)
02. Allison Crowe - Silent Night (4:2)
03. Allison Crowe - In the Bleak Midwinter (4:3)
04. Allison Crowe - What Child Is This (4:4)
05. Allison Crowe - The First Noel (5:34)
06. Allison Crowe - O Holy Night (4:16)

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

Просто по старой виндузячьей традиции теги хранятся в кодировке cp1251. Я все перекодирую при помощи easytag, но если файлик с такими тегами попался, то можно использовать iconv. Например, так:
mp3info -p '%a - %t \n'  *.mp3 |iconv -f cp1251

Следует помнить, что mp3info работает только с тегами ID3v1.1.

Читать дальше…

13 июля 2009 г.

Пишем пищалку для будильника

Часто мне не хватает удобного и простого будильника под Linux. Органайзеры со всплывающими окошками и проигрыванием заданной мелодии явно избыточны для того, чтобы просто не забыть про чайник. А старый добрый beep я использовать не могу по причине отсутствия PC-Speaker. Можно бы, конечно, поискать подходящую программу, но проще написать её самому. Тем более, программирование звука — это просто!

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

Чтобы проигать какой-то звук я решил использовать библиотеку libao — очень простую в использовании и удобную. При этом ещё и кроссплатформенную. К слову, она поддерживает ALSA, ESD, OSS, Pulse, чего должно хватить с головой.

На родном сайте есть ссылки для скачивания и подробная документация. Пользователи Debian/Ubuntu могут установить библиотеку обычным способом

sudo aptitude install libao2
sudo aptitude install libao-dev

Пакет libao2 — это сама библиотека, он нужен будет для работы программы, а libao-dev — это заголовочные файлы.

Библиотека написана на чистом C, поэтому для удобства можно обернуть все необходимые функции в класс¸ а потом, если потребуется, выделить в отдельную библиотеку. Удобство может показаться сомнительным, но лично мне в основной программе не хочется вводить посторонние переменные и писать функции для открытия/закрытия устройства. Да и забыть могу. А так всё, что нужно, прописано в конструкторе и деструкторе, а значит вызывается само собой.

Работу с библиотекой можно разбить на три этапа:

  1. инициализация, настройка формата вывода, открытие устройства;

  2. воспроизведение звука;

  3. закрытие устройства и завершение работы с библиотекой.


Класс можно написать, например, такой:

class Beeper {
private:
ao_device *device;
int driver;
ao_sample_format format;

public:
Beeper();
void operator() (double freq, double time, double volume = 0.75);
~Beeper();
};

С конструктором и деструктором всё понятно, а вот перегруженная операция вызова функции — это и есть метод класса, который издает звуки. Ему передается всего три параметра: freq — частота звучания в герцах, time — время звучания в секундах, volume — громкость (от 0.0 до 1.0).

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

Можно взять синусоиду, тогда мы получим чистый тон. Частота колебаний определяет высоту звука, а амплитуда — громкость. Тут всё просто. На практике же мы сталкиваемся со следующей проблемой: компьютер-то работает не с математическими формулами, а с числами.

Но и тут проблема легко решается. Звуковая карта получает на вход последовательность чисел, задающих амплитуду через некоторые малые промежутки времени. Количество таких отсчетов амплитуды в секунду называется частотой дискретизации. Типичное значение — это 44100 Гц. Почему именно такое число? Дело в том, что человек может слышать звуки с частотой не выше ≈20000 Гц. А по теореме Котельникова, чтобы точно передать сигнал с какой-то частотой, нужна минимум в два раза большая частота дискретизации. Хотя, для низких звуков такая высокая частота — излишество.

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

Разрядность говорит, сколько бит тратится на один отсчет амплитуды. Чем больше разрядность, тем точнее передается звук. Но для обычных нужд хватит и 16 бит, то бишь двух байт. Тут возникает такой вопрос: а как хранить эти два байта? Сначала старший или младший? Это старая компьютерная проблема. Я же решил не заморачиваться и сначала помещаю младший байт, а потом старший (так называемый little-endian порядок байтов). Для указания, что используется именно такой параметр, соответствующему полю структуры, отвечающей за формат сигнала присваивается значение AO_FMT_LITTLE.

Разбить двубайтовой целое на два однобайтовых легко. Для этого можно использовать побитовое И с числом 0xFF. Это даст младщий байт, так как старшие биты будут умножены на 0. А потом можно сдвинуть все старшие биты в исходном числе на 8 бит влево и повторить процедуру.

Для 16 битного звука значения амплитуды должны быть в диапазоне от -32768 до 32767. Эти крайние величины соответствуют максимальной громкости. Если нужен звук потише, то амплитуду можно уменьшить. Амплитуда, равная 0 — это тишина.

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

Итак, как издать звук. Для этого в памяти создается буфер, чтоб не отправлять каждый байт поодиночке. Буфер должен иметь размер t×F×b, где t — это время звучания, F — частота дискретизации, а b — разрядность в байтах. Потом мы в цикле записываем значения синусоиды с нужной частотой и амплитудой. А затем всё это дело выводится на устройство. Подробности можно посмотреть в коде программы.

Когда класс готов, можно заняться самой пищалкой. В ней просто несколько раз повторяется звук с частотой 880 Гц (это ля второй октавы) и длительностью 0,3 с. Пауза между звукам — 0,2 с. По умолчанию сигнал повторяется 20 раз (то есть 20/(0,3+0,2)=10 с), но можно передать нужную дительность как параметр командной строки.

Как видно, работа с классом очень проста и функция main выглядит ясной и чистой.

После того, как программа скомпилирована, её можно поместить, например, в /usr/local/bin или создать deb-пакет, чтоб потом можно было поделиться с друзьями.

Использовать её просто. Например, нужно подать сигнал в 12:30, а исполняемый файл называется beep. Тогда пишем следующее:

echo "beep" | at 12:30

Если beep лежит в домашней папке, то не забываем указать путь:

echo "~/beep" | at 12:30

У at очень гибкий формат указания времени исполнения, так что рекомендую заглянуть в man.

Можно также всё это оформить в виде скрипта:

#!/bin/sh
echo "~/beep" | at $@

$@ — это специальная переменная, хранящая параметры, переданные скрипту. Так что можно будет просто написать, например:

alarm.sh 12:30


А вот вся программа в одном файле.

#include <ao/ao.h>
#include <cmath>
#include <cstdlib>

#define BITS 16
#define CHANNELS 1
#define RATE 22050

class Beeper {
private:
ao_device *device;
int driver;
ao_sample_format format;

public:
Beeper() {
ao_initialize();
this->driver = ao_default_driver_id();

this->format.bits = BITS;
this->format.channels = CHANNELS;
this->format.rate = RATE;
this->format.byte_format = AO_FMT_LITTLE;

this->device = ao_open_live(driver, &format, NULL);
if (this->device == NULL)
exit(1);
}

void operator() (double freq, double time, double volume = 0.75) {
int buf_size = static_cast<int>(this->format.bits/8 * this->format.channels * this->format.rate * time);
char *buffer = new char[buf_size];
int sample;

for (int i = 0; i < this->format.rate * time; i++) {
for (int c = 0; c < this->format.channels; c++) {
sample = static_cast<int>(volume * (1<<(this->format.bits - 1)) * sin(2 * M_PI * freq * ((float) i/format.rate)));
for (int j = 0; j < this->format.bits/8; j++) {
buffer[this->format.bits/8*i*this->format.channels + this->format.channels*c + j] = sample & 0xff;
sample >>= 8;
}
}
}
ao_play(device, buffer, buf_size);
delete[] buffer;
}

~Beeper() {
ao_close(device);
ao_shutdown();
}
};

int main(int argc, char **argv)
{
Beeper b;

int count = (argc == 1) ? 20 : atoi(argv[1]);

for (int i=0; i < count*2; i++){
b(880.0, 0.3);
b(0.0, 0.2);
}

return 0;
}

Компилируется командой

g++ -lao -o beep beep.cpp

Вот, вкратце всё. Не забываем покритиковать! ;)

Читать дальше…