AndroidのJavaアプリから、FluidSynth.soをコールする方法【七転び八起き】

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

CMakeに、SharedObjectを実行させるために必要なほかのライブラリも追加します。一応、複数環境で実行できるのは、この形式のようですね。

CMakeLists.txt
cmake_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.java
package 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 { * ; }
広告を表示しています。

コメント

タイトルとURLをコピーしました