Wohlstand / libADLMIDI

A Software MIDI Synthesizer library with OPL3 (YMF262) emulator
GNU Lesser General Public License v3.0
176 stars 17 forks source link

Add the way to change single instrument of currently-loaded bank on the fly. #59

Closed Wohlstand closed 6 years ago

Wohlstand commented 6 years ago

This will allow to use libADLMIDI directly how for OPL3 Bank Editor's backend to change instruments on the fly to test them, or give the ability to use libADLMIDI as VTS-plugin for the musical software.

jpcima commented 6 years ago

Subscribing myself to thread. Yes the instrument structure should be exposed. If it's the wopl directly, even better. I'll use wopl as the internal format to save and restore plugin states.

jpcima commented 6 years ago

To add to this, you surely don't need to always copy the bank to dynamic buffer. The bank pointer count point to either the embedded or dynamic set. And allocate the dynamic array only as needed. It can be copy-on-write.

and the ID of the channel.

Why does the channel relate to instrument definitions? is it not the library which does the channel mappings internally according to program change events?

Wohlstand commented 6 years ago

and the ID of the channel.

Oops, it's totally nosense... I fixed it with type of the instrument (melodic or percussion).

jpcima commented 6 years ago

I started the foundation for a music plugin. Development will be here.

jpcima commented 6 years ago

After hard-rt is finished which is very soon, this bug will be next target. :dart: :gun: You have a status update on this one? no matter if there isn't, I just don't want to get in your feet if I'm starting something.

EDIT: by the way I think you mean for the title to say "on the fly" not "on a fly"

jpcima commented 6 years ago

It was nice of you to edit the title, but I think you forgot to answer my question. :smile: I was asking if you have made some progress on this issue already.

Wohlstand commented 6 years ago

Not yet, as at first I need to rework the working instruments storage like I made on libOPNMIDI. The goal is make the instruments cache be completely write-able and avoid direct access into embedded instruments database. Anyway, before to start this work, I awaiting for completeness of hard-rt as my future works may cause a hard conflict with this. However, that not a big work as I only need to backport stuff from OPNMIDI and organize the filling of the bank cache with instruments data from a database when using embedded banks. Also, this will give some speed-up in case that no more conditions in the instrument getters. So, just look the plan of check-boxes in a main post :fox_face: :wink:

jpcima commented 6 years ago

I awaiting for completeness of hard-rt

Hard-rt is waiting for you! But now this reminds me, I will reorganize it into a better commit. The split commits were just better for the review while there were pending bugs.

I had some thoughts in my mind about extended bank formats like GS. There is a max bank count of 256^3 because of (program-number LSB MSB). Can't there be a perfect hashing which maps large program numbers, into a smaller preallocated storage space?

Wohlstand commented 6 years ago

Hard-rt is waiting for you! But now this reminds me, I will reorganize it into a better commit. The split commits were just better for the review while there were pending bugs.

Yeah, as I must to review it and merge that if everything is fine :smile:

I had some thoughts in my mind about extended bank formats like GS. There is a max bank count of 256^3 because of (program-number LSB MSB). Can't there be a perfect hashing which maps large program numbers, into a smaller preallocated storage space?

I think, you even can have the 65536 array of size_t-s (which will eat 524288 bytes on 64-bit and 262144 bytes on 32-bit platforms) which are all will be set into '0' or the offset inside of the instruments cache. That will work fast as no effort, but, a bigger memory usage which will eat extra half of mega-byte. The array does mapping of 127-counted banks, not of an instruments.

EDIT: Oh, I forgot about melodic/percussion, so, the full range of the map will 131072 entries (two maps: one for melodic and second for percussions, but, percussions also having offset inside of instruments cache).

jpcima commented 6 years ago

The maximum count (256^3) is not 64k but 16M. Multiply by the size of int (4) and you have 64 MB. It's this unless you will only use the bank LSB and ignore MSB.

But don't XG and GS both use MSB? I'm unsure I'm not very familiar with these standards. I have found a list here. http://eric.hurtebis.chez-alice.fr/patches/inst.htm

EDIT my bad it's actually (128^3) with totals to a 8MB table

Wohlstand commented 6 years ago

Anyway, why that map is bad? it's read-only and it is writing once on a bank load or switch, BUT, it may be changed if you will add an instrument into the not-listed bank (the bank number which wasn't actually presented in selected file or bank, which will cause a reallocation of instruments store and the map too).

