У меня была такая же проблема для моего музыкального приложения. Как предполагается, на @ Брэд, вот решение с MIDI Tuning стандарт:
Шаги:
- Запрос на изменение настройки
- Карта все 127 MIDI ключи к новым вычисленных частот
Исходный код Gervills TuningApllet3.java помог мне много, чтобы получить эту работу.
Furtunatly, на моей тестовой среде с Windows 7 и JDK 1.8, стандартный MIDI-синтезатор поддерживает стандарт MIDI Tuning. Я не знаю, есть ли возможность проверить, поддерживает ли этот синтезатор этот стандарт или нет.
Как вычислить новые частоты?
private static float getFrequency(final int keyNumber,
final double concertAFreq) {
// Concert A Pitch is A4 and has the key number 69
final int KEY_A4 = 69;
// Returns the frequency of the given key (equal temperament)
return (float) (concertAFreq * Math.pow(2, (keyNumber - KEY_A4)/12d));
}
Для других строев, как пифагорейской настройки, вы можете использовать другие методы вычислений. Здесь мы используем одинаковый темперамент, поскольку MIDI использует его без перенастройки.
Как получить частоты в формате частоты данных?
Как описано в Frequency Data Format, каждая частота е должны представлен 3 байта:
Байт 1: основной ключ.Номер ключа которым имеет в стандартной настройке MIDI (равнотемперированным, A4 = 440 Гц) более низкой или равной частоте F» чем ф
private static int computeBaseKey(final double freq) {
// Concert A Pitch is A4 and has the key number 69
final int A4_KEY = 69;
final double A4_FREQ = 440d;
// Returns the highest key number with a lower or equal frequency than
// freq in standard MIDI frequency mapping (equal temparement, concert
// pitch A4 = 440 Hz).
int baseKey = (int) Math.round((12 * log2(freq/A4_FREQ) + A4_KEY));
double baseFreq = getFrequency(baseKey, A4_FREQ);
if (baseFreq > freq) {
baseKey--;
}
return baseKey;
}
Байт 2 и байт 3: Интервал в Cent от F ' к е
private static double getCentInterval(final double f1, final double f2) {
// Returns the interval between f1 and f2 in cent
// (100 Cent complies to one semitone)
return 1200d * log2(f2/f1);
}
целое представление этого -ного интервала
tuning = (int) (centInterval * 16384d/100d);
и можно разделить на 2 байта и байт 3 с этим кодом:
byte2 = (tuning >> 7) & 0x7f; // Higher 7 Bit
byte3 = tuning & 0x7f; // Lower 7 Bit
Пожалуйста, обратите внимание, что не каждая частота может быть представлена этим форматом. Базовый ключ должен находиться в диапазоне 0,127, а настройка - в диапазоне от 0,2 до 14,1 = 0,16383. Также зарезервировано (byte1, byte2, byte3) = (0x7f, 0x7f, 0x7f).
Полный рабочий пример
Этот пример подстраивает к A4 = 500 Гц и играть хроматическую гамму от С4 до В4:
public static void retune(final Track track, final double concertAFreq) {
if (track == null) {
throw new NullPointerException();
} else if (concertAFreq <= 0) {
throw new IllegalArgumentException("concertAFreq " + concertAFreq
+ " <= 0");
}
final int bank = 0;
final int preset = 0;
final int channel = 0;
addTuningChange(track, channel, preset);
// New frequencies in Hz for the 128 MIDI keys
final double[] frequencies = new double[128];
for (int key = 0; key < 128; key++) {
frequencies[key] = getFrequency(key, concertAFreq);
}
final MidiMessage message = createSingleNoteTuningChange(bank, preset,
frequencies);
track.add(new MidiEvent(message, 0));
}
private static void addTuningChange(final Track track, final int channel,
final int preset) {
try {
// Data Entry
final ShortMessage dataEntry = new ShortMessage(
ShortMessage.CONTROL_CHANGE, channel, 0x64, 03);
final ShortMessage dataEntry2 = new ShortMessage(
ShortMessage.CONTROL_CHANGE, channel, 0x65, 00);
track.add(new MidiEvent(dataEntry, 0));
track.add(new MidiEvent(dataEntry2, 0));
// Tuning program
final ShortMessage tuningProgram = new ShortMessage(
ShortMessage.CONTROL_CHANGE, channel, 0x06, preset);
track.add(new MidiEvent(tuningProgram, 0));
// Data Increment
final ShortMessage dataIncrement = new ShortMessage(
ShortMessage.CONTROL_CHANGE, channel, 0x60, 0x7F);
track.add(new MidiEvent(dataIncrement, 0));
// Data Decrement
final ShortMessage dataDecrement = new ShortMessage(
ShortMessage.CONTROL_CHANGE, channel, 0x61, 0x7F);
track.add(new MidiEvent(dataDecrement, 0));
} catch (final InvalidMidiDataException e) {
throw new AssertionError("Unexpected InvalidMidiDataException", e);
}
}
private static MidiMessage createSingleNoteTuningChange(final int bank,
final int preset, final double[] frequencies) {
// Compute the integer representation of the frequencies
final int[] baseKeys = new int[128];
final int[] tunings = new int[128];
// MIDI Standard tuning frequency
final double STANDARD_A4_FREQ = 440d;
for (int key = 0; key < 128; key++) {
final int baseKey = computeBaseKey(frequencies[key]);
if (baseKey >= 0 && baseKey <= 127) {
final double baseFreq = getFrequency(baseKey, STANDARD_A4_FREQ);
assert baseFreq <= frequencies[key];
final double centInterval = getCentInterval(baseFreq,
frequencies[key]);
baseKeys[key] = baseKey;
tunings[key] = (int) (centInterval * 16384d/100d);
} else {
// Frequency is out of range. Using default MIDI tuning for it
// TODO: Use LOGGER.warn to warn about
baseKeys[key] = key;
tunings[key] = 0;
}
}
// Data to send
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
stream.write((byte) 0xf0); // SysEx Header
stream.write((byte) 0x7e); // Non-Realtime. For Realtime use 0x7f
stream.write((byte) 0x7f); // Target Device: All Devices
stream.write((byte) 0x08); // MIDI Tuning Standard
stream.write((byte) 0x07); // Single Note Tuning Change Bank
stream.write((byte) bank);
stream.write((byte) preset);
stream.write(128); // Number of keys to retune
for (int key = 0; key < 128; key++) {
stream.write(key); // Key to retune
stream.write(baseKeys[key]);
stream.write((tunings[key] >> 7) & 0x7f); // Higher 7 Bit
stream.write(tunings[key] & 0x7f); // Lower 7 Bit
}
stream.write((byte) 0xf7); // EOX
final byte[] data = stream.toByteArray();
final MidiMessage message;
try {
message = new SysexMessage(data, data.length);
} catch (final InvalidMidiDataException e) {
throw new AssertionError("Unexpected InvalidMidiDataException", e);
}
return message;
}
private static int computeBaseKey(final double freq) {
// Concert A Pitch is A4 and has the key number 69
final int A4_KEY = 69;
final double A4_FREQ = 440d;
// Returns the highest key number with a lower or equal frequency than
// freq in standard MIDI frequency mapping (equal temparement, concert
// pitch A4 = 440 Hz).
int baseKey = (int) Math.round((12 * log2(freq/A4_FREQ) + A4_KEY));
double baseFreq = getFrequency(baseKey, A4_FREQ);
if (baseFreq > freq) {
baseKey--;
}
return baseKey;
}
private static double getCentInterval(final double f1, final double f2) {
// Returns the interval between f1 and f2 in cent
// (100 Cent complies to one semitone)
return 1200d * log2(f2/f1);
}
private static double log2(final double x) {
// Returns the logarithm dualis (log with base 2)
return Math.log(x)/Math.log(2);
}
private static float getFrequency(final int keyNumber,
final double concertAFreq) {
// Concert A Pitch is A4 and has the key number 69
final int KEY_A4 = 69;
// Returns the frequency of the given key (equal temperament)
return (float) (concertAFreq * Math.pow(2, (keyNumber - KEY_A4)/12d));
}
public static void main(String[] args) throws Exception {
final int PPQN = 16; // Pulses/Ticks per quarter note
Sequence sequence = new Sequence(Sequence.PPQ, PPQN);
final Track track = sequence.createTrack();
final double a4Freq = 500; // Hz
retune(track, a4Freq);
// Play chromatic Scale from C4 to B4
final int C4_KEY = 60;
final int B4_KEY = 71;
final long quarterTicks = PPQN;
long tick = 0;
for (int key = C4_KEY; key <= B4_KEY; key++) {
final int channel = 0;
final int velocity = 96;
final ShortMessage noteOn = new ShortMessage(ShortMessage.NOTE_ON,
channel, key, velocity);
track.add(new MidiEvent(noteOn, tick));
tick += quarterTicks;
final ShortMessage noteOff = new ShortMessage(
ShortMessage.NOTE_OFF, channel, key, 0);
track.add(new MidiEvent(noteOff, tick));
}
final Sequencer sequencer = MidiSystem.getSequencer();
sequencer.setSequence(sequence);
final CountDownLatch waitForEnd = new CountDownLatch(1);
sequencer.addMetaEventListener(e -> {
if (e.getType() == 47) {
waitForEnd.countDown();
}
});
sequencer.open();
sequencer.start();
System.out.println("started");
waitForEnd.await();
sequencer.stop();
sequencer.close();
System.out.println("ready");
}
Я использовал сообщение не в реальном времени в надежде, что больше синтезаторов поддерживает это, чем версия в реальном времени. Разница между нереальным и реальным временем должна быть, что в реальном времени позволяет перенастроить во время игры. Версия, отличная от реального времени, влияет только на ноты, воспроизводимые после перенастройки.
Это работает? Да, я записал выход и анализировали его с Sonic Visualiser:
Как вы можете видеть, частота пика А4 в спектрограммы почти 500 Гц.
У вас есть MIDI-устройство, которое на самом деле позволяет настроить его так? Я никогда не слышал об этом. – Gabe
@Gabe, некоторые синтезаторы поддерживают его. На ум приходят оригинальные инструменты Akoustik Piano и мой Roland Fantom, но я не думаю, что поддерживаю изменения с помощью SysEx. Опять же, я не пробовал. – Brad