Статьи

Интервью: JFugue идет хип-хоп

На прошлой неделе был анонсирован новый выпуск JFugue API . Его автор, Дейв Коэль, работает в JavaOne, и здесь он рассказывает об API и некоторых его скрытых сокровищах.

Для тех, кто не знает, JFugue — это API Java, который делает программирование музыки таким же легким, как написание одной или двух строк кода, например:

Player player = new Player();
player.play("C D E F G A B");

Завершив успешную сессию в прошлом году, Дейв снова представит завтра несколько интересных и забавных вещей из API JFugue на технической сессии в 16.10 в аудитории 303, а также в BOF позже в тот же день. Ниже он рассказывает о некоторых менее известных, но очень интересных функциях API, которые вы увидите в действии, если перейдете к его сеансу.

Дэйв, ты вернулся в JavaOne с JFugue. Давайте не будем биться вокруг куста и сразу приступим к медным гвоздям. Покажите нам что-нибудь действительно классное!

ОК. Мы рассмотрим некоторый реальный код, три примера, которые приводят к файлам Midi, которые вы можете скачать через ZIP-файл , нажав здесь . Итак, давайте сначала рассмотрим менее используемую функцию API JFugue, не столь популярную, как большинство других, но все же очень классную. Это позволяет вам создавать ритм, просто нажимая клавиши на клавиатуре, что иллюстрирует приведенный ниже блок кода:

rhythm.setLayer(1, "O..oO...O..oOO..");
rhythm.setLayer(2, "..*...*...*...*.");
rhythm.setLayer(3, "^^^^^^^^^^^^^^^^");
rhythm.setLayer(4, "...............!");

С JFugue API вы можете сопоставить ключи с инструментами. Например, буква «о» может быть басовым барабаном, звездочка может быть малым барабаном, восклицательным знаком или чем-то еще, и так далее. Посмотрите на 16-битный ритм-код ниже и посмотрите, как легко можно выполнить это отображение:

    public static void beat16()
{
Rhythm rhythm = new Rhythm();

// Each MusicString should have a total duration of an eighth note
rhythm.addSubstitution('O', "[BASS_DRUM]i");
rhythm.addSubstitution('o', "Rs [BASS_DRUM]s");
rhythm.addSubstitution('*', "[ACOUSTIC_SNARE]i");
rhythm.addSubstitution('^', "[PEDAL_HI_HAT]s Rs");
rhythm.addSubstitution('!', "[CRASH_CYMBAL_1]s Rs");
rhythm.addSubstitution('.', "Ri");

rhythm.setLayer(1, "O..oO...O..oOO..");
rhythm.setLayer(2, "..*...*...*...*.");
rhythm.setLayer(3, "^^^^^^^^^^^^^^^^");
rhythm.setLayer(4, "...............!");

Pattern pattern = rhythm.getPattern();
pattern.repeat(4);

Player player = new Player();
player.play(pattern);

try {
player.saveMidi(pattern, new File("beat16.mid"));
} catch (IOException e)
{
e.printStackTrace();
}
}

Это действительно круто. Почему это менее известная функция, которая на самом деле не используется?

Это, вероятно, менее известно, потому что, насколько я понимаю, как используется API, стучать по клавиатуре не является типичным способом создания музыки. Чтобы использовать этот подход, люди должны порвать со своей ментальной моделью ноты и вместо этого расширить свое мышление на новые способы создания музыки. Тем не менее, результат очень похож на битбокс. Люди привыкли к этой концепции, но здесь она немного круче, чем битбокс, потому что, получив «шаблон JFugue» (подробнее об этом ниже), вы можете манипулировать им уникальными и интересными способами.

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

Создана хэш-карта, которая отображает буквы на инструментах. Затем создаются длинные строки, которые просто используют эти замены. Строки — это музыкальные строки JFugue, которые анализатор JFugue может интерпретировать для создания событий Midi. Фактически, это одна из первых функций, запрошенных членом сообщества JFugue.

С каких пор эта функция «грохота клавиатуры» стала частью API?

Это было там с JFugue 3.0.

И теперь вышла версия 4.0, как раз к JavaOne. Что дает мне этот релиз?

