Статьи

Генерация цифрового аудио с использованием SiON

В этом уроке я покажу вам, как начать работу с SiON, библиотекой программного синтезатора AS3, которая генерирует звук, используя только код.


В конце концов это то, что мы собираемся получить:

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


Сначала вам нужно получить библиотеку SiON. Вы можете скачать его как файл SWC или как несжатый файл ActionScript. Для этого перейдите в раздел SiON Downloads и выберите нужный метод загрузки.

После того, как вы загрузили исходный код, добавьте его в глобальный путь к классу.

Обратите внимание, что на этой странице вы также можете скачать документацию ASDoc и более старые версии библиотеки.

В этом уроке мы будем использовать хорошо известную библиотеку minimalcomps , разработанную Keith Peters; если у вас его нет, то возьмите его: минимальный комплект .

Также добавьте библиотеку minimalcomps в ваш глобальный путь к классу, и начнем.

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


Библиотека SiON — это библиотека программного синтезатора, встроенная в ActionScript 3.0 и работающая в Flash Player 10 или более поздней версии .

С SiON вы можете генерировать динамические звуки на ходу без необходимости загружать любые аудиофайлы. Также очень легко синхронизировать звуки с отображаемыми объектами (например, объект, падающий на стену, взрыв и т. Д.).

Из множества функций, которые он имеет, я покажу вам основы работы с ним: использование данных MML (Music Macro Language) для генерации звука, использование голосовых пресетов и эффекторов для воспроизведения звуков, установка темпа (BPM), панорамирование и изменение громкость и, наконец, я покажу вам, как синхронизировать звуки с экранными объектами.


Давайте начнем с создания нового проекта. Откройте редактор кода и создайте новый проект ActionScript 3.

Новый проект

Я назвал свой проект SiON Tutorial . После этого откройте класс документа (в моем случае Main ).

У вас должно быть что-то похожее на это:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package
{
    import flash.display.Sprite;
     
    [SWF(width = 550, height = 300, backgroundColor = 0x1f1f1f, frameRate = 30)]
    public class Main extends Sprite
    {
         
        public function Main():void
        {
             
        }
         
    }
     
}

В FlashDevelop у вас, вероятно, будет метод init() вызываемый при добавлении фильма на сцену. Идите вперед и измените код так, чтобы он соответствовал приведенному выше.

Оставьте этот файл открытым и перейдите к следующему шагу.


Чтобы начать использовать SiON и слышать звук, нам нужно создать только один объект: SiONDriver . Этот класс обеспечивает драйвер эмулятора процессора цифровых сигналов SiON, и через этот класс все основные операции предоставляются в виде свойств ( bpm , volume ), методов ( pause() , play() , stop() , fadeIn() и событий (bmp changes). , поток запуска и остановки).

Примечание. В любой момент может быть создан только один экземпляр класса драйвера SiON. Попытка создать несколько экземпляров заставит компилятор выдать ошибку. Чтобы получить существующий экземпляр класса SiONDrive вы можете использовать статическое свойство SiONDriver.mutex .

В ваш класс document (Main) добавьте новую приватную переменную с именем _driver и _driver экземпляр в конструкторе.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
package
{
    import flash.display.Sprite;
    import org.si.sion.SiONDriver;
     
    [SWF(width = 550, height = 300, backgroundColor = 0x1f1f1f, frameRate = 30)]
    public class Main extends Sprite
    {
        private var _driver:SiONDriver;
         
        public function Main():void
        {
            _driver = new SiONDriver();
        }
         
    }
     
}

Для воспроизведения звука необходимо вызвать метод play() объекта SiONDriver и передать в качестве аргумента объект SiONData или строку MML. В нашем примере мы будем использовать строку MML (поскольку объект SiONData самом деле является скомпилированной строкой MML).

1
_driver.play(‘l8 cdefgab<c’);

Добавьте эту строку кода в класс и запустите проект ( Ctrl + Enter, если используется FlashDevelop). Теперь вы должны услышать ноты (восемь нот) от C5 (или Do в октавной пятой) до C6 (или Do в октавной шестой). Но что на самом деле означает l8 cdefgab<c ?


Music Macro Language (или MML) — это язык описания музыки, используемый для упорядочения музыки. Следующие элементы в основном присутствуют в каждой реализации MML (включая SiON):

Примечание. Буквы от a до g соответствуют нотам и воспроизводят соответствующую ноту. Таким образом, c, d, e, f, g, a, b будет иметь следующий эквивалент на планке:

Портативный эквивалент

