Воспроизведение MIDI звуков на языке JAVA

Прежде, чем перейти к сути, я немного расскажу вам о компьютерном звуке.

Существует два основных формата воспроизведения звуков компьютером:
цифровой (WAV-формат) и синтезированный (MIDI).

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

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

Но существует и другой вид компьютерного звука — синтезированный (MIDI)

Звуковая карта может служить музыкальным синтезатором, способном воспроизводить звучания до 128-ми различных музыкальных инструментов. Качество и принцип имитации инструментов зависит от вашей звуковой карты. Она может пытаться смоделировать звучание инструмента совокупностью нескольких FM (частотных) генераторов простых частот, для каждой из которых задана амплитуда, частота, фаза и куча других параметров. Но чаще она обращается к хранящимся в ней «банкам данных» образцов звучания инструментов.

В настоящий момент существует множество звуковых карт, которые могут работать с SoundFont. Это просто wav сэмплы, которые можно загрузить в звуковую карту и звучанием этих инструментов можно управлять в миди-секвенсоре. SoundFont обычно имеет расширение .sf2, их могут также называть патчами (patch) или программами (program). Несколько SoundFont объединяют в звуковые банки (SoundFont Banks), которые могут содержать до 128 инструментов и одного набора ударных.
В сети вы без труда сможете найти профессиональные библиотеки банков инструментов в формате SF2.

Чтобы синтезатор воспроизвел нужный звук, необходимо передать ему специальную команду. Совокупность таких команд описана стандартом MIDI.
MIDI это акроним от Musical Instruments Digital Interface, что в буквальном переводе — цифровой интерфейс музыкальных инструментов.

При помощи этих команд мы можем «сказать» синтезатору звуковой платы каким инструментом ту или иную ноту мы хотим чтобы он воспроизвел. Например Фа-диез на рояле или любом другом из 128-ми инструментов.

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

Скажу, что существует достаточное количество библиотек для работы с midi. И все они обещают, что с их помощью процес программирования музыки будет проще. Я столкнулся с двумя такими: jfugue и jMusic. В Ютубе есть наглядные руководства о их применении. В каждой из подобных библиотек были выдуманы свои способы и правила. Чем более экзотическая библиотека, тем меньше информации и примеров. Так же придется довериться правильности и точности их работы.
Из многих соображений полагаю, что лучше начать изучение и научиться применять в начале стандартную библиотеку: javax/sound/midi

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

Итак, все что вам для начала нужно — научиться воспроизводить ноты.
Ваше желание включает некоторую дополнительную информацию — название ноты (номер) и длительность звучания. А так же инструмент, который вы хотите чтобы её сыграл.
Желание совершенно просто и ясно сформулировано: «Хочу, чтобы прозвучала такая-то нота, определенное время и выбранным инструментом.» Однако сходу найти соответствующую информацию, как это сделать, вам будет сложно. Вот по этому эта статья и написана — чтобы вы не тратили свое время а сразу получили то, что нужно. И начали применять.

Номера нот вы можете определить по данной таблице:


А номера инструментов можно посмотреть здесь

Перед непосредственным программированием воспроизведения потребуется некоторая подготовка. А именно, вам понадобится получить объект синтезатора (Synthesizer) и отрыть его. Затем получить доступ к его каналам.
Звучит, возможно, не совсем понятно, но на практике всё просто:
     Synthesizer synth = MidiSystem.getSynthesizer();
     synth.open();
     MidiChannel[] channels = synth.getChannels();

Канал можно представить как универсального музыканта, который способен играть на любом из 128-ми инструментов. Вы располагаете 16-тью такими каналами.
Вот в принципе и вся подготовка. Далее необходимо научиться давать команды музыкантам.
Допустим вы хотите, чтоб звучала нота Фа первой октавы. Для этого, посмотрев таблицу с номерами нот, выясняете, что её номер 65. Выбираем одного из 16-ти музыкантов. Допустим под номером 0 (channels[0]). Воспроизведение происходит по команде noteOn.
Она принимает 2 параметра: MIDI номер ноты (от 0 до 127) и громкость звучания (так же до 127)
Выглядеть это будет так:
     channels[0].noteOn(65, 80);

