Программирование с помощью midi и настройка нот на определенные частоты

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

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


person froggie0106    schedule 29.12.2010    source источник
comment
У вас есть MIDI-устройство, которое действительно позволяет вам так настраивать его? Я никогда о таком не слышал.   -  person Gabe    schedule 29.12.2010
comment
@Gabe, некоторые синтезаторы это поддерживают. На ум приходят Native Instruments Akoustik Piano и мой Roland Fantom, но я не думаю, что они поддерживают изменения с SysEx. Опять же, я не пробовал.   -  person Brad    schedule 29.12.2010


Ответы (2)


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

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

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

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

person Brad    schedule 29.12.2010

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

Шаги следующие:

  1. Запрос на изменение тюнинга
  2. Сопоставьте все 127 MIDI-клавиш с новыми вычисленными частотами

Исходный код Gervills TuningApllet3.java мне очень помогли получить эту работу.

Furtunatly, в моей тестовой среде с Windows 7 и JDK 1.8 стандартный синтезатор MIDI поддерживает стандарт настройки MIDI. Не знаю, есть ли возможность проверить, поддерживает ли синтезатор этот стандарт или нет.

Как вычислить новые частоты?

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 использует его без перенастройки.

Как перевести частоты в формат частотных данных?

Как описано в формате данных частоты, каждая частота f имеет для представления 3 байтами:

Байт 1: базовый ключ. Номер ключа, который в стандартной настройке MIDI (равная темперация, A4 = 440 Гц) имеет более низкую или равную частоту f ', чем 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: интервал в центах от f ' до 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 Гц и воспроизведение хроматической гаммы от C4 до B4:

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:

Возврат к A4 = 500 Гц. A4 выделен на спектрограмме

Как видите, пиковая частота для A4 на спектрограмме составляет почти 500 Гц.

person Vertex    schedule 28.08.2014