Чтобы установить длину заметки, вы просто добавляете к ней целое число. Допустим, вы хотите полный c, вы бы написали c1. Если вы хотите записку с восьмью, вы напишите с8 (которая в 8 раз короче с1) и так далее. Если вы не добавляете число к заметке, она использует длину по умолчанию 4 (четверть).

Отдых: это определяет паузу между двумя нотами. Макрос для отдыха — это r . Когда за ним следует целое число, оно представляет длину остальных (пример: r4 будет означать четверть покоя).

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

Пример: l1 cdf l16 cdf

Октава: октавой по умолчанию для ноты (которая определяет высоту ноты) является пятая октава. Вы можете изменить октаву двумя способами:

  • Использование макроса o (за которым следует целое число от 0 до 9) перед заметкой. Например, o7c будет играть ноту C в седьмой октаве.
  • Использование макроса приращения октавы > или декремента < перед заметкой. Например, если вы хотите сыграть ноту C в четвертой октаве, вы должны написать: <c (помните, что октава по умолчанию — пятая).

Острые и плоские ноты: чтобы сыграть резкую ноту, вы добавляете + или # к ноте (например, b+ или b# , оба имеют одинаковый результат), а для воспроизведения плоской ноты вы добавляете a (например, a- ).

Циклы: чтобы воспроизвести цикл, вам нужно [cdb]3 макрос в квадратные скобки (например, [cdb]3 будет переводиться в cdb cdb cdb ). Чтобы указать, сколько раз цикл должен проигрываться, вы добавляете целое число к тексту. Если номер не указан, цикл будет воспроизводиться два раза.

Это основное использование MML. Чтобы узнать больше на эту тему, вы можете посетить вики-страницу здесь . Также посетите справочное руководство по MML для SiON для получения полной документации по MML для SiON.

Примечание. Большую базу примеров песен MML можно найти в приложении MMLTalks . Нажмите на название песни из списка, чтобы воспроизвести его. Чтобы увидеть MML-источник песни, нажмите крайнюю правую кнопку.

Получение источника MML

Итак, я сказал, что объект SiONData самом деле является скомпилированной строкой MML. Чтобы скомпилировать строку MML в объект SiONData вы можете использовать метод compile() класса SiONDriver .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package
{
    import flash.display.Sprite;
    import org.si.sion.SiONData;
    import org.si.sion.SiONDriver;
     
    [SWF(width = 550, height = 300, backgroundColor = 0x1f1f1f, frameRate = 30)]
    public class Main extends Sprite
    {
        private var _driver:SiONDriver;
        private var _data:SiONData;
         
        public function Main():void
        {
            _driver = new SiONDriver();
            _data = _driver.compile(‘l8 cdefgab&lt;c’);
            _driver.play(_data);
        }
         
    }
     
}

Как вы можете видеть сначала, мы используем метод драйверов compile() для компиляции строки MML в объект SiONData после чего мы вызываем метод play() и _data объект _data в качестве аргумента. Как вы можете видеть, это имеет тот же результат, что и раньше, когда использовалась строка.


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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package
{
    import com.bit101.components.PushButton;
    import flash.display.Sprite;
    import flash.events.Event;
    import org.si.sion.SiONData;
    import org.si.sion.SiONDriver;
     
    [SWF(width = 550, height = 300, backgroundColor = 0x1f1f1f, frameRate = 30)]
    public class Main extends Sprite
    {
        private var _driver:SiONDriver;
        private var _s1:SiONData, _s2:SiONData;
         
        public function Main():void
        {
            _driver = new SiONDriver();
            _s1 = _driver.compile(‘l8 cdefgab&lt;c’);
            _s2 = _driver.compile(‘l8 o6c o5bagfedc’);
             
            addSoundButtons();
        }
         
        private function addSoundButtons():void
        {
            var b1:PushButton = new PushButton(this, 10, 10, «Play sound 1», onPlaySound);
            var b2:PushButton = new PushButton(this, b1.x + b1.width + 5, 10, «Play sound 2», onPlaySound);
             
            function onPlaySound(e:Event):void
            {
                if (e.target == b1) _driver.play(_s1);
                if (e.target == b2) _driver.play(_s2);
            }
        }
         
    }
     
}

Запустите код и нажмите одну из двух появившихся кнопок ( Воспроизвести звук 1 или Воспроизвести звук 2 ). Первая кнопка воспроизводит ноты от C5 до C6, а вторая кнопка воспроизводит реверс первого звука. Если вы нажмете одну кнопку и нажмете другую до окончания звука, первый воспроизводимый звук будет остановлен, а второй будет воспроизведен.