jpcima commented 6 years ago

Anyway, why that map is bad?

I don't know what idea you had in your mind to handle this. I just throw the idea out there.

The instruments can be pre-indexed by their GM number, and then found by a linear scan in a linked list. You reserve the linked list memory in advance. It remains a fully dynamic structure. Linear scan will have O(n) but little elements to go through in practice.

Wohlstand commented 6 years ago

But don't XG and GS both use MSB? I'm unsure I'm not very familiar with these standards. I have found a list here. http://eric.hurtebis.chez-alice.fr/patches/inst.htm

I had to unite bank number into same number because of Sonar and some other MIDI editors where bank number is represented as single signed 16-bit integer value. Anyway, as I will provide SysEx support, I'll switch the bank formula in dependence of XG or GS reset request, but as default behavior I'll keep 16-bit unsigned key.

Wohlstand commented 6 years ago

I don't know what idea you had in your mind to handle this. I just throw the idea out there.

That map is touched when bank ID is non-zero. I made it to save the memory as huge map will be not good for devices or platforms where memory usage is critical.

Wohlstand commented 6 years ago

The instruments can be pre-indexed by their GM number, and then found by a linear scan in a linked list. You reserve the linked list memory in advance. It remains a fully dynamic structure. Linear scan will have O(n) but little elements to go through in practice.

Then, that right, however, let's benchmark this, I just interested what is faster even...

jpcima commented 6 years ago

That map is touched when bank ID is non-zero. I made it to save the memory as huge map will be not good for devices or platforms where memory usage is critical.

I see. But for me, this stuff links to realtime consideration, since I would appreciate if the audio processor would be able to switch bank and edit the programs from RT.

Wohlstand commented 6 years ago

