- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我一直在尝试用 Java 实现我自己的异步 MIDI 音序器,它通过处理 MidiEvent 的列表将 ShortMessage 发送到 VST。我需要达到最佳性能,以便在收听音频输出时不会有声音延迟。
问题是,由于滴答不准确地增加(有时增加太快或太慢,这会打乱所有 MidiEvent 的计时),肯定会有声音滞后。
下面是排序器的代码:
package com.dranithix.spectrum.vst;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.ShortMessage;
import com.synthbot.audioplugin.vst.vst2.JVstHost2;
/**
*
* @author Kenta Iwasaki
*
*/
public class VstSequencer implements Runnable {
public static long BPM = 128L, PPQ = 4L;
private long oneTick = (60000L / (BPM * PPQ)) * 1000000;
private Map<MidiEvent, Long> currentEvents = new ConcurrentHashMap<MidiEvent, Long>();
private long startTime = System.nanoTime(), elapsedTicks = 0;
private JVstHost2 vst;
public VstSequencer(JVstHost2 vst) {
this.vst = vst;
}
@Override
public void run() {
while (true) {
if (System.nanoTime() - startTime >= oneTick) {
elapsedTicks++;
startTime = System.nanoTime();
}
Iterator<MidiEvent> it = currentEvents.keySet().iterator();
while (it.hasNext()) {
MidiEvent currentEvent = it.next();
long eventTime = currentEvent.getTick() - elapsedTicks;
if (eventTime <= 0) {
vst.queueMidiMessage((ShortMessage) currentEvent
.getMessage());
it.remove();
}
}
}
}
public void queueEvents(List<MidiEvent> events) {
Map<MidiEvent, Long> add = new HashMap<MidiEvent, Long>();
for (MidiEvent event : events) {
event.setTick(event.getTick() + elapsedTicks);
add.put(event, event.getTick());
}
currentEvents.putAll(add);
}
public void queueEvent(MidiEvent event) {
event.setTick(event.getTick() + elapsedTicks);
currentEvents.put(event, event.getTick());
}
}
我们怎样才能提高这个系统的性能?我们能否确保此类系统不会有可闻延迟(例如:固定时间步长)?
提前致谢。
编辑: 为了隔离可听延迟的原因,我可以确认 VST 本身或向 VST 发送 MIDI 消息的框架没有延迟。它与目前在音序器中使用的基于滴答的计时系统有关。
已解决:我通过在同一线程中包含 VST 事件处理代码(它们最初位于不同的线程中),使 VST 事件与事件序列器本身并行处理,从而解决了该问题。对于那些读到这篇文章并一直在寻找将 MIDI 事件排序到 JVstHost2 或任何类似的 Java VST 主机库的人,请随意将部分固定代码用于您自己的项目,因为我很难找到合适的 VST 排序在线,因为 VST 是一种很少接触 Java 的商业格式。
已解决的代码:
package com.dranithix.spectrum.vst;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.ShortMessage;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import com.synthbot.audioplugin.vst.vst2.JVstHost2;
/**
*
* @author Kenta Iwasaki
*
*/
public class VstSequencer implements Runnable {
private static final float ShortMaxValueAsFloat = (float) Short.MAX_VALUE;
public static float BPM = 120f, PPQ = 2f;
private static float oneTick = 60000f / (BPM * PPQ);
private List<MidiEvent> currentEvents = new ArrayList<MidiEvent>();
private long startTime = System.currentTimeMillis(), elapsedTicks = 0;
private JVstHost2 vst;
private final float[][] fInputs;
private final float[][] fOutputs;
private final byte[] bOutput;
private int blockSize;
private int numOutputs;
private int numAudioOutputs;
private AudioFormat audioFormat;
private SourceDataLine sourceDataLine;
public VstSequencer(JVstHost2 vst) {
this.vst = vst;
numOutputs = vst.numOutputs();
numAudioOutputs = Math.min(2, numOutputs);
blockSize = vst.getBlockSize();
fInputs = new float[vst.numInputs()][blockSize];
fOutputs = new float[numOutputs][blockSize];
bOutput = new byte[numAudioOutputs * blockSize * 2];
audioFormat = new AudioFormat((int) vst.getSampleRate(), 16,
numAudioOutputs, true, false);
DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class,
audioFormat);
sourceDataLine = null;
try {
sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
sourceDataLine.open(audioFormat, bOutput.length);
sourceDataLine.start();
} catch (LineUnavailableException lue) {
lue.printStackTrace(System.err);
System.exit(1);
}
}
@Override
protected void finalize() throws Throwable {
try {
sourceDataLine.drain();
sourceDataLine.close();
} finally {
super.finalize();
}
}
private byte[] floatsToBytes(float[][] fData, byte[] bData) {
int index = 0;
for (int i = 0; i < blockSize; i++) {
for (int j = 0; j < numAudioOutputs; j++) {
short sval = (short) (fData[j][i] * ShortMaxValueAsFloat);
bData[index++] = (byte) (sval & 0x00FF);
bData[index++] = (byte) ((sval & 0xFF00) >> 8);
}
}
return bData;
}
@Override
public void run() {
while (true) {
if (Thread.interrupted()) {
break;
}
if (System.currentTimeMillis() - startTime >= oneTick) {
elapsedTicks++;
startTime = System.currentTimeMillis();
}
vst.processReplacing(fInputs, fOutputs, blockSize);
sourceDataLine.write(floatsToBytes(fOutputs, bOutput), 0,
bOutput.length);
Iterator<MidiEvent> it = currentEvents.iterator();
while (it.hasNext()) {
MidiEvent currentEvent = it.next();
long eventTime = currentEvent.getTick() - elapsedTicks;
if (eventTime <= 0) {
vst.queueMidiMessage((ShortMessage) currentEvent
.getMessage());
it.remove();
}
}
}
}
public void queueEvents(List<MidiEvent> events) {
for (MidiEvent event : events) {
event.setTick(event.getTick() + elapsedTicks);
}
currentEvents.addAll(events);
}
public void queueEvent(MidiEvent event) {
event.setTick(event.getTick() + elapsedTicks);
currentEvents.add(event);
}
}
最佳答案
我怀疑问题是:
event.setTick(event.getTick() + elapsedTicks);
add.put(event, event.getTick());
事件流大概已经有时间戳,所以没有必要向它们添加 elapsedTicks
。这只是意味着随着时间的推移,它们变得越来越晚。
有几种非常明显的方法可以提高上面代码的性能。很难说它们是否是您问题的原因:
1:不要忙等待:上面的代码没有阻塞的方法,直到有事情要做(ConcurrentHashMap
不提供阻塞行为)。相反,它循环不断地消耗 CPU 周期,即使在无事可做时也是如此。这种行为通常会受到操作系统调度程序的惩罚。您的线程在未运行时无法安排事件,其当前设计鼓励这样做。
2:为 currentEvents
使用以 MIDIEvent 为键的 HashMap 是一个糟糕的选择,而且效率低下。您需要迭代整个容器以找到需要传送到 VST 的事件。此外,由于没有顺序保证,您可能会无序地交付落在当前报价中的事件。考虑使用 SortedMap
,其中键是交货时间。事件现在是有序的,最快的在结构的开头。提供 Activity 很便宜。
这条线还有一个潜在的问题——它不会导致不规律的计时,但我的意思可能是 oneTick 是错误的:
private long oneTick = (60000L / (BPM * PPQ)) * 1000000;
除以 BPM * PPQ
会导致截断。先做乘法。
关于Java - 使用 PPQ 节拍进行计时的自定义 MIDI 音序器的可听滞后,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28637246/
好的,所以我编辑了以下... 只需将以下内容放入我的 custom.css #rt-utility .rt-block {CODE HERE} 但是当我尝试改变... 与 #rt-sideslid
在表格 View 中,我有一个自定义单元格(在界面生成器中高度为 500)。在该单元格中,我有一个 Collection View ,我按 (10,10,10,10) 固定到边缘。但是在 tablev
对于我的无能,我很抱歉,但总的来说,我对 Cocoa、Swift 和面向对象编程还很陌生。我的主要来源是《Cocoa Programming for OS X》(第 5 版),以及 Apple 的充满
我正在使用 meta-tegra 为我的 NVIDIA Jetson Nano 构建自定义图像。我需要 PyTorch,但没有它的配方。我在设备上构建了 PyTorch,并将其打包到设备上的轮子中。现
在 jquery 中使用 $.POST 和 $.GET 时,有没有办法将自定义变量添加到 URL 并发送它们?我尝试了以下方法: $.ajax({type:"POST", url:"file.php?
Traefik 已经默认实现了很多中间件,可以满足大部分我们日常的需求,但是在实际工作中,用户仍然还是有自定义中间件的需求,为解决这个问题,官方推出了一个 Traefik Pilot[1] 的功
我想让我的 CustomTextInputLayout 将 Widget.MaterialComponents.TextInputLayout.OutlinedBox 作为默认样式,无需在 XML 中
我在 ~/.emacs 中有以下自定义函数: (defun xi-rgrep (term) (grep-compute-defaults) (interactive "sSearch Te
我有下表: 考虑到每个月的权重,我的目标是在 5 个月内分散 10,000 个单位。与 10,000 相邻的行是我最好的尝试(我在这上面花了几个小时)。黄色是我所追求的。 我试图用来计算的逻辑如下:计
我的表单中有一个字段,它是文件类型。当用户点击保存图标时,我想自然地将文件上传到服务器并将文件名保存在数据库中。我尝试通过回显文件名来测试它,但它似乎不起作用。另外,如何将文件名添加到数据库中?是在模
我有一个 python 脚本来发送电子邮件,它工作得很好,但问题是当我检查我的电子邮件收件箱时。 我希望该用户名是自定义用户名,而不是整个电子邮件地址。 最佳答案 发件人地址应该使用的格式是: You
我想减小 ggcorrplot 中标记的大小,并减少文本和绘图之间的空间。 library(ggcorrplot) data(mtcars) corr <- round(cor(mtcars), 1)
GTK+ noob 问题在这里: 是否可以自定义 GtkFileChooserButton 或 GtkFileChooserDialog 以删除“位置”部分(左侧)和顶部的“位置”输入框? 我实际上要
我正在尝试在主页上使用 ajax 在 magento 中使用 ajax 显示流行的产品列表,我可以为 5 或“N”个产品执行此操作,但我想要的是将分页工具栏与结果集一起添加. 这是我添加的以显示流行产
我正在尝试使用 PasswordResetForm 内置函数。 由于我想要自定义表单字段,因此我编写了自己的表单: class FpasswordForm(PasswordResetForm):
据我了解,新的 Angular 7 提供了拖放功能。我搜索了有关 DnD 的 Tree 组件,但没有找到与树相关的内容。 我在 Stackblitz 上找到的一个工作示例.对比drag'ndrop功能
我必须开发一个自定义选项卡控件并决定使用 WPF/XAML 创建它,因为我无论如何都打算学习它。完成后应该是这样的: 到目前为止,我取得了很好的进展,但还有两个问题: 只有第一个/最后一个标签项应该有
我要定制xtable用于导出到 LaTeX。我知道有些问题是关于 xtable在这里,但我找不到我要找的具体东西。 以下是我的表的外观示例: my.table <- data.frame(Specif
用ejs在这里显示日期 它给我结果 Tue Feb 02 2016 16:02:24 GMT+0530 (IST) 但是我需要表现为 19th January, 2016 如何在ejs中执行此操作?
我想问在 JavaFX 中使用自定义对象制作 ListView 的最佳方法,我想要一个每个项目如下所示的列表: 我搜了一下,发现大部分人都是用细胞工厂的方法来做的。有没有其他办法?例如使用客户 fxm
我是一名优秀的程序员,十分优秀!