Несколько новых функций, на самом деле. Например, JFugue API теперь поддерживает инверсию аккордов и кортежи, которые были запрошены членами сообщества пользователей. Этот последний выпуск включает в себя музыкальный XML-формат Майкла Гуда , который был представлен участником сообщества пользователей по имени Э. Фил Соболик. И были улучшены способы манипулирования шаблонами в API.

Для тех из нас, кто не знает, что такое шаблон JFugue?

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

Итак, что нового в шаблонах в этом последнем выпуске?

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

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

Весь приведенный ниже код является компилируемым, то есть вы можете просто скопировать и вставить его в свое собственное приложение, поместить последнюю версию JAR JFugue API в classpath, а затем просто запустить его. Обязательно замените «mymusic.mid» в строке 112 в коде ниже на имя и местоположение файла, к которому у вас есть доступ:

import java.io.File;
import java.io.IOException;

import javax.sound.midi.InvalidMidiDataException;

import org.jfugue.Instrument;
import org.jfugue.MusicStringParser;
import org.jfugue.Note;
import org.jfugue.Pattern;
import org.jfugue.PatternTransformer;
import org.jfugue.Player;
import org.jfugue.extras.DurationPatternTransformer;
import org.jfugue.extras.GetPatternForVoiceTool;
import org.jfugue.extras.IntervalPatternTransformer;
import org.jfugue.extras.ReversePatternTransformer;

public class RemixMidi {

private Player player;
private File file;

public RemixMidi(File file) {
this.player = new Player();
this.file = file;
}

/** Loads a MIDI file, and converts the MIDI into a JFugue Pattern. */
public Pattern getPattern() {
Pattern pattern = null;
try {
pattern = player.loadMidi(file);
} catch (InvalidMidiDataException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return pattern;
}

/**
* Using the GetPatternForVoiceTool, isolate a single voice (or channel) of the
* Pattern and return it.
*/
public Pattern getPatternForVoice(Pattern pattern, int voice) {
GetPatternForVoiceTool tool = new GetPatternForVoiceTool(voice);

MusicStringParser parser = new MusicStringParser();
parser.addParserListener(tool);
parser.parse(pattern);
Pattern voicePattern = tool.getPattern();

return voicePattern;
}

/** Plays a Pattern - this is a pass-through method to JFugue's Player */
public void play(Pattern pattern) {
player.play(pattern);
}

/**
* Using the InvertPatternTransformer, invert the given pattern.
*/
public Pattern invertPattern(Pattern pattern) {
InvertPatternTransformer ipt = new InvertPatternTransformer(MusicStringParser.getNote("C5"));
Pattern invertPattern = ipt.transform(pattern);
return invertPattern;
}

/**
* Using the ReversePatternTransformer, reverse the given pattern.
* "C D E" becomes "E D C"
*/
public Pattern reversePattern(Pattern pattern) {
ReversePatternTransformer rpt = new ReversePatternTransformer();
Pattern reversePattern = rpt.transform(pattern);
return reversePattern;
}

/**
* Causes the duration of each note in the Pattern to be lengthened
* by the provided factor.
*/
public Pattern stretchPattern(Pattern pattern, double stretch) {
DurationPatternTransformer dpt = new DurationPatternTransformer(stretch);
Pattern stretchedPattern = dpt.transform(pattern);
return stretchedPattern;
}

/**
* Changes the note values of each note in the Pattern - this causes
* the entire Pattern to be played with higher or lower pitches.
*/
public Pattern changeInterval(Pattern pattern, int delta) {
IntervalPatternTransformer it = new IntervalPatternTransformer(delta);
Pattern intervalPattern = it.transform(pattern);
return intervalPattern;
}

public Pattern cleanInstrument(Pattern pattern) {
PatternTransformer t = new PatternTransformer() {

@Override
public void instrumentEvent(Instrument instrument) {
// Do nothing
}
};
Pattern cleanedPattern = t.transform(pattern);
return cleanedPattern;
}

private static void remixBach() {
RemixMidi mix = new RemixMidi(new File("mymusic.mid"));
Pattern pattern = mix.getPattern();
System.out.println("From midi: " + pattern);
Pattern voicePattern = mix.getPatternForVoice(pattern, 0);

Pattern cleanInstrumentPattern = mix.cleanInstrument(voicePattern);

Pattern invertedPattern = mix.invertPattern(cleanInstrumentPattern);
System.out.println("After inversion: " + invertedPattern);

Pattern reversedPattern = mix.reversePattern(invertedPattern);
System.out.println("After reversal: " + reversedPattern);
// mix.play(reversedPattern);

// reversedPattern = mix.stretchPattern(reversedPattern, 0.3);
reversedPattern = mix.changeInterval(reversedPattern, +16);
// mix.play(reversedPattern);
System.out.println(reversedPattern);

// Add pattern to itself, reversed again
Pattern reversedAgain = mix.reversePattern(reversedPattern);
reversedPattern.add(reversedAgain);

// Repeat it
reversedPattern.repeat(4);

Pattern fugue = new Pattern();
fugue.add("V0 I[Piano]");
fugue.add(reversedPattern);
fugue.add("V1 I[Synth_strings_1] R/0.637353");
fugue.add(mix.changeInterval(reversedPattern, -12));
fugue.add("V2 I[Blown_Bottle] R/0.637353 R/0.566652");
fugue.add(mix.changeInterval(reversedPattern, -17));
System.out.println(fugue);

mix.play(fugue);

try {
fugue.savePattern(new File("fugue.jfugue"));
} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
remixBach();
}
}


//***Sample PatternTransformer***
class InvertPatternTransformer extends PatternTransformer {

private byte fulcrumNoteValue;

public InvertPatternTransformer(Note note) {
this.fulcrumNoteValue = note.getValue();
}

/** Transforms the given note */
@Override
public void noteEvent(Note note) {
doNoteEvent(note);
}

/** Transforms the given note */
@Override
public void sequentialNoteEvent(Note note) {
doNoteEvent(note);
}

/** Transforms the given note */
@Override
public void parallelNoteEvent(Note note) {
doNoteEvent(note);
}

private void doNoteEvent(Note note) {
byte noteValue = note.getValue();

if (noteValue > fulcrumNoteValue) {
note.setValue((byte) (fulcrumNoteValue - (noteValue - fulcrumNoteValue)));
getReturnPattern().addElement(note);
} else if (noteValue < fulcrumNoteValue) {
note.setValue((byte) (fulcrumNoteValue - (fulcrumNoteValue - noteValue)));
getReturnPattern().addElement(note);
} else {
// No change in note value
getReturnPattern().addElement(note);
}
}
}

Итак, что в этом коде нового?

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