По этой команде музыкант под номером «ноль» начнет воспроизведение ноты Фа первой октавы. 80 — это громкость. Это можно представить, что музыкант нажал клавишу синтезатора и льется звук.
Думаю, вы догадались, что для прекращения звучания так же потребуется дать команду. Т.е. чтобы конкретно этот музыкант отпустил нажатую клавишу — выполняем команду noteOff.
Таким образом:
     channels[0].noteOff(65);

Между этими командами следует сделать паузу, от которой и будет зависеть длительность звучания.
Вот, собственно, и всё. И не нужны никакие сторонние библиотеки. Всё необходимое теперь вы сможете удобным способом написать под себя сами. Осталось открыть вам последний секрет — научить волшебным словам, которые заставят «музыкантов» взять другой муз. инструмент.
Напомню, что выбрать инструмент можно здесь. Любому из 16-ти каналов вы можете назначить один из 128-ми инструментов. По умолчанию почти все каналы используют фортепиано.
Так производится назначение «нулевому» каналу инструмент «скрипка»:
    channels[0].programChange(41);

Понадобится импортировать следующие стандартные пакеты:
    import javax.sound.midi.MidiChannel;
    import javax.sound.midi.MidiSystem;
    import javax.sound.midi.Synthesizer;


Итак, код воспроизводящий звук будет выглядеть следующим образом:
       try {
            Synthesizer synth = MidiSystem.getSynthesizer();
            synth.open();
            MidiChannel[] channels = synth.getChannels();
            channels[0].programChange(41);
            channels[0].noteOn(65, 80);
            Thread.sleep(1000); // in milliseconds
            channels[0].noteOff(65);
            synth.close();
       }  catch (Exception e) {
            e.printStackTrace();
       } 


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

Для примитивной демонстрации создадим свой метод воспроизведения звуков, который нажатие, паузу и отпускание будет выполнять одной командой. Отведем для этих целей отдельный класс, структура и функционал которого не нуждаются в пояснении:
package music.player;

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Synthesizer;

public class Player {

    private MidiChannel[] channels = null;
    private Synthesizer synth = null;

