【问题标题】:Using MIDI in Java在 Java 中使用 MIDI
【发布时间】:2021-11-10 08:24:05
【问题描述】:

我正在尝试编写一个使用 MIDI 的 Java 程序,因此程序必须排列:notes(包含我想要播放的所有音符)和另一个数组 times(指定何时播放音符)音符和时间一次分组三个,所以我可以有多个和弦,问题是程序只播放一个非常简短的音符然后它就停止了,我做错了什么?下面是代码,我使用的是 Java 16。

package application;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.ShortMessage;

public class App {

    public static void main(String[] args)
            throws MidiUnavailableException, InvalidMidiDataException, InterruptedException {

        var receiver = MidiSystem.getReceiver();

        int[] notes = { 60, 64, 67, 60, 65, 67, 55, 59, 62, 55, 60, 62, 53, 57, 60, 53, 58, 60 };
        int[] times = { 0, 0, 0, 1000, 1000, 1000, 2000, 2000, 2000, 3000, 3000, 3000, 4000, 4000, 4000, 5000, 5000,
                5000 };

        for (int i = 0; i < notes.length; i++) {

            int note = notes[i];
            int time = times[i];
            System.out.println(note + ":" + time);
            receiver.send(new ShortMessage(ShortMessage.NOTE_ON, 0, note, 127), time * 1000);
            receiver.send(new ShortMessage(ShortMessage.NOTE_OFF, 0, note, 127), (time + 1000) * 1000);
        }

        Thread.sleep(7000);
    }

}

我认为这与 ShortMessage.NOTE_OFF 有关,但我不确定,也无法弄清楚。

【问题讨论】:

    标签: java java-16 javax.sound.midi


    【解决方案1】:

    问题很可能与您的 times 数组有关。

    取自预言机java documentation on sequancers

    如前所述,程序可以在发送到设备接收器的每条 MIDI 消息中包含一个时间戳。但是,此类时间戳仅用于微调时序,以纠正处理延迟。调用者一般不能设置任意时间戳;传递给 Receiver.send 的时间值必须接近当前时间,否则接收设备可能无法正确调度消息。这意味着,如果应用程序想要提前为整首音乐创建一个 MIDI 消息队列(而不是创建每个消息以响应实时事件),则必须非常小心地安排每个在几乎正确的时间调用 Receiver.send。

    幸运的是,大多数应用程序不必关心这种调度。程序可以使用 Sequencer 对象来管理它的 MIDI 消息队列,而不是调用 Receiver.send 本身。

    您盲目地假设数字(1000、2000、3000...等)是毫秒,但在 api 中定义为ticks。

    就像在任何音乐中一样,您需要定义细分,以及每节拍的脉冲 (PPQ),例如四分音符、16 分音符等。

    在 java 中,这通常是通过定义一个音序器并使用这些设置来完成的,然后在所述音序器上创建一个音轨,然后在该音轨上播放音符。

    这是我在网上找到的一个例子。

    public class MyMidiPlayer {
     
        public static void main(String[] args) {
     
            System.out.println("Enter the number of notes to be played: ");
            Scanner in = new Scanner(System.in);
            int numOfNotes = in.nextInt();
     
            MyMidiPlayer player = new MyMidiPlayer();
            player.setUpPlayer(numOfNotes);
        }
     
        public void setUpPlayer(int numOfNotes) {
     
            try {
     
                // A static method of MidiSystem that returns
                // a sequencer instance.
                Sequencer sequencer = MidiSystem.getSequencer();
                sequencer.open();
     
                // Creating a sequence.
                Sequence sequence = new Sequence(Sequence.PPQ, 4);
     
                // PPQ(Pulse per ticks) is used to specify timing
                // type and 4 is the timing resolution.
     
                // Creating a track on our sequence upon which
                // MIDI events would be placed
                Track track = sequence.createTrack();
     
                    // Adding some events to the track
                for (int i = 5; i < (4 * numOfNotes) + 5; i += 4){
     
                    // Add Note On event
                    track.add(makeEvent(144, 1, i, 100, i));
     
                    // Add Note Off event
                    track.add(makeEvent(128, 1, i, 100, i + 2));
                }
     
                // Setting our sequence so that the sequencer can
                // run it on synthesizer
                sequencer.setSequence(sequence);
     
                // Specifies the beat rate in beats per minute.
                sequencer.setTempoInBPM(220);
     
                // Sequencer starts to play notes
                sequencer.start();
     
                while (true) {
     
                    // Exit the program when sequencer has stopped playing.
                    if (!sequencer.isRunning()) {
                        sequencer.close();
                        System.exit(1);
                    }
                }
            }
            catch (Exception ex) {
     
                ex.printStackTrace();
            }
        }
     
        public MidiEvent makeEvent(int command, int channel,
                                   int note, int velocity, int tick) {
     
            MidiEvent event = null;
     
            try {
     
                // ShortMessage stores a note as command type, channel,
                // instrument it has to be played on and its speed.
                ShortMessage a = new ShortMessage();
                a.setMessage(command, channel, note, velocity);
     
                // A midi event is comprised of a short message(representing
                // a note) and the tick at which that note has to be played
                event = new MidiEvent(a, tick);
            }
            catch (Exception ex) {
     
                ex.printStackTrace();
            }
            return event;
        }
    }
    

    【讨论】:

    • 感谢您的回答,但我想知道是否有办法直接从滴答声转换为毫秒,或者关于如何在不更改所有代码的情况下更改我的代码的一些建议。谢谢!
    • 请阅读我写的the time value passed to Receiver.send must be close to the present time, or the receiving device might not be able to schedule the message correctly这不是java限制,它是硬件限制。
    • 在网上搜索到javax.sound.midi.Receiver.send最后一个参数是指时间,以微秒为单位,那为什么要使用tick呢?这是链接[链接] (docs.oracle.com/javase/7/docs/api/javax/sound/midi/…)
    • 其实,无论是编译还是做任何事情,都是一个奇迹!不要在 main() 方法的静态上下文中这样做。还要让 Thread.sleep() 运行它应该是 Thread execnow = Thread.currentThread(); execnow.sleep(1000);并且该类应该扩展运行要播放的代码的 java.lang.Thread (最好作为内部嵌套子类)。查找有关如何制作 Thread 类和使用 run() 的教程
    • 因为该参数用于向前或向后推动音符以调整较小的延迟。此延迟以微秒为单位定义。阅读文档,上面写着小调整。该参数不用于定义何时应按顺序发送 NOTE_OFF 消息。它用作补偿延迟的偏移时间。 NOTE_OFF 消息按顺序放置在轨道上,轨道上定义的时间是通过使用滴答声来定义的。
    猜你喜欢
    • 2019-03-29
    • 1970-01-01
    • 1970-01-01
    • 2011-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多