здесь(2) - Intellect25.com

advertisement
Звук
Как было указано в предыдущей главе, в апплетах реализуется интерфейс Audioclip.
Экземпляр объекта, реализующего этот интерфейс можно получить методом
getAudioClip(), который, кроме того, загружает звуковой файл, а затем пользоваться
методами play о, loop о и stop о этого интерфейса для проигрывания музыки.
Для применения данного же приема в приложениях в класс Applet введен
статический метод newAudioclp(URL address), загружающий звуковой файл,
находящийся по адресу address, и возвращающий объект, реализующий интерфейс
Audioclip. Его можно использовать для проигрывания звука в приложении, если
конечно звуковая система компьютера уже настроена.
В листинге 15.14 приведено простейшее консольное приложение, бесконечно
проигрывающее звуковой файл doom.mid, находящийся в текущем каталоге. Для
завершения приложения требуется применить средства операционной системы,
например, комбинацию клавиш <Ctrl>+<C>.
Листинг 15.14. Простейшее аудиоприложение
import j ava.applet.* ;
import j ava.net.*;
class SimpleAudio{
SimpleAudio () {
try{
AudioClip ac = Applet.newAudioClip(new URL("file:doom.mid"));
ac.loop();
}catch(Exception e){}
}
public static void main(String[] args){
new SimpleAudio();
}
}
Таким способом можно проигрывать звуковые файлы типов AU, WAVE, AIFF, MIDI без
сжатия.
В состав виртуальной машины Java, входящей в SUN J2SDK начиная с версии 1.3,
включено устройство, проигрывающее звук, записанный в одном из форматов AU,
WAVE, AIFF, MIDI, преобразующее, микширующее и записывающее звук в тех же
форматах.
Для работы с этим устройством созданы классы, собранные в пакеты
javax.sound.sampled, javax.sound.midi, javax.sound.sampled.spi и
javax.sound.midi.spi. Перечисленный набор классов для работы со звуком получил
название Java Sound API.
Проигрывание звука в Java 2
Проигрыватель звука, встроенный в JVM, рассчитан на два способа записи звука:
моно и стерео оцифровку (digital audio) с частотой дискретизации (sample rate) от 8
000 до 48 000 Гц и аппроксимацией (quantization) 8 и 16 битов, и MIDIпоследовательности (sequences) типа 0 и 1.
Оцифрованный звук должен храниться в файлах типа AU, WAVE и AIFF. Его можно
проигрывать двумя способами.
Первый способ описан в интерфейсе clip. Он рассчитан на воспроизведение
небольших файлов или неоднократное проигрывание файла и заключается в том,
что весь файл целиком загружается в оперативную память, а затем проигрывается.
Второй способ описан в интерфейсе SourceDataLine. Согласно этому способу файл
загружается в оперативную память по частям в буфер, размер которого можно
задать произвольно.
Перед загрузкой файла надо задать формат записи звука в объекте класса
AudioFormat. Конструктор этого класса:
AudioFormat(float sampleRate, int sampleSize, int channels, boolean signed,
boolean bigEndian)
требует знания частоты дискретизации sampleRate (по умолчанию 44 100 Гц),
аппроксимации sampleSize, заданной в битах (по умолчанию 16), числа каналов
channels (1 — моно, по умолчанию 2 — стерео), запись чисел со знаком, signed ==
true, или без знака, и порядка расположения байтов в числе bigEndian. Такие
сведения обычно неизвестны, поэтому их получают косвенным образом из файла.
Это осуществляется в два шага.
На первом шаге получаем формат файла статическим методом getAudioFiieFormato
класса AudioSystem, на втором — формат записи звука методом getFormato класса
AudioFiieFormat. Это описано в листинге 15.15. После того как формат записи
определен и занесен в объект класса AudioFormat, в объекте класса DataLine. infо
собирается информация о входной линии (line) и способе проигрывания clip или
SourceDataLine. Далее следует проверить, сможет ли проигрыватель обслуживать
линию с таким форматом. Затем надо связать линию с проигрывателем статическим
методом getLine () класса AudioSystem. Потом создаем поток данных из файла —
объект класса Audioinputstream. Из этого потока тоже можно извлечь объект класса
AudioFormat методом getFormat (). Данный вариант выбран в листинге 15.16.
Открываем созданный поток методом орепо.
У-фф! Все готово, теперь можно начать проигрывание методом start (), завершить
методом stop(), "перемотать" в начало методом setFramePosition(0) ИЛИ
setMillisecondPosition(0).
Можно задать проигрывание п раз подряд методом loop(n) или бесконечное число
раз методом loop (Clip.LOOP_CONTINUOUSLY) . Перед этим необходимо установить
начальную n и конечную m позиции повторения методом setLoopPoints(n, m).
По окончании проигрывания следует закрыть линию методом close ().
Вся эта последовательность действий показана в листинге 15.15.
Листинг 15.15. Проигрывание аудиоклипа
import javax.sound.sampled.*;
import java.io.*;
class PlayAudio{
PlayAudio(String s){
play(s);
}
public void play(String file){
Clip line = null;
try{
// Создаем объект, представляющий файл
File f = new File (file);
// Получаем информацию о способе записи файла
AudioFileFormat aff = AudioSystem.getAudioFileFormat(f);
// Получаем информацию о способе записи звука
AudioFormat af = aff.getFormat();
// Собираем всю информацию вместе,
// добавляя сведения о классе
Class DataLine.Infо info = new DataLine.Info(Clip.class, af) ;
// Проверяем, можно ли проигрывать такой формат
if (!AudioSystem.isLineSupported(info)){
System.err.printlnt"Line is not supported");
System.exit(0);
}
// Получаем линию связи с файлом
line = (Clip)AudioSystem.getLine(info);
// Создаем поток байтов из файла
AudioInputStream ais - AudioSystem.getAudioInputStream(f);
// Открываем линию
line.open(ais);
}catch(Exception e){
System.err.println(e);
}
// Начинаем проигрывание
line.start();
// Здесь надо сделать задержку до окончания проигрывания
// или остановить его следующим методом:
line.stop();
//По окончании проигрывания закрываем линию
line.close();
}
public static void main(String[] args){
if (args.length != 1)
System.out.printlnt"Usage: Java PlayAudio filename");
new PlayAudio(args[0]);
}
}
Как видите, методы Java Sound API выполняют элементарные действия, которые
надо повторять из программы в программу. Как говорят, это методы "низкого
уровня" (low level).
Второй способ, использующий методы интерфейса SourceDataLine, требует
предварительного создания буфера произвольного размера.
Листинг 15.16. Проигрывание аудиофайла
import javax.sound.sampled.*;
import j ava.io.*;
class PlayAudioLine(
PlayAudioLine(String s){
play(s);
}
public void play(String file){
SourceDataLine line = null;
AudioInputStream ais = null;
byte[] b = new byte[2048]; // Буфер данных
try{
File f = new File(file);
// Создаем входной поток байтов из файла f
ais = AudioSystem.getAudioInputStream(f);
// Извлекаем из потока информацию о способе записи звука
AudioFormat af = ais.getFormat () ;
// Заносим эту информацию в объект info
DataLine.Infо info = new DataLine.Infо(SourceDataLine.class, af);
// Проверяем, приемлем ли такой способ записи звука
if (!AudioSystem.isLineSupported(info)){
System.err.println("Line is not supported");
System.exit(0);
}
// Получаем входную линию
line = (SourceDataLine)AudioSystem.getLine(info);
// Открываем линию
line.open(af);
// Начинаем проигрывание
line.start(); // Ждем появления данных в буфере int num = 0;
// Раз за разом заполняем буфер
while(( num = ais.read(b)) != -1)
line.write(b, 0, num);
// "Сливаем" буфер, проигрывая остаток файла
line.drain();
// Закрываем поток
ais.close();
} catch (Exception e) {
System, err.println (e);
}
// Останавливаем проигрывание
line.stop();
// Закрываем линию
line.close();
}
public static void main(String[] args){
String s = "mrmba.aif";
if (args.length > 0) s = args[0];
new PlayAudioLine(s) ;
}
}
Управлять проигрыванием файла можно с помощью событий. Событие класса
LineEvent происходит при открытии, OPEN, и закрытии, CLOSE, потока, при начале,
START, и окончании, STOP, проигрывания. Характер события отмечается указанными
константами. Соответствующий интерфейс LineListener описывает только один метод
update ().
В MIDI-файлах хранится последовательность (sequence) команд для секвен-сора
(sequencer) — устройства для записи, проигрывания и редактирования MlDIпоследовательности, которым может быть физическое устройство или программа.
Последовательность состоит из нескольких дорожек (tracks), на которых записаны
MIDI-события (events). Каждая дорожка загружается в своем канале (channel).
Обычно дорожка содержит звучание одного музыкального инструмента или запись
голоса одного исполнителя или запись нескольких исполнителей, микшированную
синтезатором (synthesizer).
Для проигрывания MIDI-последовательности в простейшем случае надо создать
экземпляр секвенсора, открыть его и направить в него последовательность,
извлеченную из файла, как показано в листинге 15.17. После этого следует начать
проигрывание методом start (). Закончить проигрывание можно методом stop(),
"перемотать" последовательность на начало записи или на указанное время
проигрывания — методами setMicrosecondPositionflong mcs) или setTickPosition(long
tick).
Листинг 15.17. Проигрывание MIDI-последовательности
import javax.sound.midi.*;
import j ava.io.*;
class PlayMIDK
PlayMIDKString s) {
play(s);
}
public void play(String file){
try{
File f = new File(file);
// Получаем секвенсор по умолчанию
Sequencer sequencer = MidiSystem.getSequencerО;
// Проверяем, получен ли секвенсор
if (sequencer = null) {
System.err.println("Sequencer is not supported");
System.exit(0);
}
// Открываем секвенсор
sequencer.open();
// Получаем MIDI-последовательность из файла
Sequence seq = MidiSystem.getSequence(f);
// Направляем последовательность в секвенсор
sequencer.setSequence(seq);
// Начинаем проигрывание
sequencer.start();
// Здесь надо сделать задержку на время проигрывания,
// а затем остановить:
sequencer.stop();
)catch(Exception e){
System.err.println(e);
}
}
public static void main(String[] args){
String s = "doom.mid";
if (args.length > 0) s = args[0];
new PlayMIDI(s);
}
}
Синтез и запись звука в Java 2
Синтез звука заключается в создании MIDI-последовательности — объекта класса
sequence — каким-либо способом: с микрофона, линейного входа, синтезатора, из
файла, или просто создать в программе, как это делается в листинге 15.18.
Сначала создается пустая последовательность одним из двух конструкторов:
Sequence(float divisionType, int resolution)
Sequence(float divisionType, int resolution, int numTracks)
Первый аргумент divisionType определяет способ отсчета моментов (ticks) MIDIсобытий — это одна из констант:

PPQ (Pulses Per Quarter note) — отсчеты замеряются в долях от длительности

звука в четверть;
SMPTE_24, SMPTE_25, SMPTE_so, SMPTE_30DROP (Society of Motion Picture and
Television Engineers) — отсчеты в долях одного кадра, при указанном числе
кадров в секунду.
Второй аргумент resolution задает количество отсчетов в указанную единицу,
например,
Sequence seq = new Sequence)Sequence.PPQ, 10);
задает 10 отсчетов в звуке длительностью в четверть.
Третий аргумент numTracks определяет количество дорожек в MIDI-последовательности.
Потом, если применялся первый конструктор, в последовательности создается одна
или несколько дорожек:
Track tr = seq.createTrack();
Если применялся второй конструктор, то надб получить уже созданные
конструктором дорожки:
Track[] trs = seq.getTracks();
Затем дорожки заполняются MIDI-событиями с помощью MIDl-сообще-ний. Есть
несколько типов сообщений для разных типов событий. Наиболее часто встречаются
сообщения типа shortMessage, которые создаются конструктором по умолчанию и
потом заполняются методом setMessageo:
ShortMessage msg = new ShortMessage();
rasg.setMessage(ShortMessage.NOTEJDN, 60, 93);
Первый аргумент указывает тип сообщения: NOTE_ON — начать звучание, NOTE_OFF —
прекратить звучание и т. д. Второй аргумент для типа NOTE_ОN показывает высоту
звука, в стандарте MIDI это числа от 0 до 127, 60 — нота "до" первой октавы. Третий
аргумент означает "скорость" нажатия клавиши MIDI-инструмента и по-разному
понимается различными устройствами.
Далее создается MIDI-событие:
MidiEvent me = new MidiEvent{msg, ticks);
Первый аргумент конструктора msg — это сообщение, второй аргумент ticks — время
наступления события (в нашем примере проигрывания ноты "до") в единицах
последовательности seq (в нашем примере в десятых долях четверти). Время
отсчитывается от начала проигрывания последовательности.
Наконец, событие заносится на дорожку:
tr.add(me);
Указанные действия продолжаются, пока все дорожки не будут заполнены всеми
событиями. В листинге 15.18 это делается в цикле, но обычно MIDI-события
создаются в методах обработки нажатия клавиш на обычной или специальной MIDIклавиатуре. Еще один способ — вывести на экран изображение клавиатуры и
создавать MIDI-собьгшя в методах обработки нажатий кнопки мыши на этой
клавиатуре.
После создания последовательности ее можно проиграть, как в листинге 15.17, или
записать в файл или выходной поток. Для этого вместо метода start() надо
применить метод startRecording (), который одновременно и проигрывает
последовательность, и подготавливает ее к записи, которую осуществляют
статические методы:
write(Sequence in, int type, File out)
write(Sequence in, int type, OutputStream out)
Второй аргумент type задает тип MIDI-файла, который лучше всего определить для
заданной последовательности seq статическим методом getMidiFiieTypes(seq).
Данный метод возвращает массив возможных типов. Надо воспользоваться нулевым
элементом массива, ,Все это. показало в листинге 15.18.
Листинг 15.18. Создание MIDI-последовательности нот звукоряда
import javax.sound.midi. *;
import java.io.*;
class SynMIDI {
SynMIDI() {
play(synth());
}
public Sequence synth(){
Sequence seq = null;
try{
// Последовательность будет отсчитывать по 10
// MIDI-событий на Звук длительйостью в четверть
seq = new Sequence(Sequence.PPQ, 10);
// Создаем в последовательности одну дорожку
Track tr = seq.createTrack();
for (int k = 0; k < 100; k++){
ShortMessage msg = new ShortMessage();
// Пробегаем MIDI-ноты от номера 10 до 109
msg.setMessage(ShortMessage.NOTE_ON, 10+k, 93);
// Будем проигрывать ноты через каждые 5 отсчетов
tr.add(new MidiEvent(msg, 5*k));
msg = null;
}
} catch (Exception e) {
System, err.printing "From synth(): "+e);
System.exit (0);
}
return seq;
}
public void play (Sequence seq) {
try{
Sequencer sequencer = MidiSystem.getSequencer();
if (sequencer = null){
System.err.println("Sequencer is not supported");
System.exit(0);
}
sequencer.open();
sequencer.setSequence(seq);
sequencer.startRecording();
int[] type = MidiSystem.getMidiFileTypes(seq);
MidiSystem.write(seq, type[0], new File("gammas.mid"));
}catch(Exception e) {
System.err.println("From play(): " + e);
}
}
public static void main(String[] args)(
new SynMIDI();
}
}
К сожалению, объем книги не позволяет коснуться темы о работе с синтезатором
(synthesizer), микширования звука, работы с несколькими инструментами и прочих
возможностей Java Sound API. В документации SUN J2SDK, в каталоге
docs\guide\sound\prog_guide, есть подробное руководство программиста, а в
каталоге demo\sound\src лежат исходные тексты синтезатора, использующего Java
Sound API.
Download