Встроенные возможности Java Midi
В стандартной комплектации Java 8 JRE определенно есть то, что вы специально запросили: встроенная библиотека синтезатора. Это описано более подробно в Synthesizing Sound .
A довольно изысканный пример обеспечивает визуальный доступ к клавиатуре для семпла сэмплированной музыки.
Библиотека javax.sound.midi содержит инструменты, которые могут воспроизводить ноты, основанные на технологии MIDI и сэмплированных инструментах. Звуки не настолько аутентичны, как звуки классической линии музыкальных инструментов Kurzweil, но фреймворк поддерживает этот уровень сложности, если вы хотите сделать свою собственную выборку в нескольких диапазонах высоты звука для одного инструмента и проработать детали довольно плавного перехода между диапазонами.
Простой пример быстрого просмотра использования
Вот тривиальный пример класса.
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.MidiChannel;
public class PlayMidiNote
{
private static void sleep(int iUSecs)
{
try
{
Thread.sleep(iUSecs);
}
catch (InterruptedException e)
{
}
}
public static void main(String[] args) throws Exception
{
if (args.length < 3 && args.length > 4)
{
System.out.println("usage: java PlayNote
<8.bit.midi.note.number> <8.bit.velocity>
<usec.duration> [<midi.channel>]");
System.exit(1);
}
int iMidiKey = Math.min(127, Math.max(0,
Integer.parseInt(args[0])));
int iVelocity = Math.min(127, Math.max(0,
Integer.parseInt(args[1])));
int iUSecsDuration = Math.max(0,
Integer.parseInt(args[2]));
int iChannel = args.length > 3
? Math.min(15, Math.max(0,
Integer.parseInt(args[3])))
: 0;
Synthesizer synth = MidiSystem.getSynthesizer();
synth.open();
MidiChannel[] channels = synth.getChannels();
MidiChannel channel = channels[iChannel];
channel.noteOn(iMidiKey, iVelocity);
sleep(iUSecsDuration);
channel.noteOff(iMidiKey);
synth.close();
}
}
Использование многопоточности или реализаций javax.sound.midi.Sequencer, подобных тем, которые доступны на GitHub.com, обеспечит структуру, необходимую для создания музыки.
Генерация формы волны
Если вы хотите генерировать свои собственные формы волны, а не использовать сэмплы, тогда ответьте на свой вопрос «Нет», однако вы можете поиграть с этим синтезатором тонов, который я написал для этого вопроса. Он имеет несколько функций.
- Контроль высоты тона (в циклах в секунду)
- Контроль амплитуды (в цифровых шагах)
- Минимальная и максимальная статистика, указывающая, когда амплитуда слишком высока (что вызывает искажение тона при отсечении звука)
- Контроль длительности тона (в секундах)
- Управление относительной амплитудой произвольного числа гармоник (которое определяет качество тона или форму волны, которая является одним из нескольких факторов, создающих тембр музыкальной ноты)
В этом синтезаторе отсутствуют многие особенности высокопроизводительных синтезаторов, генерирующих сигналы.
- Модификация в реальном времени амплитуды основной гармоники и других гармоник на протяжении ноты
- Не воспроизводит последовательности нот (но может быть изменено, чтобы сделать это с относительной легкостью)
- Также нет возможности для сдвига фаз гармоник (но это несколько несущественный недостаток, так как фазовые характеристики гармоник не то, что человеческое ухо способно обнаружить)
SimpleSynth - хорошая демонстрация
- Принципы синтеза звука
- Тембральный эффект гармоник
- Использование Java для генерации массивов аудиосэмплов
- Размещение семплов в байтовом массиве
- Передача такого байтового массива в ОС через строку
Стандартная цифровая звуковая терминология использовалась как для постоянного, так и для переменного именования. Математика объясняется в комментариях.
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.SourceDataLine;
class SimpleSynth
{
private static int SAMPLE_BITS = 16;
private static int CHANNELS = 1;
private static boolean SIGNED_TRUE = true;
private static boolean BIG_ENDIAN_FALSE = false;
private static float CDROM_SAMPLE_FREQ = 44100;
private SourceDataLine line;
private double dDuration;
private double dCyclesPerSec;
private double dAmplitude;
private double[] adHarmonics;
private double dMin;
private double dMax;
public SimpleSynth(String[] asArguments) throws Exception
{
dDuration = Double.parseDouble(asArguments[0]);
dCyclesPerSec = Double.parseDouble(asArguments[1]);
dAmplitude = Double.parseDouble(asArguments[2]);
adHarmonics = new double[asArguments.length - 3];
for (int i = 0; i < adHarmonics.length; ++ i)
adHarmonics[i] = Double.parseDouble(
asArguments[i + 3]);
AudioFormat format = new AudioFormat(
CDROM_SAMPLE_FREQ, SAMPLE_BITS,
CHANNELS, SIGNED_TRUE, BIG_ENDIAN_FALSE);
line = AudioSystem.getSourceDataLine(format);
line.open();
line.start();
}
public void closeLine()
{
line.drain();
line.stop();
}
public void playSound()
{
// allocate and prepare byte buffer and its index
int iBytes = (int) (2.0 * (0.5 + dDuration)
* CDROM_SAMPLE_FREQ);
byte[] ab = new byte[iBytes];
int i = 0;
// iterate through sample radian values
// for the specified duration
short i16;
double dSample;
double dRadiansPerSample = 2.0 * Math.PI
* dCyclesPerSec / CDROM_SAMPLE_FREQ;
double dDurationInRadians = 2.0 * Math.PI
* dCyclesPerSec * dDuration;
dMin = 0.0;
dMax = 0.0;
for (double d = 0.0;
d < dDurationInRadians;
d += dRadiansPerSample)
{
// add principle and the dot product of harmonics
// and their amplitudes relative to the principle
dSample = Math.sin(d);
for (int h = 0; h < adHarmonics.length; ++ h)
dSample += adHarmonics[h]
* Math.sin((h + 2) * d);
// factor in amplitude
dSample *= dAmplitude;
// adjust statistics
if (dMin > dSample)
dMin = dSample;
if (dMax < dSample)
dMax = dSample;
// store in byte buffer
i16 = (short) (dSample);
ab[i ++] = (byte) (i16);
ab[i ++] = (byte) (i16 >> 8);
}
// send the byte array to the audio line
line.write(ab, 0, i);
}
public void printStats()
{
System.out.println("sample range was ["
+ dMin + ", " + dMax + "]"
+ " in range of [-32768, 32767]");
if (dMin < -32768.0 || dMax > 32767.0)
System.out.println("sound is clipping"
+ "(exceeding its range),"
+ " so use a lower amplitude");
}
public static void main(String[] asArguments)
throws Exception
{
if (asArguments.length < 3)
{
System.err.println("usage: java SimpleSynth"
+ " <duration>"
+ " <tone.cycles.per.sec>"
+ " <amplitude>"
+ " [<relative.amplitude.harmonic.2>"
+ " [...]]");
System.err.println("pure tone:"
+ " java SimpleSynth 1 440 32767");
System.err.println("oboe-like:"
+ " java SimpleSynth 1 440 15000 0 1 0 .9");
System.err.println("complex:"
+ " java SimpleSynth 1 440 800 .3"
+ " .5 .4 .2 .9 .7 5 .1 .9 12 0 3"
+ " .1 5.2 2.5 .5 1 7 6");
System.exit(0);
}
SimpleSynth synth = new SimpleSynth(asArguments);
synth.playSound();
synth.closeLine();
synth.printStats();
System.exit(0);
}
}
Темы обучения для расширения опыта музыкального синтеза
Есть несколько тем для прочтения, если вы хотите стать экспертом по цифровому синтезу.
- Цифровое аудио
- Выборка сигнала
- Критерии Найквиста
- Как срывать гармоники на струнном инструменте (например, на гитаре)
- Основная тригонометрия, в частности, функция синуса