Support for custom banks loaded in runtime

Create issue
Issue #38 new
Wohlstand created an issue

Hello! I have some bunch of IBK files made by me, and I was used the Jamie O’Connel’s custom FM MIDI Driver to play&record music with it, but it has a poor support of drums (I can't implement full support of them).

That driver (Win 3x / Win 9x): http://wohlsoft.ru/docs/Sounds/SMBX_OPL/SMBX_OPL_Sounds_src/Software%20for%20FM%20chip/fmsynthJamieOConnel.zip

SMB Timbre tool (DOS 16 bit) which I used to make my mods: http://wohlsoft.ru/docs/Sounds/SMBX_OPL/SMBX_OPL_Sounds_src/Software%20for%20FM%20chip/sbtimbre.zip

My banks: http://wohlsoft.ru/docs/Sounds/SMBX_OPL/SMBX_OPL_Sounds_src/Software%20for%20FM%20chip/IBK%20banks/

EDIT: Half year ago I found the ADLMIDI program (which I redesigned into the library) which implements an MIDI-Player over OPL chip emulator and uses a big set of various bank formats. It works light and is not overloads CPU, tested on Pentium IV machine. This also supports overclocking 18-channels 2-op and 6-channels 4-op limits by running multiple copies of chip emulators and then MIDI-player shares notes between all running chip emulators.

Now I got skills and then I quickly made a bank editor for Junglevision format:

https://github.com/Wohlstand/OPL3BankEditor (also on bitbucket too: https://bitbucket.org/Wohlstand/opl3bankeditor)

That format is supported both 2-operator, 4-operator instruments. Then later I have been added more bank formats: DMX, TMB, IBK, BNK (AdLib and HMI), and AIL (also I working on other formats too).

I think, I'll make a new file format which must support:

  • 128 melodic instruments and 128 drum sets (Even as idea, allow support of multiple banks like GS or XG to have able to make full GS/XG instruments set)
  • Three modes: 2-operators (OPL2), double-voice 2-operators (Pseudo 4-operators, like DMX), 4-operators (OPL3)
  • Fine tuning for second voice (Pseudo-4-operators mode only)
  • Note offsets per instrument (to allow octave offsets for some instruments instead of changing a frequency multiplication flags which are may change resulted in sound instead "just up/down one octave")
  • Custom instrument names (especially for GX/XG instrument sets more than standard 128)

EDIT2: The new WOPL format has been created

It is already supported by OPL3 Bank Editor and by libADLMIDI:

The full specification is here: https://github.com/Wohlstand/OPL3BankEditor/blob/master/Specifications/WOPL-and-OPLI-Specification.txt

Feel free to use it on your side!

Comments (40)

  1. James Alan Nguyen repo owner

    Some of the above are already being used internally, but at the moment patch customization is limited to a build from source. I've just recently got back to working on this. Rhythm mode support is planned. Still need to cater for those specific cases and/or whether or not it should be turned on/off during playback, or stays on/off based on loaded bank.

  2. Jean Pierre Cimalando

    I absolutely want this feature, and I have started to implement something. It is not difficult to do at all.

    The simplest way to send patches to the synth is by custom sysex. Is there a format we can agree with? I started with something like this.

    • F0 7d [DeviceId]
    • 00
    • 02 (01="Request" 02="Send")
    • "MaliceX " (some 8bit unique identifier)
    • "OP3PATCH" (type of payload)
    • [instrument number]
    • [patchStruct length]
    • [patchStruct data....] (in 4bit integer pairs)
    • [Checksum]
    • F7
  3. Wohlstand reporter

    SysEx-ish way looks like to let MIDI filed became like CMF files where it's pure MIDI file and FM patches are stored as SysEx. Looks interesting, and possibly you can use OPLI instrument format as a base which I have designed to support most features of all bank formats are known in history of OPL3 chip

  4. James Alan Nguyen repo owner

    yea maybe it's about time i've gotten off my arse and did something about this (along with IBK, BNK etc.). the sysex idea by @jpcima was something I've considered in the past but never got around to doing it. last time i tried something like this was in the form of a hook-in application using windows shared memory, which is non-portable.

  5. Wohlstand reporter

    The best idea is to make the small Control panel (or a separate app that communicating to driver) dialog that will setup running driver with next actions:

    • Change path to bank file
    • Toggle between of embedded or custom bank
    • Toggle some other flags
    • Reset etc.
  6. Jean Pierre Cimalando

    The basic idea was to keep the banks as concatenated sysex, yeah (.syx file).

    The roadmap right now is like this in my mind

    • implement custom sysex
    • banks from header to .syx (trivial)
    • Bisqwit adldata to .syx
    • write/adapt tools to convert formats to custom .syx

    Honestly I dont know much of all the OPL banks around and their formats. What I know is adlmidi provides various conversion functions to its own internal format, which has potential to reuse.

  7. James Alan Nguyen repo owner

    The issue with Control panel is that it would require the use of IPC or shared memory if communicating with the driver in real-time. That said I recall some older sound cards had this behaviour (eg: some variants of Crystal Audio which made a popup appear whenever the MIDI driver was opened). I'm a bit divided on whether to take an INI file or Registry approach for load-time configuration however.

    As for doing this via sysex, turns out there's already a similar issue ticket on that: https://bitbucket.org/djtubig-malicex/opl3-synth-driver/issues/17/playback-time-patch-register-modification

  8. Wohlstand reporter

    Also, OffTop: Can you document the patch bank format stored in your header files to let me import them by my OPL3 Bank Editor? Once I have imported some of your instruments through a guessing of bytes by code into my own bank, but as result was incorrect, I had to polish them manually in the editor.

  9. Wohlstand reporter

    SysEx way is something different than change setup of the driver include of complete bank switching. Yes, you can use IPS and that must not be hard. And yeah, you can store the setup in registry and use IPS to poke driver reload it's config. That will be much simpler than passing entire settings via IPS.

    Anyway, old Jammie O'Connel's driver for Win3x and for Win9x had a configure tool (also a control panel dialog) which changes settings of running driver on a fly: banks, tremolo/vibrato bits, drum channels, etc.

  10. James Alan Nguyen repo owner

    Yeah I was reading your WOPL spec, I definitely agree WOPL is probably the format to go. Only question I have is, it doesn't appear to be that any format currently defined in the wild supports patch-level pan (eg: 2op-1 left, 2op-2 right) or retrigger time period (both used by patches from Voyetra's SuperSAPI! driver), and I haven't gotten around to implementing that either.

    There's probably no point documenting my format cause I think it was largely based on the windows default scheme which hugely resembles Junglevision with some minor differences - the banks I will most likely convert to use a more commonly used format (eg: WOPL). I do have a use case for pitch falling/rising rate (think 2xx or 1xx for XM effects, albeit nonstandard for MIDI), but i think i'm going to take it out as it doesn't appear to have much utility as a patch parameter (but it could exist as a custom MIDI-controllable effect, maybe).

  11. Wohlstand reporter
    wild supports patch-level pan (eg: 2op-1 left, 2op-2 right) or retrigger time period (both used by patches from Voyetra's SuperSAPI! driver)
    

    Good features to support them in WOPL too! And I would be happy to be able import Voyetra's instruments to play them everywhere rather Windows 3.1 DosBox environment or any old computer. I only need a documentation and sample banks to hack them.

    About of pitch falling/rising, I have a plan to provide that in libADLMIDI and libOPNMIDI to take more advantages from instruments, especially to implement something like SynthDrum with it's pitch falling.

    Why I asking about of documenting, I wanna take a valid result rather this: https://github.com/Wohlstand/libADLMIDI/tree/master/fm_banks/op3_files/2x2patch here is my attempt to convert your header into JungleVision (which unfortunately, doesn't supports note offsets) and there are was a lot of AM/FM incorrect bits setup was set.

  12. Wohlstand reporter

    P.S. As you see, WOPL at this moment have changed at least three versions are adding new features into the file formats, so, to implement file parser, you must handle versioing too, or you can use this STL-code as base: https://github.com/Wohlstand/libADLMIDI/blob/master/utils/gen_adldata/file_formats/load_wopl.h It is used in the generator of embedded banks database for libADLMIDI which also reads first banks only without of multibank support And parser that completely supports all features of WOPL which is used in runtime loading custom banks support: https://github.com/Wohlstand/libADLMIDI/blob/master/src/adlmidi_load.cpp#L67-L340

  13. Jean Pierre Cimalando

    Now I just have a sysex parsing in the driver. And this converter.

    WOPL support will come later, I'm still on the basics.

    (in fact I'm stuck by a stupid sysex software which refuses to send for some reason, and such is the joy of using linux sometimes)

  14. Jean Pierre Cimalando

    Ok I'm reporting success with sysex. A pull request will arrive when I backport the modifs.

  15. Jean Pierre Cimalando

    Can you give me some guidance on how to convert WOPL to patchStruct, given data structures of libADLMIDI?

    To start, I am not sure how to navigate the imbrications std::map and std::pair, and I may not be doing it right.

    I think I can deduce what the 11 bytes are mapped to. what to do about finetune? it seems to have something with initializing B0-3. There is this mention in patchStruct.

       /* use in a patch, the block should be 4 to indicate
       normal pitch, 3 => octave below, etc. */
    
  16. Wohlstand reporter

    Look:

    ins.op[0].finetune = (int8_t)toSint16BE(idata + 32);
    ins.op[1].finetune = (int8_t)toSint16BE(idata + 34);
    

    Those "fine tunes" are note offsets. Means half-tone offset from currently playing MIDI key.

    BUT, this fine tune is a frequency detune modifier:

        ins.adlins.voice2_fine_tune = 0.0;
        int8_t voice2_fine_tune = int8_t(idata[37]);
        if(voice2_fine_tune != 0)
        {
            if(voice2_fine_tune == 1)
                ins.adlins.voice2_fine_tune = 0.000025;
            else if(voice2_fine_tune == -1)
                ins.adlins.voice2_fine_tune = -0.000025;
            else
                ins.adlins.voice2_fine_tune = ((voice2_fine_tune * 15.625) / 1000.0);
        }
    

    It is used for DMX voices are pseudo-4-operator, there are two parallel 2-op voices.

    Also, again, the full detailed specification to understand the format itself: https://github.com/Wohlstand/OPL3BankEditor/blob/master/Specifications/WOPL-and-OPLI-Specification.txt

  17. James Alan Nguyen repo owner

    "note offsets" probably shoud be called "coarse tune" instead tbh. it's what every synth out there refers that to, albeit at the oscillator level.

  18. Wohlstand reporter

    "note offsets" probably shoud be called "coarse tune" instead tbh. it's what every synth out there refers that to, albeit at the oscillator level.

    Thanks for correction, I'll re-factor that

    And yeah, I think, to make your work easier, I'll try to make on a quick hand a super-simple WOPL file parser which will give you the set of structures are easy to understand rather you will dig a hard-understandable ADLDATA generator.

    Original ADLMIDI's code structure sucks and even I did some big job in libADLMIDI to refactor/rework many parts of original code, it still have many things to polish and refactor to resolve most of existing crap in the code.

  19. Wohlstand reporter

    Some fields are super-specific to libADLMIDI, for example, ms_sound_kon and ms_sound_koff, there are millisecond delays of while tone is sustaining and while it sounding after key off. That thing is used by libADLMIDI's channel manager which uses that to avoid breaking of long-playing notes and other things.

  20. Jean Pierre Cimalando

    So far I figured out this much. Next is to find how E682 bytes are ordered in adlmidi, and I don't know what rhythmMap is.

    void ConvertFromWOPL(patchStruct &dst, const WOPL_Inst &src)
    {
        dst.bOp = PATCH_1_2OP;
        switch (src.adlins.flags) {
        case WOPL_Flag_Enable4OP: dst.bOp = PATCH_1_4OP; break;
        case WOPL_Flag_Pseudo4OP: dst.bOp = PATCH_2_2OP; break;
        }
    
        dst.bRhythmMap = ;
    
        dst.bAtC0[0] = src.op[0].feedconn;
        dst.bAtC0[1] = src.op[1].feedconn;
    
        for (unsigned opnum = 0; opnum < 4; ++opnum) {
            dst.op[opnum].bAt20 = ;
            dst.op[opnum].bAt40 = (opnum & 1) ?
                src.op[opnum/2].carrier_40 : src.op[opnum/2].modulator_40;
            dst.op[opnum].bAt60 = ;
            dst.op[opnum].bAt80 = ;
            dst.op[opnum].bAtE0 = ;
        }
    
        /* src.op[0].finetune */  //
    }
    
  21. Wohlstand reporter

    Don't put attention to rythmMap, it's an ugly workaround I will remove and I will replace with per-instrument flags where AdLib drums are in use. That was a set of flags to tell which legacy AdLib drum type is set on the voice. For example, kick, snare, hi-hat, tom, or cymbell.

  22. Wohlstand reporter

    Just now I have made WOPL parser in pure C you can easily pick up:

    https://github.com/Wohlstand/OPL3BankEditor/tree/master/src/FileFormats/wopl

    It reads raw data taken from a file and gives you a structure with data you can take and use. For convenience, I named operator's fields with both register title and addresses. And I have documented every thing inside of structures and enums to let you understand data easily.

    How to use:

    At first, read the full bank file into byte array in memory, and then:

    int errCode;
    WOPLFile * wopl = WOPL_LoadBankFromMem(raw_filedata, raw_filedata_length, &errCode);
    
    if(!wopl)
    {
        // use errCode and WOPL_ErrorCodes enum to identify error
        // and nuke everything!
    }
    
    //...
    // Put data from `wopl` into your internal structures and arrays
    //...
    
    
    //Clear the WOPL data away after you completed to parse it
    WOPLFree(wopl);
    

    If using my new structures and your code, will be:

    void ConvertFromWOPL(patchStruct &dst, const WOPLInstrument &src)
    {
        dst.bOp = PATCH_1_2OP;
        if(inst_flags & WOPL_Ins_Pseudo4op)
            dst.bOp = PATCH_2_2OP;//When both 4-op and Pseudo4op flags defined, Pseudo-4op has higher priority
        else if(inst_flags & WOPL_Ins_4op)
            dst.bOp = PATCH_1_4OP;
        // Be careful! Some instruments may have 'WOPL_Ins_IsBlank' flag which 
        // means instrument with no sound. I use it in multi-bank to detect when 
        // I must to fall to root bank (bank-0 in melodic sets) when selected patch
        // is absence.
    
        dst.bAtC0[0] = src.fb_conn1_C0;
        dst.bAtC0[1] = src.fb_conn2_C0;
    
        for (unsigned opnum = 0; opnum < 4; ++opnum) {
            dst.op[opnum].bAt20 = src.operators[opnum].avekf_20;
            dst.op[opnum].bAt40 = src.operators[opnum].ksl_l_40;
            dst.op[opnum].bAt60 = src.operators[opnum].atdec_60;
            dst.op[opnum].bAt80 = src.operators[opnum].susrel_80;
            dst.op[opnum].bAtE0 = src.operators[opnum].waveform_E0;
        }
        // Use src.note_offset1 as modifier of MIDI note number for 2-op and 4-op instrument (+12 or -12 means modify tone in one octave up or down)
    
        // Use src.note_offset1 in pseudo-4op mode to modify tone of second voice
    
        // Use src.second_voice_detune to micro-detune the tone of 
        // second voice in pseudo-4op mode
    
        // Use src.percussion_key_number to tell which note drum instrument must
        // play in fact with no matter to "slot" where it is stored. Means, which note it
        // will play when it's a melodic instrument?
    
        // src.midi_velocity_offset can be just ignored, it is storing but not used
        // So, ignore it
    
        // src.delay_on_ms and src.delay_off_ms values are physical sounding time
        // of the given timbre. It is used inside of internal ADLMIDI's channel 
        // management system to find the best channel in condition of "age" 
        // of playing note in some physical channel. You can just ignore them 
        // as you are using different channels management system which 
        // doesn't relying on those values.
    }
    
  23. Jean Pierre Cimalando

    @Wohlstand thanks for doing this I'll include it.

    I have a status update, despite being so sick lately and not very productive. I want to ask a few things too. (sorry for making a long post)

    1.

    To finish with this issue I had started making a simple windows control panel. However it's disfunctional because I have not yet figured out WinMM. It crashes. (a funny thing is, it works under Wine).

    It has the WOPL file loader, and it transmits them to the midi port as sysex events. For the WOPL I just extracted the code parts from libADLMIDI and I condensed it into a "miniwopl", not terribly elegant but functional.

    2.

    On my converter wopl2syx: I added a conversion mode to C header file. So I was able to compare a Fatman 2Op's wopl with this driver's patch hardcoded in source. I have at least some confidence now I got most of the conversion right.

    3.

    I am tempted of forking the project to revamp the voice management.

    Not only, I would like to add multi-chip, but I have encountered problems of sticky notes. (only with 4op patches)

    @Wohlstand how hard would it be to add realtime MIDI input to libADLMIDI? if it's a trivial thing, I might as well switch to your driver.

    4.

    To somewhat emulate multi-chip I start multiple instances and dispatch the midi channels between them. screen.png I'd like to reduce idle CPU time, because it mutiplies by the number of chips (issue #37 about CPU problem). It looked into NukedOPL to save computation of the slots which have the envelope in the Off state, and definitely it seems like a feasible thing.

  24. Wohlstand reporter
    1. To make entire thing work better, stabler, and easier, put the loading of bank file into driver side and use registry to pick up the path to the bank file and setup on system load. And when bank is invalid or absence, just restore the default embedded bank back. The only command must be sent from CP is to trigger driver to re-load it's config from registry and try to fetch the bank. I don't think it's good idea to pass everything via SysEx which is looks.... weird? You can use SysEx if you don't want to use shared memory or IPS to communicate driver from the CP, but to just poke driver to reload it's config from the registry and then a bank file. Also, reviewed the "fat4.wopl" file I have found it has no note-offsets on rock organ and on the tuba I have added on my side to fix their octaves are was incorrect, especially because of JunleVision format that lacks note offsets in total. Originally when I imported your 4x4 bank into JunleVision OP3 format, it sucked because of some instruments had wrong octaves, for example, finger bass had lower octave it must be. Then I converted into AIL OPL format which supports note offsets and later into WOPL.

    2. I see, anyway since I made wopl_file.c/h, I'll use them in libADLMIDI too instead of separated bank parser to pass bank data and easier synchronize format updates between bank editor and the library.

    3. By request of GZDoom project, I have been added real-time API into libADLMIDI, so, for now you are able to use it to pass MIDI events into library and then just fetch generated PCM data and play them on audio output API. As example, I have made VLC codec where RT-API is in use to pass MIDI events into libADLMIDI and then generate out the PCM sound which will be played out. adl_rt_* calls are needed to pass MIDI events, and adl_generate to fetch an audio output, but, the size of generated audio data must be same as length of pause between MIDI event rows (one MIDI row is a group of MIDI events are has zero delay between of each others). Another example inside of GZDoom you can check out here. To have most light-weight, you can drop away embedded banks and build libADLMIDI without of having a dozen of embedded banks and rely on custom-loaded WOPL banks (library allows you to pass how filepath in UTF8 encoding, and also the memory block, to use it, for example, from embedded resources). Anyway, myself I wanna to create an interface to receive MIDI events through ALSA and play MIDI from software I using to edit MIDI files and use ADLMIDI instead of boring FluidSynth or any other WT synthesizer (or even OPNMIDI that uses MIDI playing and channel management base of libADLMIDI, but uses YM2612 instead of YMF262). I don't know, what do you think, but via FM chip I can better express my music than through any WT synths I have at me: FluidSynth, MS-GS, Roland VSC, Yamaha XG, etc.

    4. Nuked OPL3 is very accurate, and it's the simulator of real chip which reproduces logic of real chip to provide most of accuracy. And yeah it requires lot of power to use it. The DosBox 0.74 emulator also used in libADLMIDI is fastest and well-accurate emulator I know (even it has some inaccuracies in some cases are resolvable by using of Nuked OPL3 emulator). So, I use DosBox emulator on slow devices include Emscripten, mobile devices or slow computers, and Nuked OPL3 on modern powerful desktop machines where it can work well without of giving slowing/lagging and choppy sound output. Anyway, in nearest moment I'll add into libADLMIDI the ability to have both emulation cores and switch them in runtime to choose between of accuracy and performance.

    P.S. @jpcima , be healthful and don't ill 😉 🦊

  25. Jean Pierre Cimalando

    put the loading of bank file into driver side and use registry to pick up the path to the bank file and setup on system load

    Actually I believe there is such a thing in windows as MIDI "driver specific messages". If that is the case, there shouldn't be need for a separate ipc channel.

    Anyway if I was going to talk to the MIDI driver, I told myself why not just use the sysex protocol which is already there.

    By request of GZDoom project, I have been added real-time API into libADLMIDI,

    So it can actually take real-time input, I utterly missed that. It's great.

    Anyway, myself I wanna to create an interface to receive MIDI events through ALSA

    If you will target ALSA at some point, it may be interesting to make it a softsynth plugin. Especially in Juce, from which you get the standalone Linux, and all the plugin formats.

    I don't know, what do you think, but via FM chip I can better express my music than through any WT synths

    I can't really call myself musician, but I really enjoy fm. A nice sy77 is often near me but it replaces by no means the good old fm chips. :)

    Nuked OPL3 is very accurate, and it's the simulator of real chip which reproduces logic of real chip to provide most of accuracy

    I don't mind about desecrating perfection of Nuked OPL with my heretic work. If it sounds same to my ear without bit purity, fine enough. Who knows, I may be even tempted at one point to add >1bit panning.

  26. Wohlstand reporter

    Actually I believe there is such a thing in windows as MIDI "driver specific messages". If that is the case, there shouldn't be need for a separate ipc channel. Anyway if I was going to talk to the MIDI driver, I told myself why not just use the sysex protocol which is already there.

    Okay, this makes sense! I accidentally though you gonna to make that through MIDI-out chain as regular SysEx event came from MIDI files. This thing is seems different one. So, as I told, use this interface only to poke driver to reload it's config from registry (where you will write changed settings from your CP) and reload the bank file from the disk or restore default embedded.

    So it can actually take real-time input, I utterly missed that. It's great.

    I have added it aproximately-recently, so, now you know that 😉

    If you will target ALSA at some point, it may be interesting to make it a softsynth plugin. Especially in Juce, from which you get the standalone Linux, and all the plugin formats.

    Оnce I gone from Windows, on Linux I use FluidSynth to play music/notes in MIDI-related software, and now I'm able to implement the daemon which will use ALSA as source of MIDI events and play them through libADLMIDI.

    I can't really call myself musician, but I really enjoy fm. A nice sy77 is often near me but it replaces by no means the good old fm chips. :)

    Not a problem ;3 For me it's a hobby where I usually replicating some liked music into MIDI format, creating remake, or just polishing any MIDI file I have to make it sound better, and in rare cases I may compose a new melody.

    I don't mind about desecrating perfection of Nuked OPL with my heretic work. If it sounds same to my ear without bit purity, fine enough. Who knows, I may be even tempted at one point to add >1bit panning.

    Full panning is implementable, but in cost of 2x channels use and volumes balancing which is made in Jamie O'Connel's driver for Win3x/9x and in some other things like "OPL Synth Emulation" in GZDoom and kode54's ADLMIDI fork made as foobar plugin, and I gonna to backport some his features include panning stereo into my libADLMIDI. In case of emulation, just use more virtual chips to don't pay by FM channels.

  27. Jean Pierre Cimalando

    @Wohlstand After I convert my code to the new wopl library, I find that parameters of operator 1-2 are swapped, and so are operators 3-4.

    I exactly used the code fragment you have written.

    Are you sure of the operator order in your library code? it seems opposite of the previous wopl code I used, and also opposite of the internal driver format.

    for (unsigned opnum = 0; opnum < 4; ++opnum) {
        dst.op[opnum].bAt20 = src.operators[opnum].avekf_20;
        dst.op[opnum].bAt40 = src.operators[opnum].ksl_l_40;
        dst.op[opnum].bAt60 = src.operators[opnum].atdec_60;
        dst.op[opnum].bAt80 = src.operators[opnum].susrel_80;
        dst.op[opnum].bAtE0 = src.operators[opnum].waveform_E0;
    }
    
  28. Wohlstand reporter

    Operators order at me is:

    #define CARRIER1    0
    #define MODULATOR1  1
    #define CARRIER2    2
    #define MODULATOR2  3
    

    And yeah, in wopl_file.h macros are mentoined roles of operators are was wrong, I checked another in-editor macros and there are was swapped, yeah.

    So, in your case you will need to have a map:

    size_t opmap[4] = {
        WOPL_OP_MODULATOR1, //operators 1 and 2
        WOPL_OP_CARRIER1,
        WOPL_OP_MODULATOR2, //operators 3 and 4
        WOPL_OP_CARRIER2
    };//Please re-order them to match your internal operators order
    for (unsigned opnum = 0; opnum < 4; ++opnum) {
        size_t srcop = opmap[opnum];
        dst.op[opnum].bAt20 = src.operators[srcop].avekf_20;
        dst.op[opnum].bAt40 = src.operators[srcop].ksl_l_40;
        dst.op[opnum].bAt60 = src.operators[srcop].atdec_60;
        dst.op[opnum].bAt80 = src.operators[srcop].susrel_80;
        dst.op[opnum].bAtE0 = src.operators[srcop].waveform_E0;
    }
    

    Also, please take an update of wopl_file.h from my editor repo I have been fixed!

  29. Wohlstand reporter

    P.S. When you compiling libADLMIDI to use it in the driver, you may want to disable embedded MIDI sequencer - which is needed to pass MIDI files and play them through adl_play() function. With disabled embedded MIDI sequencer the real-time API will be available only and without having of a dead code in the library.

  30. Log in to comment