В предыдущих главах обсуждалось, как воспроизводить или записывать звуковые образцы. Неявная цель заключалась в том, чтобы доставить сэмплы как можно точнее, без изменений (кроме возможного смешивания сэмплов с сэмплами из других звуковых линий). Однако иногда вам может понадобиться изменить сигнал. Пользователь может захотеть, чтобы он звучал громче, тише, полнее, реверберируем, выше или ниже по высоте и так далее. В этой главе обсуждаются функции Java Sound API, которые обеспечивают такие виды обработки сигналов.

Есть два способа применить обработку сигнала:

  • Вы можете использовать любую обработку, поддерживаемую микшером или его линиями компонентов, запрашивая объекты Control и затем устанавливая элементы управления по желанию пользователя. Типичные элементы управления, поддерживаемые микшерами и линиями, включают элементы управления усилением, панорамированием и реверберацией.
  • Если необходимая вам обработка не обеспечивается микшером или его линиями, ваша программа может работать непосредственно с байтами звука, манипулируя ими по желанию.

В этой главе более подробно обсуждается первый метод, поскольку для второго метода не существует специального API.

Введение в элементы управления

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

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

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

Если микшер или любая из его линий имеют элементы управления, вы можете предоставить им доступ через графические объекты в пользовательском интерфейсе вашей программы, чтобы пользователь мог настроить звуковые характеристики по своему желанию. Элементы управления сами по себе не являются графическими; они просто позволяют вам извлекать и изменять их настройки. Вам решать, какие графические представления (ползунки, кнопки и т. д.), если таковые имеются, использовать в вашей программе.

Все элементы управления реализованы как конкретные подклассы абстрактного класса Control. Многие типичные элементы управления обработкой звука могут быть описаны абстрактными подклассами Control на основе типа данных (например, логического, перечисляемого или плавающего). Логические элементы управления, например, представляют элементы управления с двоичным состоянием, такие как элементы управления включением / выключением для отключения звука или реверберации. С другой стороны, плавающие элементы управления хорошо подходят для представления постоянно изменяемых элементов управления, таких как панорама, баланс или громкость.

Java Sound API определяет следующие абстрактные подклассы Control:

  • BooleanControl—представляет собой элемент управления с двоичным (true или false) состоянием. Например, переключатели отключения звука, соло и включения / выключения могут быть хорошими кандидатами для BooleanControls.
  • FloatControl—модель данных, обеспечивающая контроль над диапазоном значений с плавающей запятой. Например, объем и панорама - это элементы управления FloatControls, которыми можно управлять с помощью шкалы или слайдера.
  • EnumControl—предлагает выбор из множества предметов. Например, вы можете связать набор кнопок в пользовательском интерфейсе с EnumControl, чтобы выбрать одну из нескольких предустановленных настроек реверберации.
  • CompoundControl—предоставляет доступ к набору связанных элементов, каждый из которых сам является экземпляром подкласса Control. CompoundControls представляют собой многофункциональные модули управления, такие как графические эквалайзеры. (Графический эквалайзер обычно изображается набором ползунков, каждый из которых влияет на FloatControl.)

Каждый подкласс Control выше имеет методы, соответствующие его базовому типу данных. Большинство классов включают методы, которые устанавливают и получают текущее значение (я) элемента управления, получают метку (метки) элемента управления и т. д.

Конечно, у каждого класса есть свои методы и модель данных, представленная этим классом. Например, EnumControl имеет метод, который позволяет вам получить набор его возможных значений, а FloatControl позволяет вам получить его минимальное и максимальное значения, а также точность (приращение или размер шага) элемента управления.

Каждый подкласс Control имеет соответствующий подкласс Control.Type, который включает статические экземпляры, идентифицирующие определенные элементы управления.

В следующей таблице показаны каждый подкласс Control, соответствующий ему подкласс Control.Type и статические экземпляры, указывающие определенные виды элементов управления:

ControlControl.TypeControl.Type экземпляры
BooleanControl BooleanControl.Type

MUTE

Отключить статус линии

APPLY_REVERB

Включение / выключение реверберации

CompoundControl CompoundControl.Type (none)
EnumControl EnumControl.Type REVERB

Доступ к настройкам реверберации (каждая из них является экземпляром ReverbType)

 

FloatControl FloatControl.Type AUX_RETURN

Дополнительное усиление возврата на линии

AUX_SEND

Усиление вспомогательного посыла на линии

BALANCE

Баланс громкости слева и справа

MASTER_GAIN

Общий выигрыш на линии

PAN

Лево-правое положение

REVERB_RETURN

