Программирование с миди и настройка нот на определенные частоты - PullRequest
2 голосов
/ 29 декабря 2010

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

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

Ответы [ 2 ]

3 голосов
/ 29 декабря 2010

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

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

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

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

2 голосов
/ 28 августа 2014

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

Шаги:

  1. Запрос на изменение настройки
  2. Отображение всех 127 MIDIключи к новым вычисленным частотам

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

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

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

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Формат данных , каждая частота 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 :

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

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

...