FluidSynth.soについては、自分でコンパイルすると、ソースコードを開示する必要があるライセンスですので、そのまま、公式として公開しているものを用います。

CMakeに、SharedObjectを実行させるために必要なほかのライブラリも追加します。一応、複数環境で実行できるのは、この形式のようですね。
CMakeLists.txtcmake_minimum_required(VERSION 3.4.1)
project(mylib
VERSION 1.0
DESCRIPTION "mylib project"
LANGUAGES CXX
)
add_library(native-lib SHARED src/main/cpp/native-lib.cpp)
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
target_compile_features(native-lib PUBLIC cxx_std_20)
# Create a variable fluidsynth_DIR to specify where the fluidsynth library is located.
set(fluidsynth_DIR C:/github/fluidsynth-2.4.2-android24)
message(“Architecture1 = ${ANDROID_ABI}")
message(“Architecture2 = ${CMAKE_ANDROID_ARCH_ABI}")
if (${ANDROID_ABI} STREQUAL "arm64-v8a")
set(lib_omp_DIR C:/github/android-ndk-r27c/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/18/lib/linux/aarch64/)
elseif (${ANDROID_ABI} STREQUAL "armeabi-v7a")
set(lib_omp_DIR C:/github/android-ndk-r27c/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/18/lib/linux/arm/)
elseif (${ANDROID_ABI} STREQUAL "x86_64")
set(lib_omp_DIR C:/github/android-ndk-r27c/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/18/lib/linux/x86_64/)
elseif (${ANDROID_ABI} STREQUAL "x86")
set(lib_omp_DIR C:/github/android-ndk-r27c/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/18/lib/linux/i386/)
endif()
# Create a variable lib_other_DIR to specify where the other non-fluidsynth libraries is located.
#set(lib_c_DIR C:/github/android-ndk-r27c/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/lib/aarch64-linux-android/)
# set(lib_c_DIR C:/github/back/MixAndCC/app/build/intermediates/cxx/Debug/5ea1y5rp/obj/arm64-v8a)
# Fluidsynth library code will be calling some non-fluidsynth functions which are not part of
# default NDK, so we add the binaries as dependencies of our code.
#add_library(libc++_shared SHARED IMPORTED)
#set_target_properties(libc++_shared PROPERTIES IMPORTED_LOCATION ${lib_c_DIR}/libc++_shared.so)
add_library(libomp SHARED IMPORTED)
set_target_properties(libomp PROPERTIES IMPORTED_LOCATION ${lib_omp_DIR}/libomp.so)
# Our code (native-lib.cpp) will be calling fluidsynth functions, so adding the fluidsynth binaries as dependencies.
add_library(libFLAC SHARED IMPORTED)
set_target_properties(libFLAC PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libFLAC.so)
add_library(libfluidsynth SHARED IMPORTED)
set_target_properties(libfluidsynth PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libfluidsynth.so)
add_library(libfluidsynth-assetloader SHARED IMPORTED)
set_target_properties(libfluidsynth-assetloader PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libfluidsynth-assetloader.so)
add_library(libgio-2.0 SHARED IMPORTED)
set_target_properties(libgio-2.0 PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libgio-2.0.so)
add_library(libglib-2.0 SHARED IMPORTED)
set_target_properties(libglib-2.0 PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libglib-2.0.so)
add_library(libgmodule-2.0 SHARED IMPORTED)
set_target_properties(libgmodule-2.0 PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libgmodule-2.0.so)
add_library(libgobject-2.0 SHARED IMPORTED)
set_target_properties(libgobject-2.0 PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libgobject-2.0.so)
add_library(libgthread-2.0 SHARED IMPORTED)
set_target_properties(libgthread-2.0 PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libgthread-2.0.so)
add_library(libinstpatch-1.0 SHARED IMPORTED)
set_target_properties(libinstpatch-1.0 PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libinstpatch-1.0.so)
add_library(liboboe SHARED IMPORTED)
set_target_properties(liboboe PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/liboboe.so)
add_library(libogg SHARED IMPORTED)
set_target_properties(libogg PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libogg.so)
add_library(libopus SHARED IMPORTED)
set_target_properties(libopus PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libopus.so)
add_library(libpcre SHARED IMPORTED)
set_target_properties(libpcre PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libpcre.so)
add_library(libsndfile SHARED IMPORTED)
set_target_properties(libsndfile PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libsndfile.so)
add_library(libvorbis SHARED IMPORTED)
set_target_properties(libvorbis PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libvorbis.so)
add_library(libvorbisenc SHARED IMPORTED)
set_target_properties(libvorbisenc PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libvorbisenc.so)
add_library(libvorbisfile SHARED IMPORTED)
set_target_properties(libvorbisfile PROPERTIES IMPORTED_LOCATION ${fluidsynth_DIR}/lib/${ANDROID_ABI}/libvorbisfile.so)
# Specifies the directory where the C or C++ source code will look the #include <yourlibrary.h> header files
target_include_directories(native-lib PRIVATE ${fluidsynth_DIR}/include)
# Link everything all together. Notice that native-lib should be the first element in the list.
target_link_libraries(
native-lib
# Non-fluidsynth binaries
#libc++_shared
libomp
# fluidsynth binaries
libFLAC
libfluidsynth
libfluidsynth-assetloader
libgio-2.0
libglib-2.0
libgmodule-2.0
libgobject-2.0
libgthread-2.0
libinstpatch-1.0
liboboe
libogg
libopus
libpcre
libsndfile
libvorbis
libvorbisenc
libvorbisfile
)
そして、Javaから使う場合には、以下のようなラッパーライブラリが必要となります。このクラスには、純正律と平均律を使い分けるコードや、CCからエフェクトの値を設定する部分も含まれています。
native-lib.cpp#include <jni.h>
#include <string>
#include <stdlib.h>
#include <unistd.h>
#include <sstream>
#include "C:/github/fluidsynth-2.4.2-android24/include/fluidsynth.h"
#include <locale>
#include <codecvt>
#define FLUID_PEAK_ATTENUATION 960.0f
#define FLUID_CENTS_HZ_SIZE 1200
#define FLUID_VEL_CB_SIZE 128
#define FLUID_CB_AMP_SIZE 1441
#define FLUID_PAN_SIZE 1002
JavaVM* _javaVM;
jclass _javaClass = nullptr;
typedef struct {
fluid_settings_t* settings;
fluid_synth_t* synth;
fluid_audio_driver_t* audio;
int soundfont_id;
}fluid_handle_t;
fluid_handle_t* _allHandle = nullptr;
class MBString {
std::wstring _w_str;
std::string _mb_str;
public:
MBString(JNIEnv* env, jstring& jstr) {
const jchar* raw = env->GetStringChars(jstr, 0);
jsize len = env->GetStringLength(jstr);
_w_str.assign(raw, raw + len);
env->ReleaseStringChars(jstr, raw);
std::wstring_convert<std::codecvt_utf8<wchar_t> > converter;
_mb_str.append(converter.to_bytes(_w_str));
}
~MBString() {
}
const wchar_t* w_str() {
return _w_str.c_str();
}
const char* mb_str() {
return _mb_str.c_str();
}
};
int createFluidSynth() {
if (_allHandle == nullptr) {
_allHandle = (fluid_handle_t*)malloc(sizeof(fluid_handle_t) * 20);
if (_allHandle == nullptr) {
return -1;
}
memset(_allHandle, 0, sizeof(fluid_handle_t) * 20);
}
for (int i = 1; i < 20; ++i) {
if (_allHandle[i].settings == nullptr) {
_allHandle[i].settings = new_fluid_settings();
_allHandle[i].synth = nullptr;
_allHandle[i].soundfont_id = 0;
return i;
}
}
return -1;
}
void disposeFluidSynth(int id) {
if (_allHandle == nullptr) {
return;
}
if (id < 0 || id >= 20) {
return;
}
fluid_handle_t* handle = _allHandle + id;
if (handle->synth != nullptr) {
delete_fluid_synth(handle->synth);
handle->synth= nullptr;
}
if (handle->settings != nullptr) {
delete_fluid_settings(handle->settings);
handle->settings = nullptr;
}
handle->soundfont_id = 0;
}
fluid_handle_t* getFluid(int handle) {
if (handle < 0 || handle >= 20) {
return nullptr;
}
fluid_handle_t* ptr = _allHandle + handle;
return ptr;
}
extern void initializeMod(int id);
extern void JNICALL jfluid_close(JNIEnv* env, jobject obj, jint id);
jstring JNICALL jfluid_list_program(JNIEnv *env, jobject obj, int id) {
fluid_handle_t* handle = getFluid(id);
if (handle == nullptr) {
return env->NewStringUTF("");
}
fluid_sfont_t * font = fluid_synth_get_sfont_by_id(handle->synth,handle->soundfont_id);
int offset = fluid_synth_get_bank_offset(handle->synth, handle->soundfont_id);
fluid_sfont_iteration_start(font);
std::ostringstream result;
result << "list program " << id << " addresss " << ((unsigned long)(font)) << "\n";
while (true) {
fluid_preset_t *preset = fluid_sfont_iteration_next(font);
if (preset == nullptr) {
preset = fluid_sfont_iteration_next(font);
if (preset == nullptr) {
preset = fluid_sfont_iteration_next(font);
if (preset == nullptr) {
break;
}
}
}
int banknum = fluid_preset_get_banknum(preset) + offset;
int program = fluid_preset_get_num(preset);
const char*name = fluid_preset_get_name(preset);
result << banknum;
result <<",";
result << program;
result <<",";
result << "-1";
result << ",";
result << name;
result << "\n";
}
const char *buf = result.str().c_str();
return env->NewStringUTF(buf);
}
/* calculate cent from key (0 to 11) */
int getKey12Cent(int key) {
switch(key) {
case 0: return 0;
case 2: return 204;
case 4: return 386;
case 5: return 498;
case 7: return 702;
case 9: return 884;
case 11: return 1088;
}
return (getKey12Cent(key -1) + getKey12Cent(key + 1) ) / 2;
}
/* calculate cent from step (0 to 12 + octave offset (1 octave = 1200)) */
double getKeysCent(int step) {
if (step == 0) {
return 0;
}
double acent = 0;
while (step < 0) {
acent -= 1200;
step += 12;
}
while(step >= 12) {
acent += 1200;
step -= 12;
}
return acent + getKey12Cent(step);
}
void getPitchesJustIntonation(double *pitches, int root)
{
/* root = 0 to 12 */
for (int key = 0; key < 0x80; ++ key) {
int distance = key - root;
double cent = getKeysCent(distance);
pitches[key] = cent;
}
}
void getPitchesTemperament(double *pitches) {
for (int key = 0; key < 0x80; ++ key) {
pitches[key] = key * 100;
}
}
void adjustAmust(double *pitches, float hzamust) {
/* hzAmust = around 440 to 443 ? */
auto log2_ratio = log2(hzamust / 440.);
double amust = 100. * 69 + 1200. * log2_ratio;
double slide = amust - pitches[69];
for (int key = 0; key < 0x80; ++key) {
pitches[key] += slide;
}
}
int my_fluid_retune(fluid_handle_t *handle, float hzamust, bool equalTemp, int baseKey) {
int keys[0x80];
double pitches[0x80];
fluid_sfont_t * font = fluid_synth_get_sfont_by_id(handle->synth,handle->soundfont_id);
fluid_sfont_iteration_start(font);
int ret = FLUID_OK;
for (int i = 0; i < 0x80; ++ i) {
keys[i] = i;
}
if (equalTemp) {
getPitchesTemperament(pitches);
adjustAmust(pitches, hzamust);
}
else {
getPitchesJustIntonation(pitches, baseKey);
adjustAmust(pitches, hzamust);
}
int offset = fluid_synth_get_bank_offset(handle->synth, handle->soundfont_id);
while (true) {
fluid_preset_t *preset = fluid_sfont_iteration_next(font);
if (preset == nullptr) {
break;
}
int bank = fluid_preset_get_banknum(preset) + offset;
int program = fluid_preset_get_num(preset);
fprintf(stderr, " bank = %d, program = %d", bank, program);
if (bank < 0 || bank >= 128 || program < 0 || program >= 128) {
continue;
}
/*
if (equalTemp && hzamust== 440.) {
for (int ch = 0; ch < 16; ++ ch) {
fluid_synth_activate_tuning(handle->synth, ch, bank, program, 0);
}
continue;
}*/
int code = fluid_synth_tune_notes(handle->synth, bank, program, 0x80, keys, pitches, 1);
if (code == FLUID_OK) {
for (int ch = 0; ch < 16; ++ ch) {
code = fluid_synth_activate_tuning(handle->synth, ch, bank, program, 1);
}
}
if (code != FLUID_OK) {
ret = code;
}
}
return ret;
}
void JNICALL jfluid_retune(JNIEnv* env, jobject obj, jint id, jfloat hzamust, jboolean equalTemp, int baseKey) {
fluid_handle_t* handle = getFluid(id);
if (handle == nullptr) {
return;
}
my_fluid_retune(handle, hzamust, equalTemp >= 1, baseKey);
}
jint JNICALL jfluid_open(JNIEnv* env, jobject obj, jstring fontFile, jboolean lowlatency)
{
int id = createFluidSynth();
if (id < 0) {
return id;
}
fluid_handle_t* handle = getFluid(id);
if (handle == nullptr) {
return 0;
}
fluid_settings_setstr(handle->settings, "synth.audio-driver", "oboe");
if (lowlatency) {
fluid_settings_setstr(handle->settings, "audio.oboe.performance-mode", "LowLatency");
fluid_settings_setstr(handle->settings, "audio.oboe.sample-rate-conversion-quality", "Fastest");
fluid_settings_setstr(handle->settings, "audio.oboe.sharing-mode", "Exclusive");
}
fluid_settings_setint(handle->settings, "synth.audio-channels", 1);
fluid_settings_setint(handle->settings, "synth.cpu-cores", 2);
fluid_settings_setint(handle->settings, "audio.periods", 4);
fluid_settings_setint(handle->settings, "audio.period-size", 8);
fluid_settings_setint(handle->settings, "audio.realtime-prio", 60);
fluid_settings_setstr(handle->settings, "player.timing-source", "system");
handle->synth = new_fluid_synth(handle->settings);
if (handle->synth == nullptr) {
jfluid_close(env, obj, id);
return 0;
}
fluid_synth_set_polyphony(handle->synth, 128);
fluid_synth_set_interp_method(handle->synth, -1, FLUID_INTERP_LINEAR);
initializeMod(id);
MBString file(env, fontFile);
handle->soundfont_id = fluid_synth_sfload(handle->synth, file.mb_str(), 1);
if (handle->soundfont_id == FLUID_FAILED) {
jfluid_close(env, obj, id);
return 0;
}
handle->audio = new_fluid_audio_driver(handle->settings, handle->synth);
return id;
}
void JNICALL jfluid_close(JNIEnv* env, jobject obj, jint id)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr) {
if (handle->audio != nullptr) {
delete_fluid_audio_driver(handle->audio);
handle->audio = nullptr;
}
if (handle->synth != nullptr) {
delete_fluid_synth(handle->synth);
handle->synth = nullptr;
}
if (handle->settings != nullptr) {
delete_fluid_settings(handle->settings);
handle->settings = nullptr;
}
handle->soundfont_id = 0;
}
}
#define SOUND_CTRL_FILTER_RESONANCE 71
#define SOUND_CTRL_RELEASE 72
#define SOUND_CTRL_ATTACK 73
#define SOUND_CTRL_CUTOFF 74
#define SOUND_CTRL_DECAY 75
#define SOUND_CTRL_SUSTAIN 79
#define SOUND_EFFECT_REVERVE 91
#define SOUND_EFFECT_TREMOLO 92
#define SOUND_EFFECT_CHORUS 93
#define SOUND_EFFECT_DETUNE 94
#define SOUND_EFFECT_PHASER 95
void reset_control(int id, int ch) {
fluid_handle_t* handle = getFluid(id);
fluid_synth_cc(handle->synth, ch, 10, 64); //panpot
fluid_synth_cc(handle->synth, ch, SOUND_CTRL_FILTER_RESONANCE, 0);
fluid_synth_cc(handle->synth, ch, SOUND_CTRL_RELEASE, 64);
fluid_synth_cc(handle->synth, ch, SOUND_CTRL_ATTACK, 64);
fluid_synth_cc(handle->synth, ch, SOUND_CTRL_CUTOFF, 64);
fluid_synth_cc(handle->synth, ch, SOUND_CTRL_DECAY, 64);
fluid_synth_cc(handle->synth, ch, SOUND_CTRL_SUSTAIN, 64);
fluid_synth_cc(handle->synth, ch, SOUND_EFFECT_REVERVE, 30);
fluid_synth_cc(handle->synth, ch, SOUND_EFFECT_TREMOLO, 0);
fluid_synth_cc(handle->synth, ch, SOUND_EFFECT_CHORUS, 0);
fluid_synth_cc(handle->synth, ch, SOUND_EFFECT_DETUNE, 0);
fluid_synth_cc(handle->synth, ch, SOUND_EFFECT_PHASER, 0);
}
void initializeMod(int id) {
fluid_handle_t* handle = getFluid(id);
fluid_synth_set_gain(handle->synth, 0.8);
// soundfont spec says that if cutoff is >20kHz and resonance Q is 0, then no filtering occurs
fluid_mod_t* mod;
/*
mod = new_fluid_mod();
fluid_mod_set_source1(mod,
SOUND_EFFECT_REVERVE, //reverve
FLUID_MOD_CC
| FLUID_MOD_LINEAR
| FLUID_MOD_UNIPOLAR
| FLUID_MOD_POSITIVE);
fluid_mod_set_source2(mod, 0, 0);
fluid_mod_set_dest(mod, GEN_REVERBSEND);
fluid_mod_set_amount(mod, 200);
fluid_synth_add_default_mod(handle->synth, mod, FLUID_SYNTH_ADD);
mod = new_fluid_mod();
fluid_mod_set_source1(mod,
SOUND_EFFECT_CHORUS, //chorus
FLUID_MOD_CC
| FLUID_MOD_LINEAR
| FLUID_MOD_UNIPOLAR
| FLUID_MOD_POSITIVE);
fluid_mod_set_source2(mod, 0, 0);
fluid_mod_set_dest(mod, GEN_CHORUSSEND);
fluid_mod_set_amount(mod, 1000);
fluid_synth_add_default_mod(handle->synth, mod, FLUID_SYNTH_ADD);
F*/
// http://www.synthfont.com/SoundFont_NRPNs.PDF
float env_amount = 20000.0f;
mod = new_fluid_mod();
fluid_mod_set_source1(mod,
SOUND_CTRL_FILTER_RESONANCE,
FLUID_MOD_CC
| FLUID_MOD_UNIPOLAR
| FLUID_MOD_CONCAVE
| FLUID_MOD_POSITIVE);
fluid_mod_set_source2(mod, 0, 0);
fluid_mod_set_dest(mod, GEN_FILTERQ);
fluid_mod_set_amount(mod, FLUID_PEAK_ATTENUATION);
fluid_synth_add_default_mod(handle->synth, mod, FLUID_SYNTH_ADD);
mod = new_fluid_mod();
fluid_mod_set_source1(mod,
SOUND_CTRL_RELEASE, // MIDI CC 72 Release time
FLUID_MOD_CC
| FLUID_MOD_BIPOLAR
| FLUID_MOD_LINEAR
| FLUID_MOD_POSITIVE);
fluid_mod_set_source2(mod, 0, 0);
fluid_mod_set_dest(mod, GEN_VOLENVRELEASE);
fluid_mod_set_amount(mod, 12000);
fluid_synth_add_default_mod(handle->synth, mod, FLUID_SYNTH_ADD);
mod = new_fluid_mod();
fluid_mod_set_source1(mod,
SOUND_CTRL_ATTACK, // MIDI CC 73 Attack time
FLUID_MOD_CC
| FLUID_MOD_BIPOLAR
| FLUID_MOD_LINEAR
| FLUID_MOD_POSITIVE);
fluid_mod_set_source2(mod, 0, 0);
fluid_mod_set_dest(mod, GEN_VOLENVATTACK);
fluid_mod_set_amount(mod, env_amount);
fluid_synth_add_default_mod(handle->synth, mod, FLUID_SYNTH_ADD);
// soundfont spec says that if cutoff is >20kHz and resonance Q is 0, then no filtering occurs
mod = new_fluid_mod();
fluid_mod_set_source1(mod,
SOUND_CTRL_CUTOFF, // MIDI CC 74 Brightness (cutoff frequency, FILTERFC)
FLUID_MOD_CC
| FLUID_MOD_LINEAR
| FLUID_MOD_BIPOLAR
| FLUID_MOD_POSITIVE);
fluid_mod_set_source2(mod, 0, 0);
fluid_mod_set_dest(mod, GEN_FILTERFC);
fluid_mod_set_amount(mod, 10000.0f);
fluid_synth_add_default_mod(handle->synth, mod, FLUID_SYNTH_ADD);
mod = new_fluid_mod();
fluid_mod_set_source1(mod,
SOUND_CTRL_DECAY, // MIDI CC 75 Decay Time
FLUID_MOD_CC
| FLUID_MOD_UNIPOLAR
| FLUID_MOD_LINEAR
| FLUID_MOD_POSITIVE);
fluid_mod_set_source2(mod, 0, 0);
fluid_mod_set_dest(mod, GEN_VOLENVDECAY);
fluid_mod_set_amount(mod, env_amount);
fluid_synth_add_default_mod(handle->synth, mod, FLUID_SYNTH_ADD);
mod = new_fluid_mod();
fluid_mod_set_source1(mod,
SOUND_CTRL_SUSTAIN, // MIDI CC 79 undefined
FLUID_MOD_CC
| FLUID_MOD_UNIPOLAR
| FLUID_MOD_CONCAVE
| FLUID_MOD_NEGATIVE);
fluid_mod_set_source2(mod, 0, 0);
fluid_mod_set_dest(mod, GEN_VOLENVSUSTAIN);
// fluice_voice.c#fluid_voice_update_param()
// clamps the range to between 0 and 1000, so we'll copy that
fluid_mod_set_amount(mod, 1000.0f);
fluid_synth_add_default_mod(handle->synth, mod, FLUID_SYNTH_ADD);
for (int ch = 0; ch < 16; ++ ch) {
reset_control(id, ch);
fluid_synth_set_portamento_mode(handle->synth, ch, FLUID_CHANNEL_PORTAMENTO_MODE_EACH_NOTE);
}
}
void JNICALL jfluid_unload_font(JNIEnv* env, jobject obj, jint id)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->synth != nullptr && handle->soundfont_id > 0) {
fluid_synth_sfunload(handle->synth, handle->soundfont_id, 1);
handle->soundfont_id = 0;
}
}
void JNICALL jfluid_system_reset(JNIEnv* env, jobject obj, jint id)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->synth != nullptr) {
fluid_synth_system_reset(handle->synth);
}
}
fluid_midi_event_t *_event = nullptr;
int gmReset[] = {0xF0, 0x7e, 0x7f, 0x09, 0x01, 0xf7};
int gsReset[] = {0xF0, 0x41, -1, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7};
int xgReset[] = {0xF0, 0x43, -1, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7};
int masterVolume[] = {0xF0, 0x7f, 0x7F, 0x04, 0x01, 0x11, -1, 0xF7};
int masterVolume_pos = 6;
bool matchBytes(int* temp, jbyte* data, int dataLen)
{
for (int i = 0; i < dataLen; ++ i) {
int c = data[i] & 0xff;
int x = temp[i];
if (x == 0xf7) {
if (c == 0xf7) {
return true;
}
else {
return false;
}
}
if (x != c) {
if (x < 0) {
continue;
}
else {
return false;
}
}
}
return false;
}
extern void JNICALL jfluid_short_message(JNIEnv* env, jobject obj, jint id, jint status, jint data1, jint data2);
void JNICALL jfluid_long_message(JNIEnv* env, jobject obj, jint id, jbyteArray data)
{
jsize len = env->GetArrayLength(data);
jbyte* b = env->GetByteArrayElements(data, 0);
if (len <= 3) {
int status = 0;
int data1 = 0;
int data2 = 0;
if (len >= 1) {
status = b[0] & 0xff;
}
if (len >= 2) {
data1 = b[1] & 0xff;
}
if (len >= 3) {
data2 = b[2] & 0xff;
}
jfluid_short_message(env, obj, id, status, data1, data2);
}
else {
if (matchBytes(gmReset, b, len) || matchBytes(gsReset, b, len) || matchBytes(xgReset, b, len)) {
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr) {
fluid_synth_system_reset(handle->synth);
fluid_synth_program_reset(handle->synth);
}
}
else if (matchBytes(masterVolume, b, len)) {
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr) {
int c = b[masterVolume_pos] & 0xff;
fluid_synth_set_gain(handle->synth, 0.8 * c / 127);
}
}
}
env->ReleaseByteArrayElements(data, b, JNI_ABORT);
}
void JNICALL jfluid_short_message(JNIEnv* env, jobject obj, jint id, jint status, jint data1, jint data2)
{
fluid_handle_t* handle = getFluid(id);
if (_event == nullptr) {
_event = new_fluid_midi_event();
}
if (handle != nullptr && handle->synth != nullptr) {
int command = status & 0xf0;
int ch = status & 0x0f;
if (command == 0x80) { //noteoff
fluid_synth_noteoff(handle->synth, ch, data1);
return;
}
if (command == 0x90) {//noteon
if (data2 == 0) {
fluid_synth_noteoff(handle->synth, ch, data1);
return;
}
else {
fluid_synth_noteon(handle->synth, ch, data1, data2);
return;
}
}
if (command == 0xa0) { //polypressure
fluid_synth_key_pressure(handle->synth, ch, data1, data2);
return;
}
if (command == 0xb0) { //CC
if (data1 == 0) {
fluid_synth_bank_select(handle->synth, ch, data2);
}
else {
fluid_synth_cc(handle->synth, ch, data1, data2);
if (data2 == 121) {
reset_control(id, ch);
}
}
return;
}
if (command == 0xc0) { //program change
fluid_synth_program_change(handle->synth, ch, data1);
return;
}
if (command == 0xd0) {
fluid_synth_channel_pressure(handle->synth, ch, data1);
return;
}
if (command == 0xe0) { //pitch
fluid_synth_pitch_bend(handle->synth, ch, (data2 << 7) | data1);
return;
}
}
}
void JNICALL jfluid_set_double(JNIEnv* env, jobject obj, jint id, jstring key, jdouble value)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->settings != nullptr && key != nullptr) {
MBString jkey(env, key);
fluid_settings_setnum(handle->settings, jkey.mb_str(), (float)value);
}
}
void JNICALL jfluid_set_int(JNIEnv* env, jobject obj, jint id, jstring key, jint value)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->settings != nullptr && key != nullptr) {
MBString jkey(env, key);
fluid_settings_setint(handle->settings, jkey.mb_str(), (int)value);
}
}
void JNICALL jfluid_set_string(JNIEnv* env, jobject obj, jint id, jstring key, jstring value)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->settings != nullptr && key != nullptr) {
MBString jkey(env, key);
MBString jvalue(env, value);
fluid_settings_setstr(handle->settings, jkey.mb_str(), jvalue.mb_str());
}
}
void JNICALL fluid_get_double(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->settings != nullptr && key != nullptr) {
jclass cl = env->GetObjectClass(ref);
jmethodID mid = env->GetMethodID(cl, "setValue", "(D)V");
if (mid != 0) {
double value = 0;
MBString jkey(env, key);
fluid_settings_getnum(handle->settings, jkey.mb_str(), &value);
env->CallVoidMethod(ref, mid, (jdouble)value);
}
}
}
void JNICALL jfluid_get_int(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->settings != nullptr && key != nullptr) {
jclass cl = env->GetObjectClass(ref);
jmethodID mid = env->GetMethodID(cl, "setValue", "(I)V");
if (mid != 0) {
int value = 0;
MBString jkey(env, key);
fluid_settings_getint(handle->settings, jkey.mb_str(), &value);
env->CallVoidMethod(ref, mid, (jint)value);
}
}
}
void JNICALL jfluid_get_string(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->settings != nullptr && key != nullptr) {
jclass cl = env->GetObjectClass(ref);
jmethodID mid = env->GetMethodID(cl, "setValue", "(Ljava/lang/String;)V");
if (mid != 0) {
jstring jvalue = nullptr;
char* value = nullptr;
MBString jkey(env, key);
fluid_settings_dupstr(handle->settings, jkey.mb_str(), &value);
jvalue = env->NewStringUTF(value);
env->CallVoidMethod(ref, mid, jvalue);
}
}
}
void JNICALL jfluid_get_default_double(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->settings != nullptr && key != nullptr) {
jclass cl = env->GetObjectClass(ref);
jmethodID mid = env->GetMethodID(cl, "setValue", "(D)V");
if (mid != 0) {
MBString jkey(env, key);
double value = 0;
fluid_settings_getnum_default(handle->settings, jkey.mb_str(), &value);
env->CallVoidMethod(ref, mid, (jdouble)value);
}
}
}
void JNICALL jfluid_get_default_int(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->settings != nullptr && key != nullptr) {
jclass cl = env->GetObjectClass(ref);
jmethodID mid = env->GetMethodID(cl, "setValue", "(I)V");
if (mid != 0) {
MBString jkey(env, key);
int value = 0;
fluid_settings_getint_default(handle->settings, jkey.mb_str(), &value);
env->CallVoidMethod(ref, mid, (jint)value);
}
}
}
void JNICALL jfluid_get_default_string(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->settings != nullptr && key != nullptr) {
jclass cl = env->GetObjectClass(ref);
jmethodID mid = env->GetMethodID(cl, "setValue", "(Ljava/lang/String;)V");
if (mid != 0) {
MBString jkey(env, key);
char* value;
fluid_settings_getstr_default(handle->settings, jkey.mb_str(), &value);
jstring jvalue = env->NewStringUTF(value);
env->CallVoidMethod(ref, mid, jvalue);
}
}
}
void JNICALL jfluid_get_double_range(JNIEnv* env, jobject obj, jint id, jstring key, jobject minimumRef, jobject maximumRef)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->settings != nullptr && key != nullptr) {
jclass clMin = env->GetObjectClass(minimumRef);
jclass clMax = env->GetObjectClass(maximumRef);
jmethodID midMin = env->GetMethodID(clMin, "setValue", "(D)V");
jmethodID midMax = env->GetMethodID(clMax, "setValue", "(D)V");
if (midMin != 0 && midMax != 0) {
double minimum = 0;
double maximum = 0;
MBString jkey(env, key);
fluid_settings_getnum_range(handle->settings, jkey.mb_str(), &minimum, &maximum);
env->CallVoidMethod(minimumRef, midMin, (jdouble)minimum);
env->CallVoidMethod(maximumRef, midMax, (jdouble)maximum);
}
}
}
void JNICALL jfluid_get_int_range(JNIEnv* env, jobject obj, jint id, jstring key, jobject minimumRef, jobject maximumRef)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->settings != nullptr && key != nullptr) {
jclass clMin = env->GetObjectClass(minimumRef);
jclass clMax = env->GetObjectClass(maximumRef);
jmethodID midMin = env->GetMethodID(clMin, "setValue", "(I)V");
jmethodID midMax = env->GetMethodID(clMax, "setValue", "(I)V");
if (midMin != 0 && midMax != 0) {
int minimum = 0;
int maximum = 0;
MBString jkey(env, key);
fluid_settings_getint_range(handle->settings, jkey.mb_str(), &minimum, &maximum);
env->CallVoidMethod(minimumRef, midMin, (jint)minimum);
env->CallVoidMethod(maximumRef, midMax, (jint)maximum);
}
}
}
typedef struct {
JNIEnv* env;
jobject options;
}fluid_settings_foreach_option_data;
void fluid_settings_foreach_option_callback(void* data, char* name, char* option)
{
fluid_settings_foreach_option_data* handle = (fluid_settings_foreach_option_data*)data;
jstring joption = (handle->env)->NewStringUTF(option);
jclass cl = (handle->env)->GetObjectClass(handle->options);
jmethodID mid = (handle->env)->GetMethodID(cl, "add", "(Ljava/lang/Object;)Z");
if (mid != 0) {
(handle->env)->CallBooleanMethod(handle->options, mid, joption);
}
}
void JNICALL jfluid_get_properties(JNIEnv* env, jobject obj, jint id, jstring key, jobject options)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->settings != nullptr) {
MBString jkey(env, key);
fluid_settings_foreach_option_data* data = (fluid_settings_foreach_option_data*)malloc(sizeof(fluid_settings_foreach_option_data));
data->env = env;
data->options = options;
fluid_settings_foreach_option(handle->settings, jkey.mb_str(), data, (fluid_settings_foreach_option_t)fluid_settings_foreach_option_callback);
free(data);
}
}
void JNICALL jfluid_is_realtime_property(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
{
fluid_handle_t* handle = getFluid(id);
if (handle != nullptr && handle->settings != nullptr && key != nullptr) {
jclass cl = env->GetObjectClass(ref);
jmethodID mid = env->GetMethodID(cl, "setValue", "(Z)V");
if (mid != 0) {
MBString jkey(env, key);
int value = fluid_settings_is_realtime(handle->settings, jkey.mb_str());
env->CallVoidMethod(ref, mid, (value != 0 ? JNI_TRUE : JNI_FALSE));
}
}
}
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_1) != JNI_OK) {
return JNI_ERR;
}
_javaVM = vm;
// Find your class. JNI_OnLoad is called from the correct class loader context for this to work.
jclass c = env->FindClass("org/star_advance/mixandcc/midinet/fluidsynth/JFluidSynth");
if (c == nullptr) return JNI_ERR;
static JNINativeMethod methods[] = {
{ (char*)"open", (char*)"(Ljava/lang/String;Z)I", reinterpret_cast<void *>(jfluid_open)},
{ (char*)"retune", (char*)"(IFZI)V", reinterpret_cast<void *>(jfluid_retune)},
{ (char*)"close", (char*)"(I)V", reinterpret_cast<void *>(jfluid_close)},
{ (char*)"listProgram", (char*)"(I)Ljava/lang/String;", reinterpret_cast<void *>(jfluid_list_program)},
{ (char*)"sendShortMessage", (char*)"(IIII)V", reinterpret_cast<void*>(jfluid_short_message)},
{ (char*)"sendLongMessage", (char*)"(I[B)V", reinterpret_cast<void*>(jfluid_long_message)},
/*
void JNICALL jfluid_set_double(JNIEnv* env, jobject obj, jint id, jstring key, jdouble value)
void JNICALL jfluid_set_int(JNIEnv* env, jobject obj, jint id, jstring key, jint value)
void JNICALL jfluid_set_string(JNIEnv* env, jobject obj, jint id, jstring key, jstring value)
void JNICALL fluid_get_double(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
void JNICALL jfluid_get_int(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
void JNICALL jfluid_get_string(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
void JNICALL jfluid_get_default_double(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
void JNICALL jfluid_get_default_int(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
void JNICALL jfluid_get_default_string(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
void JNICALL jfluid_get_double_range(JNIEnv* env, jobject obj, jint id, jstring key, jobject minimumRef, jobject maximumRef)
void JNICALL jfluid_get_int_range(JNIEnv* env, jobject obj, jint id, jstring key, jobject minimumRef, jobject maximumRef)
void JNICALL jfluid_get_properties(JNIEnv* env, jobject obj, jint id, jstring key, jobject options)
void JNICALL jfluid_is_realtime_property(JNIEnv* env, jobject obj, jint id, jstring key, jobject ref)
*/
};
int rc = env->RegisterNatives(c, methods, sizeof(methods) / sizeof(JNINativeMethod));
if (rc != JNI_OK) return rc;
_javaClass = c;
return JNI_VERSION_1_6;
}
JFluidSynth.javapackage org.star_advance.mixandcc.midinet.fluidsynth;
public class JFluidSynth {
static JFluidSynth _instance = new JFluidSynth();
public static JFluidSynth getInstance() {
return _instance;
}
private boolean _loaded = false;
protected JFluidSynth() {
try {
System.loadLibrary("native-lib");
_loaded = true;
} catch (Throwable e) {
System.err.println("App can't load native-lib - (FluidSynth).");
e.printStackTrace();
}
}
public boolean isUsable() {
return _loaded;
}
public native int open(String font, boolean lowlatency);
public native String listProgram(int handle);
public native void close(int handle);
public native void retune(int handle, float hzamust, boolean equalTemp, int baseKey);
public native void sendShortMessage(int handle, int status, int data1, int data2);
public native void sendLongMessage(int handle, byte[] data);
}
もう1ステップあります。Javaアプリについては、Googleに公開するさい、クラスの匿名化が行われます。
それですと、FluidSynthと相互通信するさい、問題となります。
CMakeListsと同様に、<project>/app/のディレクトリに以下のファイルを配置すると、解決します。
proguard-rules.pro# Render Script
-keep class android.support.v8.renderscript.** { *; }
-keep class androidx.renderscript.** { *; }
-keep class org.star_advance.mixandcc.midinet.fluidsynth.JFluidSynth { * ; }
広告を表示しています。
コメント