- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
自一段时间以来,我一直在寻找一种以Android为目标的在Delphi XE5中播放MIDI的方法。我之前的几个问题都与此“任务”有关:-)。我向embarcadero提出了两个请求:#119422向TMediaPlayer添加MIDI支持,以及#119423向Firemonkey添加MIDI框架,但这没有帮助。我终于成功了。据我所知,还有更多人正在Android上寻找MIDI,我将这个问题与文档答案一起发布。
最佳答案
Android系统具有内部MIDI合成器。您可以通过Android NDK访问它。我已经在article containing some downloads中对此进行了描述。该答案是对本文的简短描述。您将在此处看到的是概念证明。它将显示如何在Android系统上播放MIDI音符,但需要改进。欢迎提出改进建议:-)
使用Eclipse与Java项目交互。我假设您有带有Mobile Pack的Delphi XE5,它为您提供了已经安装的两件东西:Android SDK和NDK。不要通过从Google下载完整的Android SDK重新安装它们。 Download and install Eclipse Android开发工具(ADT)插件,并按照安装说明进行操作。这使您可以使用Delphi XE5已安装的Android SDK / NDK环境(您可以在Delphi中的“选项” |“工具” |“ SDK管理器”中找到路径)。这样,Delphi和Eclipse将共享相同的SDK和NDK。
我使用了MIDI library developed by Willam Farmer。他还拥有完整的SoniVox文档,而我在其他地方找不到。他的驱动程序附带一个完整的示例(Java)程序。我使用创建了自己的项目,并将包名称更改为org.drivers.midioutput,因此所有函数均以Java_org_drivers_midioutput_MidiDriver_为前缀(请参见下面的代码)。
当您希望编译midi.c时,请打开命令窗口,然后在项目目录中调用ndk-build。某些错误消息是可以的。在我的情况下,没有构建mips和x86库。
有一点需要注意:ndk的路径不能包含空格。在让Delphi安装程序安装Delphi的过程中,肯定会有一个空格:该可怕的长文件名中的Rad Studio子目录,Delphi将在其中安装SDK和NDK。为了解决此问题,请在驱动器C:上创建一个空目录,将其命名为C:\ ndk。使用MKLINK将此目录链接到ndk目录。这只能在提升权限的命令提示符下完成,否则您将失去网络连接。该链接是持久的,因此只需关闭命令提示符并打开另一个未提升的命令,所有命令现在都可以正常工作。现在您可以真正使用ndk-build了。
midi.c-SoniVox的NDK接口
////////////////////////////////////////////////////////////////////////////////
//
// MidiDriver - An Android Midi Driver.
//
// Copyright (C) 2013 Bill Farmer
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Bill Farmer william j farmer [at] yahoo [dot] co [dot] uk.
//
///////////////////////////////////////////////////////////////////////////////
// Some slight modifications by Arnold Reinders. Added a test function and changed
// the package to org.drivers.midioutput. The original copyright still applies
#include
// for EAS midi
#include "eas.h"
#include "eas_reverb.h"
// determines how many EAS buffers to fill a host buffer
#define NUM_BUFFERS 4
// EAS data
static EAS_DATA_HANDLE pEASData;
const S_EAS_LIB_CONFIG *pLibConfig;
static EAS_PCM *buffer;
static EAS_I32 bufferSize;
static EAS_HANDLE midiHandle;
// This function is added to test whether the functionality of this NDK code can be accesses
// without needing to access the MIDI system. Added for testing purposes
jint
Java_org_drivers_midioutput_MidiDriver_version (JNIEnv *env, jobject clazz)
{
return 3;
}
// init EAS midi
jint
Java_org_drivers_midioutput_MidiDriver_init(JNIEnv *env,
jobject clazz)
{
EAS_RESULT result;
// get the library configuration
pLibConfig = EAS_Config();
if (pLibConfig == NULL || pLibConfig->libVersion != LIB_VERSION)
return 0;
// calculate buffer size
bufferSize = pLibConfig->mixBufferSize * pLibConfig->numChannels *
NUM_BUFFERS;
// init library
if ((result = EAS_Init(&pEASData)) != EAS_SUCCESS)
return 0;
// select reverb preset and enable
EAS_SetParameter(pEASData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET,
EAS_PARAM_REVERB_CHAMBER);
EAS_SetParameter(pEASData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS,
EAS_FALSE);
// open midi stream
if (result = EAS_OpenMIDIStream(pEASData, &midiHandle, NULL) !=
EAS_SUCCESS)
{
EAS_Shutdown(pEASData);
return 0;
}
return bufferSize;
}
// midi config
jintArray
Java_org_drivers_midioutput_MidiDriver_config(JNIEnv *env,
jobject clazz)
{
jboolean isCopy;
if (pLibConfig == NULL)
return NULL;
jintArray configArray = (*env)->NewIntArray(env, 4);
jint *config = (*env)->GetIntArrayElements(env, configArray, &isCopy);
config[0] = pLibConfig->maxVoices;
config[1] = pLibConfig->numChannels;
config[2] = pLibConfig->sampleRate;
config[3] = pLibConfig->mixBufferSize;
(*env)->ReleaseIntArrayElements(env, configArray, config, 0);
return configArray;
}
// midi render
jint
Java_org_drivers_midioutput_MidiDriver_render(JNIEnv *env,
jobject clazz,
jshortArray shortArray)
{
jboolean isCopy;
EAS_RESULT result;
EAS_I32 numGenerated;
EAS_I32 count;
jsize size;
// jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy)
// void ReleaseByteArrayElements(jbyteArray array, jbyte* elems,
// void* GetPrimitiveArrayCritical(JNIEnv*, jarray, jboolean*);
// void ReleasePrimitiveArrayCritical(JNIEnv*, jarray, void*, jint);
if (pEASData == NULL)
return 0;
buffer =
(EAS_PCM *)(*env)->GetShortArrayElements(env, shortArray, &isCopy);
size = (*env)->GetArrayLength(env, shortArray);
count = 0;
while (count < size) { result = EAS_Render(pEASData, buffer + count, pLibConfig->mixBufferSize, &numGenerated);
if (result != EAS_SUCCESS)
break;
count += numGenerated * pLibConfig->numChannels;
}
(*env)->ReleaseShortArrayElements(env, shortArray, buffer, 0);
return count;
}
// midi write
jboolean
Java_org_drivers_midioutput_MidiDriver_write(JNIEnv *env,
jobject clazz,
jbyteArray byteArray)
{
jboolean isCopy;
EAS_RESULT result;
jint length;
EAS_U8 *buf;
if (pEASData == NULL || midiHandle == NULL)
return JNI_FALSE;
buf = (EAS_U8 *)(*env)->GetByteArrayElements(env, byteArray, &isCopy);
length = (*env)->GetArrayLength(env, byteArray);
result = EAS_WriteMIDIStream(pEASData, midiHandle, buf, length);
(*env)->ReleaseByteArrayElements(env, byteArray, buf, 0);
if (result != EAS_SUCCESS)
return JNI_FALSE;
return JNI_TRUE;
}
// shutdown EAS midi
jboolean
Java_org_drivers_midioutput_MidiDriver_shutdown(JNIEnv *env,
jobject clazz)
{
EAS_RESULT result;
if (pEASData == NULL || midiHandle == NULL)
return JNI_FALSE;
if ((result = EAS_CloseMIDIStream(pEASData, midiHandle)) != EAS_SUCCESS)
{
EAS_Shutdown(pEASData);
return JNI_FALSE;
}
if ((result = EAS_Shutdown(pEASData)) != EAS_SUCCESS)
return JNI_FALSE;
return JNI_TRUE;
}
////////////////////////////////////////////////////////////////////////////////
//
// MidiDriver - An Android Midi Driver.
//
// Copyright (C) 2013 Bill Farmer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Bill Farmer william j farmer [at] yahoo [dot] co [dot] uk.
//
///////////////////////////////////////////////////////////////////////////////
package org.drivers.midioutput;
import java.io.File;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
// MidiDriver
public class MidiDriver implements Runnable
{
private static final int SAMPLE_RATE = 22050;
private static final int BUFFER_SIZE = 4096;
private Thread thread;
private AudioTrack audioTrack;
private OnMidiStartListener listener;
private short buffer[];
// Constructor
public MidiDriver ()
{
Log.d ("midi", " *** MidiDriver started");
}
public void start ()
{
// Start the thread
thread = new Thread (this, "MidiDriver");
thread.start ();
} // start //
@Override
public void run ()
{
processMidi ();
} // run //
public void stop ()
{
Thread t = thread;
thread = null;
// Wait for the thread to exit
while (t != null && t.isAlive ())
Thread.yield ();
} // stop //
// Process MidiDriver
private void processMidi ()
{
int status = 0;
int size = 0;
// Init midi
Log.d ("midi", " *** processMIDI");
if ((size = init()) == 0)
return;
buffer = new short [size];
// Create audio track
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT,
BUFFER_SIZE, AudioTrack.MODE_STREAM);
if (audioTrack == null)
{
shutdown ();
return;
} // if
// Call listener
if (listener != null)
listener.onMidiStart();
// Play track
audioTrack.play();
// Keep running until stopped
while (thread != null)
{
// Render the audio
if (render (buffer) == 0)
break;
// Write audio to audiotrack
status = audioTrack.write (buffer, 0, buffer.length);
if (status < 0) break; } // while // Render and write the last bit of audio if (status > 0)
if (render(buffer) > 0)
audioTrack.write(buffer, 0, buffer.length);
// Shut down audio
shutdown();
audioTrack.release();
} // processMidi //
public void setOnMidiStartListener (OnMidiStartListener l)
{
listener = l;
} // setOnMidiStartListener //
public static void load_lib (String libName)
{
File file = new File (libName);
if (file.exists ())
{
System.load (libName);
} else
{
System.loadLibrary (libName);
}
} // Listener interface
public interface OnMidiStartListener
{
public abstract void onMidiStart ();
} // OnMidiStartListener //
// Native midi methods
public native int version ();
private native int init ();
public native int [] config ();
private native int render (short a []);
public native boolean write (byte a []);
private native boolean shutdown ();
// Load midi library
static
{
System.loadLibrary ("midi");
}
}
package org.drivers.midioutput;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import org.drivers.midioutput.MidiDriver.OnMidiStartListener;
import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;
import android.os.Environment;
import android.util.Log;
public class MIDI_Output implements OnMidiStartListener
{
protected MidiDriver midi_driver;
protected MediaPlayer media_player;
public MIDI_Output ()
{
// Create midi driver
midi_driver = new MidiDriver();
Log.d ("midi", " *** midi_driver opened with version " +
String.valueOf (midi_driver.version ()));
// Set onmidistart listener to this class
if (midi_driver != null)
midi_driver.setOnMidiStartListener (this);
} // MIDI_Output () //
public int test_int (int n)
{
int sq = n * n;
// Log.d ("midi", " *** test_int computes " + String.valueOf (sq));
return n * n;
}
public void start ()
{
if (midi_driver != null)
{
midi_driver.start ();
Log.d ("midi", " *** midi_driver.start ()");
}
} // start //
public void stop ()
{
if (midi_driver != null)
{
midi_driver.stop ();
Log.d ("midi", " *** midi_driver.stop ()");
}
stopSong ();
} // stop //
// Listener for sending initial midi messages when the Sonivox
// synthesizer has been started, such as program change. Runs on
// the MidiDriver thread, so should only be used for sending midi
// messages.
@Override
public void onMidiStart()
{
Log.d ("midi", " *** onSMidiStart");
// TODO
}
// Sends a midi message
protected void putShort (int m, int n, int v)
{
if (midi_driver != null)
{
byte msg [] = new byte [3];
msg [0] = (byte) m;
msg [1] = (byte) n;
msg [2] = (byte) v;
Log.d ("midi", " *** putShort (" + String.valueOf (m) + ", " + String.valueOf (n) + ", " + String.valueOf (v) + ")");
midi_driver.write (msg);
} // if
} // putShort //
public boolean isPlayingSong ()
{
return media_player != null;
} // isPlayingSong //
public void playSong (String audioFilename)
{
String audioPath;
try
{
FileDescriptor fd = null;
audioFilename = "/Data/d/song.mid";
File baseDir = Environment.getExternalStorageDirectory ();
audioPath = baseDir.getAbsolutePath () + audioFilename;
Log.d ("midi", " *** Look for file: " + audioPath);
FileInputStream fis = new FileInputStream (audioPath);
fd = fis.getFD ();
if (fd != null)
{
Log.d ("midi", " *** Found file, trying to play: " + audioPath);
MediaPlayer mediaPlayer = new MediaPlayer ();
mediaPlayer.setDataSource (fd);
mediaPlayer.prepare ();
mediaPlayer.start ();
}
} catch (Exception e)
{
Log.d ("midi", " *** Exception while trying to play file: " + e.getMessage ());
}
}
public void stopSong ()
{
if (media_player != null)
{
media_player.stop ();
media_player.release ();
media_player = null;
} // if
} // stopSong //
} // Class: MIDI_Output //
unit MIDI_Output_Device;
interface
uses
System.SysUtils,
FMX.Types,
Androidapi.JNIBridge,
Androidapi.JNI.JavaTypes,
Androidapi.Jni,
Androidapi.JNI.Dalvik,
Androidapi.JNI.GraphicsContentViewText;
const
test_apk_fn = '/storage/sdcard0/Data/d/MIDI_Output.apk';
type
TMIDI_Output_Device = class (TObject)
private
JavaEnv: PJNIEnv;
context: JContext;
CL: JDexClassLoader;
JavaObject: JObject;
JavaObjectID: JNIObject;
jTempClass: Jlang_Class;
jTemp: JObject;
oTemp: TObject;
jLocalInterface: ILocalObject;
optimizedpath_jfile: JFile;
dexpath_jstring, optimizedpath_jstring: JString;
fun_version: JNIMethodID;
fun_start: JNIMethodID;
fun_put_short: JNIMethodID;
fun_play_song: JNIMethodID;
public
constructor Create;
procedure setup_midi_output (class_name: string);
procedure put_short (status, data_1, data_2: integer);
procedure play_song (file_name: string);
end; // Class: MIDI_Output_Device //
implementation
uses
FMX.Helpers.Android;
constructor TMIDI_Output_Device.Create;
begin
setup_midi_output ('MIDI_Output');
end; // Create //
procedure TMIDI_Output_Device.setup_midi_output (class_name: string);
var
javaClassName: string;
ji: JNIInt;
jiStatus, jiData_1, jiData_2: JNIValue;
begin
javaClassName := Format ('org.drivers.midioutput/%s', [class_name]);
context := SharedActivityContext;
JavaEnv := TJNIResolver.GetJNIEnv;
Log.d ('Loading external library from "' + test_apk_fn + '"');
dexpath_jstring := StringToJString (test_apk_fn);
// locate/create a directory where our dex files can be put
optimizedpath_jfile := context.getDir (StringToJString ('outdex'), TJContext.javaclass.mode_private);
optimizedpath_jstring := optimizedpath_jfile.getAbsolutePath;
Log.d ('Path for DEX files = ' + JStringToString (optimizedpath_jstring));
Log.d ('APK containing target class = ' + JStringToString (dexpath_jstring));
CL := TJDexClassLoader.JavaClass.init (dexpath_jstring, optimizedpath_jstring, nil, TJDexClassLoader.JavaClass.getSystemClassLoader);
// Test whether the Dex class is loaded, if not, exit
if not assigned (CL) then
begin
Log.d ('?Failed to get DEXClassLoader');
exit;
end; // if
// Load the Java class
jTempClass := CL.loadClass (StringToJString (javaClassName));
if assigned (jTempClass) then
begin
jTemp := jTempClass; // N.B You could now import the entire class
if jTemp.QueryInterface (ILocalObject,jLocalInterface) = S_OK then
begin
// supports ilocalobject
JavaObject := jTempClass.newInstance;
oTemp := JavaObject as TObject;
JavaObjectID := tjavaimport (otemp).GetObjectID;
Log.d (oTemp.ClassName);
// try to access the version function from the midi_output class
fun_version := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'version', '()I');
if not assigned (fun_version) then
begin
Log.d ('?fun_version not supported');
end else
begin
ji := JavaEnv^.CallIntMethodA (JavaEnv, JavaObjectID, fun_version, nil);
Log.d ('version returns ' + inttostr (ji));
end; // if
// try to access the start function from the midi_output class
fun_start := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'start', '()V');
if not assigned (fun_start) then
begin
Log.d ('?fun_start not supported');
end else
begin
JavaEnv^.CallVoidMethodA (JavaEnv, JavaObjectID, fun_start, nil);
Log.d ('fun_start found');
end; // if
// try to access the putShort function from the midi_output class
fun_put_short := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'putShort','(III)V');
if not assigned (fun_put_short) then
begin
Log.d ('?putShort not supported');
end else
begin
Log.d (Format (' @@@ putShort (%d, %d, %d)', [jiStatus.i, jiData_1.i, jiData_2.i]));
put_short ($90, 60, 127);
end; // if
// try to access the playSong function from the midi_output class
fun_play_song := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'playSong', '(Ljava/lang/String)V');
if not assigned (fun_play_song) then
begin
Log.d ('?playSong not supported');
end else
begin
Log.d (' @@@ playSong found');
end; // if
end else
begin
Log.d ('?Could not derive ILOCALOBJECT');
end;
end else Log.d ('?'+javaClassname+' not found')
end; // setup_midi_output //
procedure TMIDI_Output_Device.put_short (status, data_1, data_2: integer);
var
jiStatus, jiData_1, jiData_2: JNIValue;
x: array of JNIOBJECT;
begin
jiStatus.i := status;
jiData_1.i := data_1;
jiData_2.i := data_2;
setLength (x, 3);
x [0] := jiStatus.l;
x [1] := jiData_1.l;
x [2] := jiData_2.l;
Log.d (Format ('putShort (%d, %d, %d)', [jiStatus.i, jiData_1.i, jiData_2.i]));
JavaEnv^.CallVoidMethodV (JavaEnv, JavaObjectID, fun_put_short, x);
end; // put_short //
procedure TMIDI_Output_Device.play_song (file_name: string);
var
x: array of JNIObject;
begin
SetLength (x, 1);
x [0] := StringToJNIString (JavaEnv, file_name);
Log.d ('playSong (' + file_name + ')');
JavaEnv^.CallVoidMethodV (JavaEnv, JavaObjectID, fun_play_song, x);
end; // playSong //
end. // Unit: MIDI_Output_Device //
关于android - 在Android上使用Delphi实现MIDI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21148723/
我正在使用 Python 中的 mido 库进行 MIDI 项目。我在手册中看到时间签名的元消息,其值为:notated_32nd_notes_per_beat,其默认值为 8。 这是有道理的。但是
我猜出一个相当简单的问题,但在任何地方都找不到明确的答案。 背景:我有一个多轨道midi文件,其中第一条轨道带有TEMPO控件。我需要将其他轨道中的ABSOLUTE_TICK计数转换为“秒”(与mid
我目前正在实现一个应用程序来对 MIDI 文件执行一些任务,我当前的问题是将我读过的音符输出到 LilyPond 文件。 我已将 note_on 和 note_off 事件合并为具有绝对开始和绝对持续
我有一把 Yamaha MIDI 吉他,当我播放使用 XG MIDI 标准编码的 MIDI 文件时,它会导致吉他上的某些灯打开和关闭。我正在尝试确定导致此问题的 MIDI 事件,以便我可以在不使用 M
我正在 Unity 中开发一款可以从音乐生成关卡的游戏。我计划在游戏中包含简单的文本文件(不必采用标准格式)并解析它们以生成关卡。问题是,我需要能够将 MIDI 文件转换为文本格式(最好不要像 Mus
我目前正在构建一个软件,用于显示 MIDI 文件中的音符。我可以从 NoteOn 和 NoteOff 事件中获取每个音调字母,但我不知道如何获取或如何计算音符类型(全音、半音、八音..)和其他拍号。我
我想知道如何将 MIDI 节拍转换为实际播放秒数。 例如,如果 MIDI PPQ(每四分音符的脉冲数)为 1120,我如何将其转换为真实世界的播放秒数? 最佳答案 您需要两条信息: PPQ (每四分音
我想了解可用于对 MIDI 文件执行一些简单任务的开源库: 一次读取一个音符或和弦; 提取给定的工具并将其单独重新编码到新文件中; 允许生成“可定制”乐谱——我的意思是我应该能够使用库改变从 midi
我已经用谷歌搜索了一段时间,但我发现的所有信息都有些模棱两可。我不是 midi 专家,我只对 midi 文件感兴趣。我需要知道此元事件的用途,以及它如何影响多轨 MIDI 文件(格式 1)的播放。 最
我正在尝试使用 AKSequencer() 从 .mid 文件发送 midi 数据,在虚拟输出上以在应用程序外部使用它(下面有更多详细信息)。 我的问题是我的 AKSequencer 没有将 midi
我想制作一个简单的 VST 插件来执行此操作: 分析音频流(音量、节拍等...) 在分析器的输出上有触发器(例如,当音量 > 阈值时做某事) 根据触发器生成 MIDI 事件 这是为了能够链接插件,即使
我有一个 .mid 文件 - this一个具体的。除了标题 block 之外,这里是 midi 的相关部分。第一个轨道 block 仅包含元事件,描述为 4D 54 72 6B 00 00 00 52
我试图为 Java 程序实现一个 MIDI 播放器。所以我开始使用 javax.sound.midi 库。我在那里加载了我的 Sequencer 和我的 Synthesizer: private vo
import javax.sound.midi.*; import javax.swing.*; import java.awt.*; /** * Created by Jonik on 09.01
我已经搜索了一段时间,但找不到我想要做的事情的答案。 我想播放一个 midi 文件,并在播放时在屏幕上显示音符。当音符停止播放时,它应该从屏幕上消失。 我可以使用音序器播放 midi,但不知道如何获取
对 MIDI 中的音符长度有误解。我在 Ableton Live 中制作了一个简单的 2 个全音符 midi 文件(整个文件是 1 个小节),然后将其导出并使用 python 脚本(mididump.
关闭。这个问题不满足Stack Overflow guidelines .它目前不接受答案。 想改善这个问题吗?更新问题,使其成为 on-topic对于堆栈溢出。 2年前关闭。 Improve thi
这不是关于 how to create a MIDI file from Lilypond 的重复问题.我已经做过很多次了。 我创建了一些 lilypond 片段,我想从中提取 MIDI。通常我会在
我的目标是将从 Alesis 合成器发送的字节码流转换为人类可读的格式。我需要能够进行“程序转储”并读取组成补丁名称的 10 个字符的字符串。 为了从合成器接收“程序转储”,我通过 MIDI-OX 向
是否有任何公共(public)数据库允许从设备 ID 代码中获取型号名称(在回复 f0 7e 7f 06 01 f7 SysEx 时返回)? 最佳答案 MIDI 制造商协会维护一个 list of I
我是一名优秀的程序员,十分优秀!