使用midi编程,并将音符调整到特定频率

时间:2010-12-29 06:50:15

标签: java performance frequency midi

我正在开展一个项目,我需要能够以尽可能高的精度生成不同频率的midi音符。我最初尝试用Java编写我的程序,但事实证明,sound.midi包不支持更改音符的调音,除非频率是等回声频率(或者至少它不是1.4,我没有'能够找到证据证明这已在最近的版本中得到修复)。我一直在努力寻找一个更合适的语言/库来完成这项任务,但由于这是我第一次使用MIDI进行编程,而且我对特定调优功能的需求是必不可少的,所以我一直很难找到我需要的东西。< / p>

我正在寻找有经验编写MIDI程序的人的建议,以了解哪些语言非常有用,特别是将音符调整到特定频率。任何带有API文档和示例代码的网站链接也非常有用。

2 个答案:

答案 0 :(得分:3)

您不能普遍改变调整。这是合成器的一个功能,与MIDI无关。

现在,有一些SysEx消息通常被理解为此任务。有关详细信息,请参阅此参考:http://www.midi.org/techspecs/midituning.php

另一个参考:http://www.microtonal-synthesis.com/MIDItuning.html

同样,MIDI只是一种控制协议。产生声音取决于合成器。合成器不必支持更改调整,通常不支持。这与MIDI无关,与您发送MIDI数据的语言无关。

答案 1 :(得分:2)

我的音乐应用程序遇到了同样的问题。正如@Brad所假设的,这是一个MIDI调整标准的解决方案:

步骤如下:

  1. 请求调整更改
  2. 将所有127个MIDI键映射到新的计算频率
  3. Gervills TuningApllet3.java的源代码帮助我完成了这项工作。

    幸运的是,在我的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使用它而不重新调整。

    如何将频率变为频率数据格式?

    Frequency Data Format所述,每个频率 f 必须用3个字节表示:

    字节1 :基本密钥。标准MIDI调音(等于气质,A4 = 440 Hz)的关键数字是低于或等于频率 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 Hz并播放从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");
    }
    

    我希望使用非实时消息,更多的合成器支持这个而不是实时版本。非实时和实时之间的区别应该是,realtime允许在播放时重新调整。非实时版本仅影响重调后播放的音符。

    有用吗? 是的,我已记录输出并使用Sonic Visualiser

    进行分析

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

    如您所见,频谱图中A4的峰值频率接近500 Hz。