About of bank switching and instruments updates: usually, if you will override existing instruments or you will append instruments into banks are referred into the map, nothing will be re-allocated. However, when you will add instrument into non-existing bank (for example, loaded bank has 0, 3 and 5 banks. Then you added an instrument into bank 4. That bank wasn't existed, then, this request will cause a reallocation of the banks store). Full reload of the bank (opening another bank file) will possibly cause re-allocation when count of banks is different. Usually that is. However, when you switching different GM-only banks, then no any reallocations needed.

Wohlstand commented 6 years ago

about of appending: banks are always allocating multiple-127 size, and empty instruments are always marked by a special flag. Appending means replacing of a blank instrument entry. So, not a physical appending into tail of the array :wink:

jpcima commented 6 years ago

OK and what about having a preallocated pool of instrument structures, and ADLMIDI would take from there instead of allocating a memory block? Couldn't I just ask ADLMIDI API: just reserve me a pool of 1k instruments, or whatever will be enough for my need.

Wohlstand commented 6 years ago

I think, that is possible, however, as I told, the pool must have size multiple 127, so, the reserve request would use banks than individual instruments. The legacy AdLib banks are allowed to store instruments of any count even there are non-multiple 127.

Wohlstand commented 6 years ago

Or, instruments count value must be aligned to nearest ceiling that multiple 127.

jpcima commented 6 years ago

Ok, I understand this! Anyway you have the picture of what my need is. You do as you want, you're the captain.

Wohlstand commented 6 years ago

So, the goal is to completely avoid any memory allocations from RT and avoid overuse of the memory, especially on low-end devices. So, for purpose of RT, the banks allocation can be pre-initialized to the big range when for MIDI file playing purposes, set it to range of loaded bank. What do you think? Or let's some minimal range will be pre-allocated on library initialization and then expand it when needed or by the request.

jpcima commented 6 years ago

So, for purpose of RT, the banks allocation can be pre-initialized to the big range

That's it, depending on your definition of what is "big range". I would just reserve a decent maximum number of instrument banks, for example 128, not the complete space of the 16384 of them. It would be absurdly wasteful.

jpcima commented 6 years ago

I'll give the ability to pre-allocate N banks as we already discussed.

Great. :+1:

jpcima commented 6 years ago

I want to ask you this: when I will dynamically change instrument parameters, I don't know what to put in place of the delay measurements (on/off) which are normally created by the bank editor. What is the purpose of these measurements? can't they be deduced from the ADSR values? or just estimated?

Wohlstand commented 6 years ago

Those measurements are milliseconds are meant how long note will sound while it is on (for example, guitar, piano, dulcimer, music box, etc.) and how long it will sound while off (releasing sounding). Those values are used in chip channels management to detect free channels more accurately. For example, music box sounds shorter, and when key is still pressed, channel can be released because of timeout. Therefore every running note has "age".

Wohlstand commented 6 years ago

I think, you will need to use this https://github.com/Wohlstand/OPL3BankEditor/blob/master/src/opl/measurer.cpp to calculate those values from current instrument. If those values are will not be right, some notes (synth strings as example) may be killed unexpectedly because of too short timeout, or short music box notes may cause arpeggio be on because channels are overused because there are has too long time out.

Wohlstand commented 6 years ago

Suggested to use fastest emulator to calculate this. Anyway, for testing purposes while editing those values are may be set to 1000 and 200. For using instrument in music processing you must calculate those values to have channel manager work correctly.

jpcima commented 6 years ago

I think, you will need to use this

I have seen this, but this needs some additional complexity and computation. I'm looking for simpler if I can. Or a basic approximation.

I miss something to understand why this is necessary. Please help me to understand why this line of thinking is wrong: In OPL instruments you have this set of parameters.

60-75Attack RateDecay Rate
80-95Sustain LevelRelease Rate

These rates really represent time values, except inverted for convenient storage in integer. Why wouldn't (1/ReleaseRate) provide the same information as the key off duration?

Wohlstand commented 6 years ago

I also thinking about of implementing something to simplify this instead of running the emulator to capture physical output. This may increase full re-generation GenAdldata a lot as with Nuked OPL3 it's a hellish-slow tortoise I had to speed-up with multi-threading and caching of already-measured values.

jpcima commented 6 years ago

I don't understand this.. I understand it's also a thing of the original ADLMIDI. I guess I'll dig into this and make a comparison between the measure and envelope parameters. Why there should be a difference; this is a mystery to me.

Wohlstand commented 6 years ago

Look: it's an alternative way to manage chip channels. Each note has time out of sounding. When sounding time is big, means, the channel used by this note is "bad" choice for a next note. When timeout is small or note is not sustaining, but releasing, means, channel is "better" choice. Means, next note can use channels where is smallest releasing timeout, same instrument, or channel is unused by anyone. Therefore the "goodness calculation" function is exists which checks timeouts and other factors and gives points to he channel. After calculation of "goodness", checking for a biggest value which tells which channel will be used by the given note. Timeouts are changing goodness/badness level and providing a chance level the channel will be used by another note or will be kept busy.

Wohlstand commented 6 years ago

And yeah, it was made by Joel originally, and this looks good as old method of channels priority sucks by lot of reasons.

jpcima commented 6 years ago

Look: it's an alternative way to manage chip channels

I understand this, I have never questioned the problematic of channel management. I question, however, the way how the delay computation is made, because it could be so simple, without having to run the emulator. (unless I miss a very important detail.)

I have just written a small program to be able to output the envelope which matches some instrument parameters. I would use these values to predict how much the playback of notes would last. (I observed it's also dependent on the note frequency, and ksl/ksr)

eg

Wohlstand commented 6 years ago

I even found the repository where is described OPL3's math which would help us. Usually, the calculation is needed for middle note, as "KSL" factor makes difference in dependence on a note height. There is an attack level which is 15, means instant, and 0 - entire sound is silent.

Also, the measurer doesn't counts absolute values: where is releasing, it takes half or shorter delay as too long release delay may cause a crap which I recently had on OPNMIDI which I had to resolve by increasing of shorting coefficient while calculating releasing delay.

Wohlstand commented 6 years ago

About of OPL3 Math: https://github.com/gtaylormb/opl3_fpga/tree/master/docs/opl3math

Wohlstand commented 6 years ago

And yeah, you even can embed a unit-test into ADL-GenData tool to compare old algorithm with new and values are must be similar. Also, because of emulator-based computing, it makes hell on OPL3 Bank Editor too which does saving of those values into WOPL and FBOP3 formats.

jpcima commented 6 years ago

Very cool! Thanks for the document. :+1: I had implemented from Nuked OPL ROM tables and I already started to reverse some of the things. You can open me an issue to start work on this. I'm stuck anyway while the bank work is not done.

jpcima commented 6 years ago

You know, from what I collect of this information... It's very possible to start with a very brute force method. We can save the possible combinations in a large table. It's in the order of a megabyte.

Wohlstand commented 6 years ago

Anyway, it's the side of ADL GenData and OPL3 Bank Editor and their sizes is not critical as libADLMIDI / libOPNMIDI theme selves, but source code of them yeah, would growl, then yeah...

jpcima commented 6 years ago

It's a potential step 1, there's a lot of ways to think about how to reduce this. Approximations and interpolations and whatever there also is. I overlooked the doc quickly, and there does not seem to be a straight formula. Let's try by practical experiment and see.

loki666 commented 6 years ago

so I've successfully implemented libADLMIDI in my OPL3 MIDI Synthesizer. works great, need to add options to select the emulator and the 4op channels the only thing missing now for a release is a way to patch instruments as my Synth has en simple Instrument editor.

Wohlstand commented 6 years ago

Also, about of four-op, I have made the "-1" value which also means "auto" which tells libADLMIDI calculate necessary count of four-op instruments in dependence of existing 4-op instruments in the bank.

jpcima commented 6 years ago

I created this tool from Nuked 1.8, to measure the duration of envelopes from just parameters and note. https://github.com/jpcima/opl3-time-measurement It's to research more into relations between duration and rate values.

The bank editor's measurement mentions "quarter amplitude time" but compares threshold against 0.2 not 0.25 (-14 vs -12dB). Is it a mistake or an intended thing?

Wohlstand commented 6 years ago

The bank editor's measurement mentions "quarter amplitude time" but compares threshold against 0.2 not 0.25 (-14 vs -12dB). Is it a mistake or an intended thing?

As each different instrument has a different volumes level, even total levels are same, but different wave shapes, may give a different amplitude. It's approximation made by Joel to avoid super-long or super-short releasing delay time.

Wohlstand commented 6 years ago

I created this tool from Nuked 1.8, to measure the duration of envelopes from just parameters and note. https://github.com/jpcima/opl3-time-measurement It's to research more into relations between duration and rate values.

Looks nice, I hope this will save lots of time to re-generate values for all instruments, and even allow to do that on slow computers (You know my Win9x-edition of bank editor I have intended for usage of real chip directly, and usually sound cards are shipped with OPL3 chip are ISA in most. However I heard about SB16-PCI, but I never had it, but I would like to have it especially for REAL OPL3 chip).

jpcima commented 6 years ago

different wave shapes, may give a different amplitude

Yes, this is the point I was going to get to next.

my Win9x-edition of bank editor I have intended for usage of real chip directly I heard about SB16-PCI, but I never had it, but I would like to have it especially for REAL OPL3 chip

I've already successfully programmed the x86 i/o ports of the opl3 on Linux, but it needs root rights. you've looked for old ebay laptops, especially toshiba? Tecra 8000 has the opl3-sax onboard.

jpcima commented 6 years ago

Here is a simple idea to test for an approximation of the time delays. 1) compute RMS level of the 8 basic waveforms at all output levels (864 values) 2) out of the 6 possible FM algorithms, output is a sum of either N=1, 2, or 3 carriers. compute total RMS of summed carrier outputs: √(Σ i:1→N* (WaveRMS(Wave(i), OutputLevel(i))²)) 3) compute the inverse envelope function to get the time value