    public Player() {
        try {
            synth = MidiSystem.getSynthesizer();
            synth.open();
            channels = synth.getChannels();
            channels[0].programChange(41);
        } catch (MidiUnavailableException ex) {
            Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void close() {
        synth.close();
    }

    public void playSound(int channel, int duration, int volume, int... notes) {
        for (int note : notes) {
            channels[channel].noteOn(note, volume);
        }
        try {
            Thread.sleep(duration);
        } catch (InterruptedException ex) {
            Logger.getLogger(Player.class.getName()).log(Level.SEVERE, null, ex);
        }
        for (int note : notes) {
            channels[channel].noteOff(note);
        }
    }
}



В конструкторе класса создается и открывается синтезатор. Получаем массив каналов и первому (нулевому) назначаем инструмент «скрипка».
Метод playSound получает на вход номер канала, длительность звучания, громкость и последовательность нот, которые прозвучат одновременно. Реализация предельно проста — воспроизводятся все входящие ноты, удерживается интервал длительности и затем все выключаются.
Вот пример использования данного класса — воспроизводятся четыре аккорда:
    Player player = new Player();
       player.playSound(0, 1000, 80, 69, 72, 76);
       player.playSound(0, 1000, 80, 69, 74, 77);
       player.playSound(0, 1000, 80, 67, 71, 74);
       player.playSound(0, 1000, 80, 67, 72, 76);
    player.close();


Ну и в заключении, давайте для эксперимента запрограммируем какую-нибудь известную красивую мелодию.
Просто скопируйте себе код и, запустив, услышите «Jasper Forks — River Flows In You»:
      int notes[][] = {{470, 81}, {230, 80}, {470, 81}, {250, -1}, {230, 80}, {470, 81}, {230, 69}, {230, 76}, {470, 81}, {230, 69}, {470, 74}, {470, 73}, {470, 74}, {470, 76}, {470, 73}, {470, 71}, {970, -1}, {230, 69}, {230, 68}, {470, 69}, {730, -1}, {230, 64}, {230, 69}, {230, 71}, {470, 73}, {970, -1}, {230, 73}, {230, 74}, {470, 76}, {730, -1}, {230, 69}, {230, 74}, {230, 73}, {470, 71}, {1450, -1}, {470, 81}, {230, 80}, {470, 81}, {250, -1}, {230, 80}, {470, 81}, {230, 69}, {230, 76}, {470, 81}, {230, 69}, {470, 74}, {470, 73}, {470, 74}, {470, 76}, {470, 73}, {470, 71}, {970, -1}, {230, 69}, {230, 68}, {470, 69}, {730, -1}, {230, 64}, {230, 69}, {230, 71}, {470, 73}, {970, -1}, {230, 73}, {230, 74}, {470, 76}, {730, -1}, {230, 69}, {230, 74}, {230, 73}, {470, 71}, {250, -1}};
        Player player = new Player();
        for (int[] note : notes) {
            if (note[1] != -1) {
                player.playSound(0, note[0], 80, note[1]);
            } else {
                try {
                    Thread.sleep(note[0]);
                } catch (InterruptedException ex) {
                    Logger.getLogger(Music.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
        player.close();

В массиве пары чисел содержат величину длительности звучания и номер ноты. Паузу, для отличия, запишем «минус единицей». Канал и громкость оставим для всех нот одинаковые — 0 и 80 соответственно.

Что ж, полагаю у вас не возникло сложностей с пониманием изложенного материала. Буду рад, если эта статья сэкономит ваше время и силы, позволив с легкостью приступить к программированию воспроизведения MIDI звука на JAVA.
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 23

    +2
    Но чаще она обращается к хранящимся в ней «банкам данных» образцов звучания настоящих инструментов. (Возможно комбинирование этих двух способов)
    «Чаще»? Нету такого нигде, имхо. Впрочем, как и
    смоделировать звучание инструмента совокупностью нескольких FM (частотных) генераторов
    В большинстве современных карт синтезаторы используются исключительно полностью софтовые уж много-много лет. Вообще, думаю, что в обычных картах во всех. Хотя, может остались какие-то эксклюзивные исключения.
      0
      Думаю, под «чаще» автор имел ввиду то, что чаще всего на деле используют готовые библиотеки с образцами звучания.
        0
        Да нет, тут явно сказано, что за генерацию midi-звуков отвечает звуковая карта и от неё же зависят какие они будут:
        Звуковая карта может служить музыкальным синтезатором, способном воспроизводить звучания до 128-ми различных музыкальных инструментов. Качество и принцип имитации инструментов зависит от вашей звуковой карты.
        и типа есть два варианта: либо она генерирует их частотным генератором, либо использует какие-то там «банки данных» находящиеся на ней. Да, было такое кое-где, но сейчас на деле карты вообще этим не занимаются, всё генерируется софтом вне её. Например, описываемый в статье javax.sound.midi использует программный синтезатор встроенный в JRE, емнип.
      0
      Но звучит «миди» гарантированно ужасно и заставлять поьзователя слушать это не в 1995 году – издевательство.
        0
        Если воспроизводить эти файлы на профессиональных синтезаторах — то звучит соответствующе.
          0
          Набор General MIDI даже в крутых воркстейшенах типа Motif звучит все равно весьма посредственно и делается по остаточному принципу (у ямах так вообще запиливается набор от древних самоиграек серии PSR)
          0
          Соглашусь, «миди» звуками пользователя сложно удивить, особенно если использовать стандартную базу инструментов. Но для экспериментов с музыкой, особенно при манипуляциях с её нотной основой, простая возможность воспроизведения муз. инструментов может пригодиться. И если кому-либо понадобится — он всегда может глянуть этот пост и сразу же будет готов к воплощению своей идеи, включающей в себя необходимость воспроизводить «миди». И хоть уже 2014-ый год, но звуковые платы по прежнему предоставляют эту возможность. Допустим вы захотели попробовать свои силы в программном сочинении музыки и вам понадобилось озвучивать ноты — не потребуется отвлекаться от воплощения основных мыслей идеи. Не понадобится искать и разбираться с тем как можно запрограммировать такую простую возможность. Мне такая необходимость понадобилась и пришлось потратить время на выяснение как это делается. К примеру, о смене стандартного пианино на любой из 128-ми других, не попадалось информации. В общем, если понадобится — будете знать что есть возможность простой работы с «миди» звуками и эта статься, как шпаргалка.
            0
            И хоть уже 2014-ый год, но звуковые платы по прежнему предоставляют эту возможность
            Вы почему-то настойчиво игнорируете то что я написал выше. Не знают в 2014 году карты ни о каком миди. Ваши звуки из примеров генерируются программным синтезатором. Вы сами проверьте экземпляр какого класса реализующего Synthesizer получается этим вашим кодом:
            Synthesizer synth = MidiSystem.getSynthesizer();
              0
              Synthesizer — это java интерфейс. И буду рад, если ты приведешь ссылку на информацию, каким образом java api само синтезирует midi звучание. Но в любом случае — даже программно можно прямо из java загружать готовые семплы SoundFont и воспроизводить, не синтезируя.
                0
                Synthesizer — это java интерфейс.
                Именно потому я и написал:
                экземпляр какого класса реализующего Synthesizer

                ссылку на информацию, каким образом java api само синтезирует midi звучание
                Таким же образом, как весь остальной софт. У меня нет подробной информации как устроен синтезатор именно в JRE, изучение исходников в помощь, например.
                Но в любом случае — даже программно можно прямо из java загружать готовые семплы SoundFont и воспроизводить, не синтезируя.
                Да, можно, но до звуковой карты они не доходят. И всего что написано мной выше это не отменяет.

                з.ы. И да, скорее всего этот SoftSynthesizer и есть то, что вы получаете через MidiSystem.getSynthesizer().
                  0
                  Не вижу связи javax.sound.midi.Synthesizer с com.sun.media.sound.SoftSynthesizer.
                    0
                    Не уверен что я 100% знаю картину, но я так понимаю что программа синтеза звука встроена в ОС, а Java просто врапит доступ к нему.
                    В секвенсорах он называется что-то типа «Microsoft midi synthesizer» (на винде, конечно)
                      0
                      По ссылке выше видно примерно как работает синтезатор. Он сам использует саундбанки. В дефолтном случае он ещё пытается найти несколько банков (в зависимости от системы), если и тут не получается, то возвращает вообще EmergencySoundbank, понятное дело весьма унылый.
                        0
                        Да. Похоже вы правы.
                      0
                      Не вижу связи javax.sound.midi.Synthesizer с com.sun.media.sound.SoftSynthesizer.
                      А я не вижу что тут непонятного и с чем именно вы спорите. Я же ссылку на исходник даже дал. Прямо таки вот вообще никакой связи не видите? Меж тем связь тут самая прямая — класс com.sun.media.sound.SoftSynthesizer имплементирует javax.sound.midi.Synthesizer. И этот же самый com.sun.media.sound.SoftSynthesizer возвращается вашим же кодом
                      Synthesizer synth = MidiSystem.getSynthesizer();

                      Ну или какой-то другой класс возвращается, который в вашей конкретной JRE реализует софтовый синтезатор. У себя проверил — в оракловой джаве реализация именно com.sun.media.sound.SoftSynthesizer и есть.
              0
              Если поставить какой-нибудь Soundbank для MIDI то можно заставить звучать MIDI файлы, как живые.
              Например http://coolsoft.altervista.org/en/virtualmidisynth
              +1
              Похоже на мерзкий перевод боянистого мана.
                0
                Чутье тебя подвело
                0
                Но существует и другой вид компьютерного звука — синтезированный (MIDI)

                В корне неверная формулировка.
                MIDI — это Musical Instrument Digital Interface. Там нет звука и он там ни когда быть не должен. На устройство (синтезатор, контроллер) приходят команды оно их интерпретирует результатом чего может быть звук.
                То есть звук есть, да, но это не другой вид звука, это результат обработки команд, например синтезатором.
                  0
                  Иная форма и концепция воспроизведения звука. В статье четко расписано что такое MIDI. И в данной фразе говорилось, что РЕЧЬ идет о MIDI (как технологии), а не о том, что набор команд — это звук.
                    0
                    Думаю что «проблема» в том, что «MIDI звук» — не совсем верный термин. Обычному пользователю понятно, а вот профессионального музыканта такое может расстроить… Скорее должно быть что-то типа «звук дефолтного PC миди синтезатора».
                    Кстати, если вы играетесь с программированием нот и вас не очень устраивает стандартный миди синтезатор, то вы можете поискать секвенсор, в который загружаются VSTi синтезаторы и вот с помощью них добиться офигенного профессионального саунда.
                  0
                  Было бы интересно в рамках этой же статьи:

                  1) как выводить звук в midi-порт звуковой карты?
                  2) узнать как добавлять кастомные банки, в каком формате они должны быть?
                    0
                    Еще было бы здорово понять, можно ли потом результат выкатить как миди файл?

                  Only users with full accounts can post comments. Log in, please.