    public static void hiphopBeat()
{

Rhythm rhythm = new Rhythm();

// Each MusicString should have a total duration of an eighth note
rhythm.addSubstitution('j', "<1>s Rs");
rhythm.addSubstitution('k', "<6>s Rs");
rhythm.addSubstitution('l', "<8>s Rs");
rhythm.addSubstitution('m', "<9>s Rs");
rhythm.addSubstitution('n', "<4>s Rs");
rhythm.addSubstitution('o', "[BASS_DRUM]i");
rhythm.addSubstitution('*', "[HAND_CLAP]s Rs");
rhythm.addSubstitution('%', "[TAMBOURINE]s Rs");
rhythm.addSubstitution('.', "Ri");

rhythm.setLayer(3, "o...o...o...o...o...o...o...o...");
rhythm.setLayer(4, "..*...*...*...*...*...*...*...*.");
rhythm.setLayer(5, "...%...%...%...%...%...%...%...%");

rhythm.setVoice(1, "jjnnjjmlnnllnnlkjjnnjjmlkkklnnnk");
rhythm.setVoiceDetails(1, "I[CHOIR_AAHS]");

Pattern pattern = rhythm.getPatternWithInterval("Bb4");
pattern.insert("T95");

Player player = new Player();
player.play(pattern);

try {
player.saveMidi(pattern, new File("hiphopBb4.mid"));
} catch (IOException e)
{
e.printStackTrace();
}
}

Здесь мы стучим по клавиатуре, но в новой части мы можем настроить некоторые неперкуссионные инструменты, чтобы они соответствовали этому ритму. Затем мы можем получить паттерн с басовой нотой, в данном случае B-flat с 4-й октавой. И, наконец, мы можем сохранить только что созданный файл Midi.

Спасибо Дэйв и удачи в вашей сессии и BOF!