2010-12-29 2 views
2

Я работаю над проектом, в котором мне нужно иметь возможность генерировать MIDI-ноты с различными частотами с максимальной точностью. Первоначально я пытался написать свою программу на Java, но оказывается, что пакет sound.midi не поддерживает изменение настроек заметок, если частоты не равны темпам, которые были равномерны (или, по крайней мере, это не было в 1.4, и я не знаю, t удалось найти доказательства того, что это было исправлено в последних версиях). Я пытаюсь найти более подходящий язык/библиотеку для выполнения этой задачи, но так как это мое первое программирование с использованием MIDI, и моя потребность в конкретных функциях настройки очень важна, у меня были значительные проблемы с поиском именно того, что мне нужно.Программирование с midi и настройка нот на определенные частоты

Я ищу советы от людей, которые имеют опыт написания MIDI-программ относительно того, какие языки полезны, особенно для настройки заметок на определенные частоты. Любые ссылки на сайты с документами API и примером кода также будут чрезвычайно полезными.

+0

У вас есть MIDI-устройство, которое на самом деле позволяет настроить его так? Я никогда не слышал об этом. – Gabe

+0

@Gabe, некоторые синтезаторы поддерживают его. На ум приходят оригинальные инструменты Akoustik Piano и мой Roland Fantom, но я не думаю, что поддерживаю изменения с помощью SysEx. Опять же, я не пробовал. – Brad

ответ

3

Вы не можете повсеместно изменить настройку. Это особенность синтезатора и не имеет ничего общего с MIDI.

Теперь есть некоторые сообщения SysEx, которые обычно понимаются для этой задачи. Смотрите эту ссылку для получения дополнительной информации: http://www.midi.org/techspecs/midituning.php

Другой ссылка: http://www.microtonal-synthesis.com/MIDItuning.html

Опять же, MIDI только протокол управления. Произведение звуков зависит от синтезатора. Синтез не должен поддерживать изменение настройки, и часто это не так. Это не имеет ничего общего с MIDI и не связано с языком, на который вы отправляете данные MIDI.

2

У меня была такая же проблема для моего музыкального приложения. Как предполагается, на @ Брэд, вот решение с MIDI Tuning стандарт:

Шаги:

  1. Запрос на изменение настройки
  2. Карта все 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 Гц) более низкой или равной частоте чем ф

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:

Retuning to A4 = 500 Hz. The A4 is highlighted in the spectrogram

Как вы можете видеть, частота пика А4 в спектрограммы почти 500 Гц.