Усиление после реверберации на линии

REVERB_SEND

Усиление до реверберации на линии

SAMPLE_RATE

Частота дискретизации воспроизведения

VOLUME

Громкость на линии

 

Реализация Java Sound API может предоставлять любой или все эти типы управления на своих микшерах и линиях. Он также может предоставлять дополнительные типы управления, не определенные в Java Sound API. Такие типы элементов управления могут быть реализованы через конкретные подклассы любого из этих четырех абстрактных подклассов или через дополнительные подклассы Control, которые не наследуются ни от одного из этих четырех абстрактных подклассов. Прикладная программа может запрашивать каждую линию, чтобы найти, какие элементы управления она поддерживает.

Получение линии с желаемыми элементами управления

Во многих случаях прикладная программа просто отображает все элементы управления, которые поддерживаются данной линией. Если на линии нет элементов управления, пусть будет так. Но что, если важно найти линию, в которой есть определенные элементы управления? В этом случае вы можете использовать Line.Info для получения линии с нужными характеристиками, как описано в разделе "Получение линии желаемого типа" в главе 3 "Доступ к ресурсам аудиосистемы"

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

Port lineIn;
FloatControl volCtrl;
try {
  mixer = AudioSystem.getMixer(null);
  lineIn = (Port)mixer.getLine(Port.Info.LINE_IN);   lineIn.open();
  volCtrl = (FloatControl) lineIn.getControl(
      FloatControl.Type.VOLUME);
  // Предполагая, что вызов getControl завершился успешно,
  // теперь у нас есть элемент управления LINE_IN VOLUME.
} catch (Exception e) {
  System.out.println("Failed trying to find LINE_IN"
    + " VOLUME control: exception = " + e);
}
if (volCtrl != null)
  // ...

Получение управления с линии

Прикладная программа, которой необходимо отображать элементы управления в своем пользовательском интерфейсе, может просто запросить доступные линии и элементы управления, а затем отобразить соответствующий элемент пользовательского интерфейса для каждого элемента управления в каждой интересующей линии. В таком случае единственная задача программы - предоставить пользователю «ручки» элемента управления; не знать, что эти элементы управления делают со звуковым сигналом. Пока программа знает, как отображать элементы управления линией в ​​элементы пользовательского интерфейса, архитектура Java Sound API Mixer, Line и Control, как правило, позаботится обо всем остальном.

Например, предположим, что ваша программа воспроизводит звук. Вы используете SourceDataLine, который вы получили, как описано в разделе "Получение линии желаемого типа" в главе 3 "Доступ к ресурсам аудиосистемы".  Вы можете получить доступ к элементам управления линией, вызвав метод класса Line:

Control[] getControls()

Затем для каждого элемента управления в этом возвращаемом массиве вы затем используете следующий метод Control, чтобы получить тип элемента управления:

Control.Type getType()

Зная конкретный экземпляр Control.Type, ваша программа может отображать соответствующий элемент пользовательского интерфейса. Конечно, выбор «соответствующего элемента пользовательского интерфейса» для конкретного Control.Type зависит от подхода, используемого вашей программой. С одной стороны, вы можете использовать один и тот же элемент для представления всех экземпляров Control.Type одного и того же класса. Для этого вам потребуется запросить класс экземпляра Control.Type, используя, например, метод Object.getClass. Допустим, результат соответствует BooleanControl.Type. В этом случае ваша программа может отображать общий флажок или кнопку переключения, но если ее класс соответствует FloatControl.Type, вы можете отобразить графический слайдер.

С другой стороны, ваша программа может различать разные типы элементов управления - даже принадлежащие к одному классу - и использовать разные элементы пользовательского интерфейса для каждого из них. Для этого вам потребуется протестировать экземпляр, возвращаемый методом getType  класса Control. Затем, если, например, тип соответствует BooleanControl.Type.APPLY_REVERB, ваша программа может отобразить флажок; в то время как, если тип соответствует BooleanControl.Type.MUTE, вы можете вместо этого отобразить кнопку переключения.

Использование элемента управления для изменения аудиосигнала

Примечание

Текущая реализация требует, чтобы для изменения значения Control была открыта соответствующая ему Line.

Теперь, когда вы знаете, как получить доступ к элементу управления и определить его тип, в этом разделе будет описано, как использовать Controls для изменения аспектов аудиосигнала. В этом разделе не рассматриваются все доступные элементы управления; скорее, он предоставляет несколько примеров в этой области, чтобы показать вам, как начать работу. Эти примеры включают:

  • Управление беззвучным состоянием линии
  • Изменение громкости линии
  • Выбор среди различных предустановок реверберации

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

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