Если вы хотите воспроизводить оба звука независимо, вам нужно использовать метод sequenceOn() класса SiONDriver .

Примечание. Некоторые методы (например, sequenceOn() ) будут работать только после SiONDriver метода play() объекта SiONDriver . Если вы попытаетесь вызвать эти методы до этого, компилятор выдаст ошибку.

Чтобы наш код воспроизводил оба звука одновременно, сначала вызовите метод play() в конструкторе непосредственно перед addSoundButtons() .

1
2
_driver.play();
addSoundButtons();

Теперь, когда драйвер активен, мы можем безопасно изменить onPlaySound() следующим образом:

1
2
3
4
5
function onPlaySound(e:Event):void
{
    if (e.target == b1) _driver.sequenceOn(_s1);
    if (e.target == b2) _driver.sequenceOn(_s2);
}

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


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

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

Внесите изменения в выделенные строки из функции onPlaySound() :

01
02
03
04
05
06
07
08
09
10
11
12
13
function onPlaySound(e:Event):void
{
    if (e.target == b1)
    {
        _driver.sequenceOff(1);
        _driver.sequenceOn(_s1, null, 0, 0, 1, 1 );
    }
    if (e.target == b2)
    {
        _driver.sequenceOff(2);
        _driver.sequenceOn(_s2, null, 0, 0, 1, 2 );
    }
}

Как вы, возможно, уже заметили, мы устанавливаем идентификатор дорожки для каждой последовательности (шестой параметр), чтобы мы могли ссылаться на него позже, используя метод sequenceOff() чтобы остановить его.


Думайте о «голосах» как о различных инструментах. Как вы заметили, звуки по умолчанию, воспроизводимые в SiON, немного раздражают, и вы можете захотеть услышать звук с пианино.

К счастью для нас, библиотека SiON имеет несколько предустановленных голосов (если быть точным, 462 голоса в текущей версии).

Все эти голосовые пресеты содержатся в классе SiONPresetVoice и сортируются по 15 категориям.

Голоса

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

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

01
02
03
04
05
06
07
08
09
10
var presets:SiONPresetVoice = new SiONPresetVoice();
             
// Accessing voice by key
var voiceByKey:SiONVoice = presets[«valsound.brass2»];
// Accessing category by key
var categoryByKey:Array = presets[«valsound.wind»];
// Accessing voice by number
var voiceByNo:SiONVoice = categoryByKey[3];
// Accessing category by number
var categoryByNo:Array = presets.categolies[3];

(Да, categolies с categolies L; это не опечатка.)


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

Сначала создайте в своем проекте новый класс с именем VoiceSelector который расширяет класс Sprite .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package
{
    import com.bit101.components.ComboBox;
    import flash.display.Sprite;
    import org.si.sion.utils.SiONPresetVoice;
     
    public class VoiceSelector extends Sprite
    {
        private var _presets:SiONPresetVoice;
        private var _categories:ComboBox;
        private var _voices:ComboBox;
         
        public function VoiceSelector()
        {
            _presets = new SiONPresetVoice();
            _categories = new ComboBox(this, 0, 0, «Select a category»);
             
            _voices = new ComboBox(this, _categories.width + _categories.x + 5, 0, «Select a voice»);
        }
         
    }
 
}

Здесь нам нужна ссылка на объект SiONPresetVoice поэтому мы добавляем новую переменную с именем _presets и _presets экземпляр в конструкторе.

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


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