According to C.Roads-J.Chowning, modulation index does not wildly affect overall amplitude of the modulated signal; so it may be possible to neglect the amplitude effect of the modulator altogether.

Addendum: I put the numbers into matlab and I've found attack(t)=1-exp(-2.5*2^(ar-1)*t) to be an almost perfect fit which lands precisely on integer values. It should give a basis to compare the "key on" results.

jpcima commented 6 years ago

For the dynamic loading of instruments, I think I am going to start working on something myself, because I want to make progress on the VST.

So here I throw some ideas and basic roadmap to accomplish this.

  1. ADLMIDI must expose an instrument structure. This can be the structure from WOPL, but I propose to version it, so the instrument can get its futures updates freely without breaking any compatibility.

  2. It must provide an access to the OPL banks, read and write, and also search/list/add/remove. The bank will be identified by LSB+MSB+melodic/percussive.

The bank storage area will be a std::vector. This storage can be reserved to some desired capacity. The add method will have a flag which specifies if the storage is allowed to extend. If it's a call from hard-RT, storage must not be allowed to extend.

The search operation must not take O(N). An auxiliary data structure must arrange for fast lookups, still preserving the conditions of hard-RT, but not with extravagant consumption of memory.

  1. Unit testing must be added to check all this stuff works like it should.

Good?