Управление отключенным состоянием линии

Для управления отключением звука любой линии достаточно вызвать следующий метод класса BooleanControl:

void setValue(boolean value)

(Предположительно, программа знает, обращаясь к своим структурам данных управления, что отключение звука является экземпляром BooleanControl.) Чтобы отключить сигнал, проходящий через линию, программа вызывает метод выше, указав true в качестве значения . Чтобы отключить приглушение и разрешить прохождению сигнала по линии, программа вызывает метод с параметром, установленным в false.

Изменение громкости линии

Предположим, ваша программа связывает определенный графический слайдер с регулятором громкости конкретной линии. Значение регулятора громкости (например, FloatControl.Type.VOLUME) устанавливается с помощью следующего метода класса FloatControl:

void setValue(float newValue)

Обнаружив, что пользователь переместил ползунок, программа получает текущее значение ползунка и передает его в качестве параметра newValue указанному выше методу. Это изменяет громкость сигнала, проходящего через линию, которая «владеет» элементом управления.

Выбор среди различных предустановок реверберации

Предположим, что в нашей программе есть микшер со линией, имеющей элемент управления типа EnumControl.Type.REVERB. Вызов метода EnumControl:

java.lang.Objects[] getValues() 

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

int getDecayTime() 
int getEarlyReflectionDelay() 
float getEarlyReflectionIntensity() 
int getLateReflectionDelay() 
float getLateReflectionIntensity() 

Например, если программе нужен только один параметр реверберации, который звучит как каверна, она может перебирать объекты ReverbType, пока не найдет тот, для которого getDecayTime возвращает значение больше 2000. Для подробного объяснения этих методов, включая таблицу типичных возвращаемых значений см. в справочной документации API для javax.sound.sampled.ReverbType.

Однако обычно программа создает элемент пользовательского интерфейса, например радиокнопку, для каждого из объектов ReverbType в массиве, возвращаемом методом getValues. Когда пользователь нажимает на один из этих переключателей, программа вызывает метод EnumControl.

void setValue(java.lang.Object value) 

где value установлено значение ReverbType, которое соответствует вновь задействованной кнопке. Аудиосигнал, отправленный через линию, которая «владеет» этим EnumControl, затем будет реверберироваться в соответствии с настройками параметров, которые составляют текущий ReverbType элемента управления (то есть конкретный ReverbType, указанный в аргументе value метода setValue).

Таким образом, с точки зрения нашей прикладной программы, возможность перехода пользователя от одной предустановки реверберации (например, ReverbType) к другой - это просто вопрос подключения каждого элемента массива, возвращаемого getValues, к отдельному переключателю.

Непосредственное управление аудиоданными

Control API позволяет реализовать Java Sound API или сторонний поставщик микшера, чтобы предоставлять произвольные виды обработки сигналов с помощью элементов управления. Но что, если ни один микшер не предлагает нужную вам обработку сигнала? Это потребует больше работы, но, возможно, вы сможете реализовать обработку сигналов в своей программе. Поскольку Java Sound API предоставляет вам доступ к аудиоданным в виде массива байтов, вы можете изменять эти байты по своему усмотрению.

Если вы обрабатываете входящий звук, вы можете читать байты из TargetDataLine и затем манипулировать ими. Алгоритмически тривиальный пример, который может дать интригующие с точки зрения звука результаты, - это воспроизведение звука в обратном направлении, располагая его кадры в обратном порядке. Этот тривиальный пример может быть не очень полезен для вашей программы, но существует множество сложных методов цифровой обработки сигналов (DSP), которые могут быть более подходящими. Некоторые примеры - это эквализация, сжатие динамического диапазона, ограничение пиков, растяжение или сжатие по времени, а также специальные эффекты, такие как задержка, хорус, флэнжер, искажение и т. Д.

Чтобы воспроизвести обработанный звук, вы можете поместить управляемый массив байтов в SourceDataLine или Clip. Конечно, массив байтов не обязательно должен быть получен из существующего звука. Вы можете синтезировать звуки с нуля, хотя для этого требуются некоторые знания в области акустики или доступ к функциям синтеза звука. Для обработки или синтеза вы можете обратиться к учебнику по аудио DSP, чтобы узнать об алгоритмах, которые вас интересуют, или же импортировать стороннюю библиотеку функций обработки сигналов в свою программу. Для воспроизведения синтезированного звука подумайте, соответствует ли API-интерфейс Synthesizer в пакете javax.sound.midi вашим потребностям. (См. Главу 12, "Синтезирование звука.")

 

Предыдущая Следующая