1
2
3
4
5
private function populateCategories():void
{
    for each (var cat:Array in _presets.categolies)
        _categories.addItem(cat.name);
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package
{
    import com.bit101.components.ComboBox;
    import flash.display.Sprite;
    import flash.events.Event;
    import org.si.sion.utils.SiONPresetVoice;
     
    public class VoiceSelector extends Sprite
    {
        private var _presets:SiONPresetVoice;
        private var _categories:ComboBox;
        private var _voices:ComboBox;
         
        public function VoiceSelector()
        {
            _presets = new SiONPresetVoice();
            _categories = new ComboBox(this, 0, 0, «Select a category»);
            _categories.width = 120;
             
            _voices = new ComboBox(this, _categories.width + _categories.x + 5, 0, «Select a voice»);
            _voices.width = 120;
             
            populateCategories();
            _categories.addEventListener(Event.SELECT, populateVoices);
        }
         
        private function populateCategories():void
        {
            for each (var cat:Array in _presets.categolies)
                _categories.addItem(cat.name);
        }
         
        private function populateVoices(e:Event = null):void
        {
            _voices.removeAll();
            _voices.selectedIndex = 0;
            var voices:Array = _presets[_categories.selectedItem];
            for (var i:int = 0; i < voices.length; i++)
                _voices.addItem(voices[i].name);
        }
         
    }
 
}

Как вы можете видеть в методе populateCategories() мы используем цикл for-each для получения каждой доступной категории.

В методе populateVoices() нам нужно сначала удалить все элементы из списка голосов, получить массив, соответствующий выбранной категории, и добавить элементы в список голосов.

Последнее, что нам нужно сделать, чтобы завершить наш голосовой селектор, это добавить геттер, чтобы вернуть выбранный голос.

1
2
3
4
5
6
public function get voice():SiONVoice
{
    if(_categories.selectedItem)
        return _presets[_categories.selectedItem][_voices.selectedIndex];
    else return null;
}

OK. Итак, у нас есть готовый голосовой селектор. Вернитесь в основной класс и добавьте новое частное свойство с именем _selector типа VoiceSelector .

1
private var _selector:VoiceSelector;

Теперь мы собираемся добавить метод для создания селектора под названием addSelector() в котором мы создаем экземпляр объекта VoiceSelector и добавляем его на сцену.

1
2
3
4
5
6
7
private function addSelector():void
{
    _selector = new VoiceSelector();
    _selector.x = 220;
    _selector.y = 10;
    addChild(_selector);
}

Также вызовите этот метод из конструктора сразу после addSoundButtons() метода addSoundButtons() .

1
2
3
4
5
6
7
8
9
public function Main():void
{
    _driver = new SiONDriver();
    _s1 = _driver.compile(‘l8 cdefgab&lt;c’);
    _s2 = _driver.compile(‘l8 o6co5bagfedc’);
    _driver.play();
    addSoundButtons();
    addSelector();
}

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

Для этого просто измените onPlaySound() следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
function onPlaySound(e:Event):void
{
    if (e.target == b1)
    {
        _driver.sequenceOff(1);
        _driver.sequenceOn(_s1, _selector.voice, 0, 0, 1, 1 );
    }
    if (e.target == b2)
    {
        _driver.sequenceOff(2);
        _driver.sequenceOn(_s2, _selector.voice, 0, 0, 1, 2 );
    }
}

При воспроизведении последовательности второй параметр метода sequenceOn представляет голос, используемый для звуковых данных.

Запустите код и поиграйте с разными голосами, чтобы увидеть, как звучит каждый из них (нажмите кнопку « Воспроизвести звук 1» или « Воспроизвести звук 2» , чтобы воспроизвести один из двух звуков).


Еще один способ получить больше контроля над воспроизведением звуков в SiON — использование SoundObjects . Класс SoundObject является базовым классом для всех объектов, которые могут воспроизводить звуки с помощью SiONDriver .

Вы можете определить свой собственный звуковой объект, расширив класс SoundObject и реализовав все, что вам нужно. В библиотеке SiON есть несколько встроенных классов, которые расширяют SoundObject для реализации различных функций. Некоторыми из них являются MMLPlayer (используется для воспроизведения строки MML с контролем над воспроизводимыми дорожками), PatternSequencer (который предоставляет простой проигрыватель шаблонов с одной дорожкой) и MultiTrackSoundObject (это базовый класс для SoundObjects , использующих несколько дорожек).

Для простоты мы не будем создавать собственный звуковой объект, но будем использовать уже существующий в SiON. Есть интересный класс DrumMachine который предоставляет независимые басовые , малые барабаны и хай-хэт треки тарелок . Он не является прямым потомком класса SoundObject но я считаю, что он лучше подходит для этого урока.


Как я уже говорил, класс DrumMachine — это MultiTrackSoundObject который, в свою очередь, является потомком класса SoundObject .

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

Сначала давайте добавим две новые переменные в наш класс Main :

1
2
private var _drumsToggler:PushButton;
private var _drums:DrumMachine;

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

Теперь добавьте метод addDrums() который используется для создания объекта DrumMachine и кнопку, которая включает и выключает его.

1
2
3
4
5
6
private function addDrums():void
{
    _drums = new DrumMachine();
    _drumsToggler = new PushButton(this, 10, 35, «Drums OFF», toggleDrums);
    _drumsToggler.toggle = true;
}

Также нам нужно добавить обработчик к кнопке:

1
2
3
4
5
6
private function toggleDrums(e:Event):void
{
    _drumsToggler.label = _drumsToggler.selected ?
    if (_drumsToggler.selected) _drums.play();
    else _drums.stop();
}

И наконец, что не менее addDrums() метод addDrums() из конструктора:

01
02
03
04
05
06
07
08
09
10
public function Main():void
{
    _driver = new SiONDriver();
    _s1 = _driver.compile(‘l8 cdefgab&lt;c’);
    _s2 = _driver.compile(‘l8 o6co5bagfedc’);
    _driver.play();
    addSoundButtons();
    addSelector();
    addDrums();
}

Если вы запустите код сейчас, вы должны увидеть новую кнопку. Если вы нажмете на нее, вы услышите, как играет DrumMachine . Аккуратно, не правда ли?


Как я уже говорил, каждый трек в MultiTrackSoundObject (например, DrumMachine ) может управляться независимо друг от друга. Чтобы продемонстрировать это, мы изменим громкость на каждой дорожке объекта DrumMachine созданного ранее.

Давайте начнем с добавления некоторых ручек для тома, используя класс Knob minimalcomps. Для этого мы собираемся создать новый класс с именем DrumsVolume .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package
{
    import com.bit101.components.Knob;
    import flash.display.Sprite;
    import flash.events.Event;
     
    public class DrumsVolume extends Sprite
    {
        private var _bassKnob:Knob;
        private var _snareKnob:Knob;
        private var _hihatKnob:Knob;
         
        public function DrumsVolume()
        {
            addBassKnob();
            addSnareKnob();
            addHiHatKnob();
        }
         
        private function addBassKnob():void
        {
            _bassKnob = new Knob(this, 15, 0, «Bass vol.», onChange);
            _bassKnob.radius = 10;
            _bassKnob.labelPrecision = 0;
            _bassKnob.value = 100;
        }
         
        private function addSnareKnob():void
        {
            _snareKnob = new Knob(this, 15, _bassKnob.y + _bassKnob.height + 5, «Snare vol.», onChange);
            _snareKnob.radius = 10;
            _snareKnob.labelPrecision = 0;
            _snareKnob.value = 100;
        }
         
        private function addHiHatKnob():void
        {
            _hihatKnob = new Knob(this, 15, _snareKnob.y + _snareKnob.height + 5, «Hi-Hat vol.», onChange);
            _hihatKnob.radius = 10;
            _hihatKnob.labelPrecision = 0;
            _hihatKnob.value = 100;
        }
 
        public function get bassVol():Number { return _bassKnob.value/100;
        public function get snareVol():Number { return _snareKnob.value/100;
        public function get hihatVol():Number { return _hihatKnob.value/100;
 
        private function onChange(e:Event):void
        {
            dispatchEvent(new Event(Event.CHANGE));
        }
    }
 
}

В этом классе мы просто создаем три разные ручки (по одной для каждой дорожки) и отправляем событие CHANGE когда ручка поворачивается. Также есть три метода получения значения каждой ручки громкости.


Теперь вернитесь в класс Main добавьте новую переменную с именем _drumsVolume и добавьте метод addDrumsVolume() :

1
2
3
4
5
6
7
8
private function addDrumsVolume():void
{
    _drumsVolume = new DrumsVolume();
    _drumsVolume.addEventListener(Event.CHANGE, onVolumeChange);
    _drumsVolume.x = 5;
    _drumsVolume.y = _drumsToggler.y + _drumsToggler.height + 5;
    addChild(_drumsVolume);
}

Также, как вы можете видеть, мы добавляем слушатель к объекту DrumsVolume для событий CHANGE поэтому нам нужно также добавить обработчик.

1
2
3
4
5
6
private function onVolumeChange(e:Event):void
{
    _drums.bassVolume = _drumsVolume.bassVol;
    _drums.snareVolume = _drumsVolume.snareVol;
    _drums.hihatVolume = _drumsVolume.hihatVol;
}

И, наконец, добавьте выделенную строку в конструктор класса Main .

01
02
03
04
05
06
07
08
09
10
11
public function Main():void
{
    _driver = new SiONDriver();
    _s1 = _driver.compile(‘l8 cdefgab&lt;c’);
    _s2 = _driver.compile(‘l8 o6co5bagfedc’);
    _driver.play();
    addSoundButtons();
    addSelector();
    addDrums();
    addDrumsVolume();
}

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


SoundObjects полезны для еще одной цели. Вы можете добавить к ним эффекторы , чтобы получить разные звуковые эффекты.

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

Вы можете найти полный список встроенных эффекторов в пакете org.si.sion.effector . Конечно, вы также можете создавать свои собственные эффекторы, расширяя класс SiEffectBase .

Чтобы добавить один эффектор (или несколько), вы можете просто установить их, используя свойство effectors экземпляра SoundObjects .

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

1
_drums.effectors = [new SiEffectDistortion(), new SiEffectAutoPan()];

Чтобы удалить все эффекторы из SoundObject просто передайте пустой массив.

1
_drums.effectors = [];

В некоторых случаях вам нужно динамически указывать скорость воспроизведения всего звука, воспроизводимого в SiON (например, в игре, в которой вы входите в режиме замедленной съемки).

Чтобы указать скорость воспроизведения, вам нужно изменить свойство bpm (что означает количество ударов в минуту ). Это свойство класса SiONDriver и может использоваться в качестве глобального модификатора для всех звуков, воспроизводимых в SiON.

Давайте добавим ручку, чтобы изменить это свойство.

Сначала определите новую приватную переменную в классе Main именем _bpmKnob .

1
private var _bpmKnob:Knob;

Далее мы создадим метод для добавления ручки и прикрепления к ней обработчика событий.

01
02
03
04
05
06
07
08
09
10
11
12
13
private function addBPMKnob():void
{
    _bpmKnob = new Knob(this, 70, _drumsVolume.y + 10, «BPM», changeMaster);
    _bpmKnob.minimum = 48;
    _bpmKnob.maximum = 256;
    _bpmKnob.labelPrecision = 0;
    _bpmKnob.value = _driver.bpm;
}
 
private function changeMaster(e:Event):void
{
    if(e.target == _bpmKnob) _driver.bpm = _bpmKnob.value;
}

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

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

Примечание. Не забудьте вызвать метод addBPMKnob() из конструктора.

01
02
03
04
05
06
07
08
09
10
11
12
public function Main():void
{
    _driver = new SiONDriver();
    _s1 = _driver.compile(‘l8 cdefgab&lt;c’);
    _s2 = _driver.compile(‘l8 o6co5bagfedc’);
    _driver.play();
    addSoundButtons();
    addSelector();
    addDrums();
    addDrumsVolume();
    addBPMKnob();
}

Как и в случае с bpm, вы можете изменить громкость всех звуков, воспроизводимых в SiON, используя свойство volume драйвера.

Мы собираемся добавить еще одну ручку, которая будет служить модификатором мастер громкости.

1
private var _volumeKnob:Knob;

Метод addVolumeKnob() создает экземпляр регулятора громкости и настраивает его.

1
2
3
4
5
6
private function addVolumeKnob():void
{
    _volumeKnob = new Knob(this, 70, _bpmKnob.y + _bpmKnob.height + 10, «Volume», changeMaster);
    _volumeKnob.labelPrecision = 0;
    _volumeKnob.value = _driver.volume * 100;
}

Как видно выше, регулятор громкости использует тот же обработчик события changeMaster() для изменения громкости, поэтому нам нужно добавить еще одну строку в обработчик:

1
2
3
4
5
private function changeMaster(e:Event):void
{
    if (e.target == _bpmKnob) _driver.bpm = _bpmKnob.value;
    if (e.target == _volumeKnob) _driver.volume = _volumeKnob.value / 100;
}

Примечание . Значение ручки громкости делится на 100, потому что свойство SiONDriver класса SiONDriver принимает значение от 0 до 1.

Также добавьте следующий код в качестве последней строки в конструкторе:

1
addVolumeKnob();

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

Класс SiONDriver имеет свойство pan которое можно использовать для панорамирования всего звука, выходящего из SiON. Давайте добавим элемент управления панорамированием в наш проект.

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

1
private var _panController:HUISlider;

Затем мы будем использовать метод addPanControl() для создания и добавления ползунка на сцену.

1
2
3
4
5
6
7
private function addPanControl():void
{
    _panController = new HUISlider(this, _selector.x, _selector.y + _selector.height + 10 , «Pan», panSound);
    _panController.minimum = -100;
    _panController.maximum = 100;
    _panController.labelPrecision = 0;
}

Как вы можете видеть, обработчик для слайдера — panSound() поэтому давайте добавим и этот.

1
2
3
4
private function panSound(e:Event):void
{
    _driver.pan = _panController.value / 100;
}

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

Добавьте addPanControl() метода addPanControl() в последнюю строку конструктора, скомпилируйте код и попробуйте.

Примечание: чтобы лучше увидеть разницу, вы можете использовать наушники.


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

Самый простой способ синхронизировать звуки с noteOn() объектами — использовать метод noteOn() класса SiONDriver .

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

1
2
3
4
private function onBallHit(e:Event):void
{
    _driver.noteOn(50, _presets[«valsound.percus3»], 1);
}

Это будет играть ударную ноту, когда мяч ударяет что-то.

Конечно, для приведенного выше примера мы рассматривали _driver как экземпляр _presets а _presets как экземпляр класса SiONPresetVoice .

Примечание. Как и для любой другой операции в SiON, драйвер должен быть потоковым, чтобы он работал ( _drive.play() должен быть вызван ранее).


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

Мы начнем с добавления нового класса с именем Ball который будет представлять движущийся объект.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package
{
    import flash.display.Sprite;
     
    public class Ball extends Sprite
    {
        public var vx:Number;
        public var vy:Number;
         
        public function Ball()
        {
            draw();
        }
         
        private function draw():void
        {
            graphics.clear();
            graphics.beginFill(0xffffff, 0.9);
            graphics.drawCircle(5, 5, 5);
            graphics.endFill();
        }
    }
 
}

В этом классе мы просто рисуем белый круг диаметром 10 10px который имеет два открытых свойства vx и vy которые представляют скорости на каждой оси.


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

Добавьте новый класс в проект под названием BallContainer

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package
{
    import flash.display.Sprite;
    import org.si.sion.SiONDriver;
    import org.si.sion.utils.SiONPresetVoice;
     
    public class BallContainer extends Sprite
    {
        private var _balls:Array;
        private var _voices:SiONPresetVoice;
        private var _driver:SiONDriver;
         
        public function BallContainer()
        {
            _voices = new SiONPresetVoice();
            _driver = SiONDriver.mutex ?
            _driver.play();
            draw();
        }
         
        private function draw():void
        {
            graphics.beginFill(0, .3);
            graphics.lineStyle(1, 0, 0.6);
            graphics.drawRect(0, 0, 320, 170);
            graphics.endFill();
        }
         
    }
 
}

В этом классе у нас есть массив с именем _balls (он будет содержать экземпляры созданных шариков), экземпляр SiONDriver именем _driver и экземпляр SiONPresetVoice означающий переменную _voices .

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

Метод draw() просто рисует фон контейнера и стены.

Теперь, когда контейнер нарисован, нам нужно добавить несколько шариков. Метод addBalls() делает именно это, поэтому добавьте его в класс BallContainer .

01
02
03
04
05
06
07
08
09
10
11
12
private function addBalls():void
{
    _balls = [];
    for (var i:int = 0; i < 5; i++)
    {
        var b:Ball = new Ball();
        addChild(b);
        _balls.push(b);
        bx = Math.random() * 310;
        by = Math.random() * 160;
    }
}

В этом методе for-loop используется для создания пяти шаров и добавления их в контейнер в произвольной позиции.

Также добавьте следующий код сразу после последней строки в конструкторе:

1
addBalls();

OK. Пока все хорошо. Последнее, что нам нужно добавить в наш класс, чтобы он стал функциональным, — это прослушиватель событий ENTER_FRAME котором мы перемещаем шары.

Сначала добавьте прослушиватель событий в конструктор:

1
2
3
4
5
6
7
8
9
public function BallContainer()
{
    _voices = new SiONPresetVoice();
    _driver = SiONDriver.mutex ?
    _driver.play();
    draw();
    addBalls();
    addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

Затем добавьте обработчик для этого слушателя:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private function onEnterFrame(e:Event):void
{
    var b:Ball;
    for (var i:int = 0; i < _balls.length; i++)
    {
        b = _balls[i];
        bx += b.vx;
        by += b.vy;
        b.vx = bx >= 310 ||
        b.vy = by >= 160 ||
         
        if (bx >= 310 || bx <= 0 || by >= 160 || by <= 0)
            _driver.noteOn(Scale(new Scale(«Amp»)).getNote(i), _voices[«midi.chrom6»], 1);
    }
}

В обработчике onEnterFrame() мы используем цикл for для прохождения каждого шара и обновления его позиции. Также мы проверяем, достиг ли мяч границы, используя оператор if, и если он есть, мы используем метод noteOn() для воспроизведения звука.

В noteOn() мы использовали следующие параметры:

  • note — это нота, которую нужно сыграть, и целое число от 0 до 127 (или от C0 до B9). Как вы можете видеть, мы получаем соответствующую ноту в зависимости от номера шара (просто для того, чтобы микшировать ноты и не иметь одинаковый звук).
  • voice — это голос, в котором будет звучать нота. В нашем случае используется ксилофон (или голос midi.chrom6 ).
  • length — этот параметр представляет длину ноты в 16-м такте. Если это значение равно 0, заметка не будет удалена и останется в памяти (если вы используете длину 0, вы должны использовать метод noteOff() , чтобы удалить ее).

Теперь, когда все настроено, давайте использовать этот контейнер. Откройте класс Main и добавьте новую закрытую переменную с именем _ballCont типа BallContainer .

Мы будем использовать другой метод (удобно называемый addBallContainer() ), чтобы добавить контейнер на сцену.

1
2
3
4
5
6
7
private function addBallContainer():void
{
    _ballCont = new BallContainer();
    addChild(_ballCont);
    _ballCont.x = 170;
    _ballCont.y = 80;
}

А также вызовите этот метод из конструктора.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public function Main():void
{
    _driver = new SiONDriver();
    _s1 = _driver.compile(‘l8 cdefgab < c’);
    _s2 = _driver.compile(‘l8 o6co5bagfedc’);
    _driver.play();
    addSoundButtons();
    addSelector();
    addDrums();
    addDrumsVolume();
    addBPMKnob();
    addVolumeKnob();
    addPanControl();
    addBallContainer();
}

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


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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package
{
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import org.si.sion.SiONDriver;
    import org.si.sion.utils.Scale;
    import org.si.sion.utils.SiONPresetVoice;
     
    public class BallContainer extends Sprite
    {
        private var _balls:Array;
        private var _on:Boolean;
        private var _voices:SiONPresetVoice;
        private var _driver:SiONDriver;
         
        public function BallContainer()
        {
            _voices = new SiONPresetVoice();
            _driver = SiONDriver.mutex ?
            _driver.play();
            draw();
            addBalls();
            addEventListener(MouseEvent.CLICK, onClick);
        }
         
        private function onClick(e:MouseEvent):void
        {
            if (_on) stop();
            else start();
        }
         
        private function onEnterFrame(e:Event):void
        {
            var b:Ball;
            for (var i:int = 0; i < _balls.length; i++)
            {
                b = _balls[i];
                bx += b.vx;
                by += b.vy;
                b.vx = bx >= 310 ||
                b.vy = by >= 160 ||
                 
                if (bx >= 310 || bx <= 0 || by >= 160 || by <= 0)
                    _driver.noteOn(Scale(new Scale(«Amp»)).getNote(i), _voices[«midi.chrom6»], 1);
            }
        }
         
        private function draw():void
        {
            graphics.beginFill(0, .3);
            graphics.lineStyle(1, 0, 0.6);
            graphics.drawRect(0, 0, 320, 170);
            graphics.endFill();
        }
         
        private function addBalls():void
        {
            _balls = [];
            for (var i:int = 0; i < 5; i++)
            {
                var b:Ball = new Ball();
                addChild(b);
                _balls.push(b);
                bx = Math.random() * 310;
                by = Math.random() * 160;
            }
        }
         
        public function start():void
        {
            var b:Ball;
            for (var i:int = 0; i < _balls.length; i++)
            {
                b = _balls[i];
                b.vx = (Math.random() * 10) — 5;
                b.vy = (Math.random() * 10) — 5;
            }
            _on = true;
            addEventListener(Event.ENTER_FRAME, onEnterFrame);
        }
         
        public function stop():void
        {
            _on = false;
            removeEventListener(Event.ENTER_FRAME, onEnterFrame);
        }
         
    }
 
}

Как видите, я выделил дополнения в классе BallContainer .

В качестве краткого объяснения я сначала добавил логическую переменную, _onкоторая отслеживает, играет ли фильм (движутся ли шары) или нет. В конструкторе я изменил строку, которая добавляет прослушиватель ENTER_FRAMEсобытий для события с одним для MOUSE_CLICKсобытий. Кроме того, MouseEventназванный обработчик onClick()используется для запуска или остановки фильма при нажатии.

И наконец в start()и stop()методах ENTER_FRAMEдобавляются приемник событий и , соответственно , удален. Также в start()методе мы сбрасываем скорости на каждом шаре.


SiON library is very useful when you need to use a lot of sound (in games most often) but you can’t afford the extra size of the SWF or time to load them. As you can see it’s not that hard at all to create interesting sounds on the run.

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

Надеюсь, вам понравился этот урок, и спасибо, что прочитали его.