VOGONS


First post, by superfury

User metadata
Rank l33t
Rank
l33t

I'm building MPU-401 emulation for my emulator with a MIDI Softsynth using soundfonts for rendering. The rendering almost works, but for some reason when playing a MIDI file, I get the first tone OK, but the other notes get jumbled up and play way too fast. After some fast notes, it starts with some seemingly random instruments, instead of the song being played. When I enable the Windows passthrough, the entire song plays fine at the correct speed and with the correct instruments and notes. Anyone knows what's going wrong?

The frontend is the mpu.c from Dosbox, which passes the commands to midi.c, function MIDI_OUT, one byte at a time.

Anyone knows what's going wrong?

The synth files:

Filename
MIDI Softsynth_20150502_1657.zip
File size
29.17 KiB
Downloads
31 downloads
File comment
My latest MIDI Softsynth emulation.
File license
Fair use/fair dealing exception
Last edited by superfury on 2020-07-31, 21:11. Edited 1 time in total.

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases

Reply 1 of 14, by superfury

User metadata
Rank l33t
Rank
l33t

Here's the updated version of my MIDI softsynth:

Filename
MIDI Softsynth_20150503_1526.zip
File size
9.73 KiB
Downloads
32 downloads
File comment
The updated version of my MIDI softsynth.
File license
Fair use/fair dealing exception

Playback works, but lots of Key ON commands don't get executed for some reason (trying to play http://www.touhoumidi.altervista.org/highly-r … to-prayers.html file Eternal Shrine Maiden (th1_01.mid)).

My log tells me:

0:00:28:12.9.0000: channel 0: Error @position 3 during MID processing! Unexpected EOS? Last command: 96, Current data: 46

It seems to reach the end of the stream (EOS) while reading command parameters (second parameter of a NOTE ON command on channel 6), the first parameter is 0x46 according to the log.

The MID file seems to play, the primary channel (channel 0) seems to go fine (the main piano plays correctly), but the other channels give strange output (missing notes), like it's being hit too fast and out of voices to allocate (which can't be so early afaik)? It's got 64 voices to use, but I don't think it needs that many voices in this song?

Is it me, or is it responding like it's in MONO OMNI mode? I just hear one channel at any time and when one new note starts, the next note starts to play?

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases

Reply 2 of 14, by superfury

User metadata
Rank l33t
Rank
l33t

My latest version:

Filename
mididevice.c
File size
35.1 KiB
Downloads
28 downloads
File comment
Latest MIDI Softsynth version.
File license
Fair use/fair dealing exception

The 'MONO OMNI' mode was because it was reallocating the first voice on every Note On. It never looked past the first voice. This has been fixed now.

For some reason it does play the song, but after about a few seconds it just gets white noise until it terminates (song eventually finishes with an unexpected EOS(End of stream) on channel 0. The stream gets to the end in the middle of a command). Anyone knows what's going wrong here? Is there an error in the soundfont handling? Or is it an error in playing the voices?

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases

Reply 3 of 14, by superfury

User metadata
Rank l33t
Rank
l33t

I've gotten it working atm: the songs are playing fine. But I needed to disable Decay and Release phase of the Volume envelope to do so (they were set to extremely long periods for the instrument: both 15-20 second periods. When this happens, the MIDI voices will fill up because there are constantly lots of channels in use and it can't stop the running channels until all 64 channels are playing, which, when that happens, kind of floods the output (one big single sound because of all the instruments and different notes)).

Is there something that protects against such long envelopes in the Soundfont specs? I don't think any note needs 14.7 second of decay before stopping the note when a Piano is playing (instrument #0)? Also notes within those 14.7 seconds decay won't be freed or reused because they're not released yet according to the ADSR envelope? Or is this an error in my ADSR envelope?

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases

Reply 4 of 14, by superfury

User metadata
Rank l33t
Rank
l33t

I think it's high time I finally fix the Soundfont rendering engine a bit. There still are some notes that sound quite off for some reason?

The basic script is pretty simple:

	//Calculate MIDI difference in notes!
if (lookupSFInstrumentGenGlobal(soundfont, LE16(instrumentptr.genAmount.wAmount), ibag, overridingRootKey, &applyigen))
{
rootMIDITone = LE16(applyigen.genAmount.wAmount); //The MIDI tone to apply is different!
if ((rootMIDITone<0) || (rootMIDITone>127)) //Invalid?
{
rootMIDITone = voice->sample.byOriginalPitch; //Original MIDI tone!
}
}
else
{
rootMIDITone = voice->sample.byOriginalPitch; //Original MIDI tone!
}

rootMIDITone -= /*(sword)note->note*/ 60; //>=positive difference, <=negative difference.
//Ammount of MIDI notes too high is in rootMIDITone.

//Coarse tune...
if (lookupSFInstrumentGenGlobal(soundfont, LE16(instrumentptr.genAmount.wAmount), ibag, coarseTune, &applyigen))
{
cents = LE16(applyigen.genAmount.shAmount); //How many semitones!
cents *= 100; //Apply to the cents: 1 semitone = 100 cents!
}

//Fine tune...
if (lookupSFInstrumentGenGlobal(soundfont, LE16(instrumentptr.genAmount.wAmount), ibag, fineTune, &applyigen))
{
cents += LE16(applyigen.genAmount.shAmount); //Add the ammount of cents!
}

//Scale tuning: how the MIDI number affects semitone (percentage of semitones)
tonecents = 100; //Default: 100 cents(%) scale tuning!
if (lookupSFPresetGenGlobal(soundfont, preset, pbag, scaleTuning, &applygen))
{
tonecents = LE16(applygen.genAmount.shAmount); //Apply semitone factor in percent for each tone!
}
else if (lookupSFInstrumentGenGlobal(soundfont, LE16(instrumentptr.genAmount.wAmount), ibag, scaleTuning, &applyigen))
{
tonecents = LE16(applyigen.genAmount.shAmount); //Apply semitone factor in percent for each tone!
}

tonecents *= -rootMIDITone; //Difference in tones we use is applied to the ammount of cents!

cents += tonecents; //Apply the MIDI tone cents for the MIDI tone!

//Now the cents variable contains the diviation in cents.
voice->initsamplespeedup = cents; //Load the default speedup we need for our tone!

The initsamplespeedup is a speedup/down in cents for the requested MIDI note. And that's determined as found above that by the (overriding) root key, summation of course and fine tuning and scale tuning of the note relative to the root key.
Anyone can see if I messed up anything?
In the example it's locked at note #60(middle C afaik) to compare it to other instruments.
But most instruments end up being a different tone altogether?

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases

Reply 5 of 14, by superfury

User metadata
Rank l33t
Rank
l33t

This is what I have currently:
https://bitbucket.org/superfury/unipcemu/src/ … di/mididevice.c

The attenuation is one thing I can't seem to get right(the initialAttenuation precalcs), as well as the midi key calculation somehow failing with some instruments? Perhaps incorrect amount of cents is calculated somehow? Both are calculated in the middle of mididevice_newvoice. That's on line 751 and onwards for both(first tone precalcs, then attenuation precalcs).

How am I supposed to combine and apply those moderators and initial pitch? Anyone?
Btw, dB2factorf(x,y) performs like the following:

//Basic dB and factor convertions!
#define dB2factorf(dB, fMaxLevelDB) (powf(10, (((dB) - (fMaxLevelDB)) / 20)))
#define factor2dBf(factor, fMaxLevelDB) ((fMaxLevelDB) + (20 * logf(factor)))
#define dB2factor(dB, fMaxLevelDB) (pow(10, (((dB) - (fMaxLevelDB)) / 20)))
#define factor2dB(factor, fMaxLevelDB) ((fMaxLevelDB) + (20 * log(factor)))

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases

Reply 6 of 14, by cyclone3d

User metadata
Rank l33t
Rank
l33t

When messing with stuff math-wise when programming, and something isn't working properly I will run through the equations on paper to verify I am doing stuff right.

I will also put debug messages all over the place to check that everything is working properly.

I once had a C program that would never return the correct results because for some reason a couple of the variables I was using would never get the correct values assigned to them even though debug code showed that the calculations were correct.

As a last ditch effort I decided to try renaming those variables. Low and behold the variables started getting the correct values assigned. I had apparently miraculously accidentally used the names of a couple of the internal compiler variables which was causing the problem. This was with Visual Studio 2003 if I remember the version correctly.

Yamaha YMF modified setupds and drivers
Yamaha XG resource repository - updated November 27, 2018
Yamaha YMF7x4 Guide
AW744L II - YMF744 - AOpen Cobra Sound Card - Install SB-Link Header
Epstein didn't kill himself

Reply 7 of 14, by superfury

User metadata
Rank l33t
Rank
l33t

Well, I already know of Visual Studio still having some problems with variables and duplicate names. For example, I have various variables that the debugger can't show, unless i switch to the context of the parent function or the owning thread that's handling that data. Although this leads to strange issues(like ATA[0] somehow showing the contents of some weird unrelated other hardware's public global variable instead.
Not to speak of being unable to view in the debuger's watch window anything that's outside of the current context (even if they're global variables, which once again requires making a switch to the correct (main) thread and walking up the subroutines to the main execution loop of the emulator in order to view what the status of various hardware components in the emulator actually is for example.

So far managed to find that the modulators actually have defaults assigned to them if they don't exist. That explains that "Amount" field that's explained with the modulator's descriptions. The value mentioned with that field is actually the default value, unless it's overridden.

What I'm still wondering about is how those attenuation modulators are actually combined with each other to create one big attenuation value to use for all samples rendered.
I'm currently getting them from the instrument level first, applying the presets to the instrument level by normalizing them and multiplying them with the instrument ones. If no instrument modulator exists, the preset will override the default value instead. In the remaining case(neither exists) it will use the default value instead.
Then, once it has the calculated value(only when instrument+preset combined) or either one or default value, it will use the mentioned dB2factorf macro to convert the 960dB range input(which is clipped beforehand) and apply it to the current attenuation from the previous steps.

OPTINLINE float MIDIvolume(float value, float maxvalue, float maxdB)
{
return (float)dB2factor((((maxvalue - (DOUBLE)value) * (maxdB / (maxvalue/10.0f))) / 10.0), maxdB); //Generate default attenuation!
}

It just multiplies the previous attenuation(the first being the result of the initialAttenuation generator) with the result of MIDIvolume to obtain the combined volume settings of all the previously handled attenuations.
It does this for Note on Velocity (0x0502), CC7(0x0582) and finally CC11(0x058B). It then performs one final check to make sure the attenuarion is properly capped between 0 and 1 before saving it and letting the renderer use it to render.

I also just implemented said defaults to the code (instead of defaulting them at 0).

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases

Reply 8 of 14, by superfury

User metadata
Rank l33t
Rank
l33t

OK. Having implemented them now with their 960dB defaults, all those settings are using 960dB attenuation when the value 0 is supplied.

	attenuation *= MIDIvolume(tempattenuation, 960.0, 96.0); //Range is 960cB, so convert and apply(add to the initial attenuation generator) using 96dB attenuation range!

For the Windows soundfont I'm testing with, I see the initial attenuation being 202cB, then velocity 100 with 960dB default attenuation resulting in 204cB attenuation, then volume 127 with 960dB resulting in a 0dB attenuation, thus not attenuating and (so far only 2% of the signal is left due to the attenuation) finally CC11 being 0 resulting in a 960dB attenuation, causing it to drop to 3.02e-07, so a very very small volume, which causes the volume to plummet into nothingness on the 16-bit samples.

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases

Reply 9 of 14, by superfury

User metadata
Rank l33t
Rank
l33t

Managed to get the pitch calculations a bit better, with improved preset support(additive instead of multiplying in some cases).
Many of the pitch calculations now deal in cents only during precalcs, to be converted to a linear speedup factor when reading the sample itself.
Also some chorus/reverb fixed have been made.

Certain instruments like harps etc. still have one weird property: they seem to decay or release very slowly, causing them to be sounding for way longer than they're propably intended, filling the entire output with their noise when receiving note on in succession to each other?

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases

Reply 10 of 14, by superfury

User metadata
Rank l33t
Rank
l33t

Anyone knows how the modulators combine with each other and the initialAttenuation generator? Currently it takes:
- Takes the instrument level directly. The preset level is divided by 960.0(or 1440.0 for the initialAttenuation generator) and multiplied with this if it's present too.
- Otherwise(no instrument level), take the preset level directly.
Said output is multiplied with the unipolar negative source for note on velocity, CC7 and CC11 and added to the attenuation variable(using simple addition).

Said resulting attenuation variable is converted to a factor to apply to the samples using the following function call:

	attenuation = MIDIattenuate(attenuation,9600.0f, 1.0f); //96dB range volume using a 1440dB attenuation!

MIDIattenuate is the following function:

//MIDIvolume: converts a value of the range of maxvalue to a linear volume factor using maxdB dB.
float MIDIattenuate(float value, float maxvalue, float scale)
{
return (float)powf(10,(0.0f-((((value/maxvalue)*scale))*96.0f)/20.0f)); //Generate default attenuation!
}

That's applying the conversion from dB attenuation on maxvalue being full attenuation, scale modifying the result. The basic conversion from dB of attenuation to factor(0.0-1.0) should be 10 to the power of ((-x)/20)? Where x is from 0 to 96dB of attenuation?

Or are each of said steps supposed to use MIDIattenuate to multiply the resulting factor(starting at 1.0)? Now it's getting attenuation of (1440+960+960+960)dB with all sources being max attenuation? Is that correct? Or should it be 1440dB*960dB*960dB*960dB? Shouldn't CC7 and CC11 be combined into 1?

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases

Reply 11 of 14, by superfury

User metadata
Rank l33t
Rank
l33t

Just tried implementing it on a 960dB scale for each setting but the initial attenuation setting. All are calculated by the formula (10^(0-dB)), where dB is from 0 to 96, depending on the inversed inputs calculated from the settings(inverted ones, so 0=127, 127=0 and everything in between is in between inverted) that are normalized, then multiplied by the modulator attenuation to get a value from 0-960cB attenuation, finally multiplying the output sample factor(0-1 range) with the cB of attenuation converted to a voltage factor using said formula.

Every of those voltage factors is multiplied by the other modulators(so initial attenuation times CC7 times expression times velocity).

Now I get very soft volumes?

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases

Reply 12 of 14, by superfury

User metadata
Rank l33t
Rank
l33t

The volume now seems correct, but it's very silent(for most instruments)?

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases

Reply 13 of 14, by superfury

User metadata
Rank l33t
Rank
l33t

I've just changed the attenuation to be additive (each limited to 1440cB for the generator and 960cB for the modulators). I then changed the conversion from a 960cB or 1440cB range of attenuatioh to 1440cB of attenuation range.
It now seems to sound at a proper volume?

Although the frequency still seems incorrect, depending on the MIDI note number?

	//Now, calculate the speedup according to the note applied!

//Calculate MIDI difference in notes!
if (lookupSFInstrumentGenGlobal(soundfont, LE16(instrumentptr.genAmount.wAmount), ibag, overridingRootKey, &applyigen))
{
rootMIDITone = LE16(applyigen.genAmount.wAmount); //The MIDI tone to apply is different!
if ((rootMIDITone<0) || (rootMIDITone>127)) //Invalid?
{
rootMIDITone = voice->sample.byOriginalPitch; //Original MIDI tone!
}
}
else
{
rootMIDITone = voice->sample.byOriginalPitch; //Original MIDI tone!
}

rootMIDITone = (((sword)note->note)-rootMIDITone); //>positive difference, <negative difference.
//Ammount of MIDI notes too high is in rootMIDITone.

cents = 0; //Default: none!
cents += voice->sample.chPitchCorrection; //Apply pitch correction for the used sample!

//Coarse tune...
if (lookupSFInstrumentGenGlobal(soundfont, LE16(instrumentptr.genAmount.wAmount), ibag, coarseTune, &applyigen))
{
cents = LE16(applyigen.genAmount.shAmount)*100; //How many semitones! Apply to the cents: 1 semitone = 100 cents!
if (lookupSFPresetGenGlobal(soundfont, preset, pbag, coarseTune, &applygen))
{
cents += LE16(applygen.genAmount.shAmount) * 100; //How many semitones! Apply to the cents: 1 semitone = 100 cents!
}
}
else if (lookupSFPresetGenGlobal(soundfont, preset, pbag, coarseTune, &applygen))
{
cents = LE16(applygen.genAmount.shAmount)*100; //How many semitones! Apply to the cents: 1 semitone = 100 cents!
}

//Fine tune...
if (lookupSFInstrumentGenGlobal(soundfont, LE16(instrumentptr.genAmount.wAmount), ibag, fineTune, &applyigen))
{
cents += LE16(applyigen.genAmount.shAmount); //Add the ammount of cents!
if (lookupSFPresetGenGlobal(soundfont, preset, pbag, fineTune, &applygen))
{
cents += LE16(applygen.genAmount.shAmount); //Add the ammount of cents!
}
}
else if (lookupSFPresetGenGlobal(soundfont, preset, pbag, fineTune, &applygen))
{
cents += LE16(applygen.genAmount.shAmount); //Add the ammount of cents!
}

//Scale tuning: how the MIDI number affects semitone (percentage of semitones)
tonecents = 100; //Default: 100 cents(%) scale tuning!
if (lookupSFInstrumentGenGlobal(soundfont, LE16(instrumentptr.genAmount.wAmount), ibag, scaleTuning, &applyigen))
{
tonecents = LE16(applyigen.genAmount.shAmount); //Apply semitone factor in percent for each tone!
if (lookupSFPresetGenGlobal(soundfont, preset, pbag, scaleTuning, &applygen))
{
tonecents += LE16(applygen.genAmount.shAmount); //Apply semitone factor in percent for each tone!
}
}
Show last 12 lines
	else if (lookupSFPresetGenGlobal(soundfont, preset, pbag, scaleTuning, &applygen))
{
tonecents = LE16(applygen.genAmount.shAmount); //Apply semitone factor in percent for each tone!
}

tonecents *= rootMIDITone; //Difference in tones we use is applied to the ammount of cents!

cents += tonecents; //Apply the MIDI tone cents for the MIDI tone!

//Now the cents variable contains the diviation in cents.
voice->initsamplespeedup = cents; //Load the default speedup we need for our tone!

Can you see what's going wrong?

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases

Reply 14 of 14, by superfury

User metadata
Rank l33t
Rank
l33t

Just improved it a bit. I had to cap attenuation at 1440cB (not including the volume envelope) and convert the volume envelope to 1000cB range and invert it(to provide attenuation instead of amplification).

	if (attenuation > 1440.0f) attenuation = 1440.0f; //Limit!
if (attenuation < 0.0f) attenuation = 0.0f; //Limit!

#ifdef IS_LONGDOUBLE
voice->initialAttenuation = attenuation; //We're converted to a rate of 960 cb!
#else
voice->initialAttenuation = attenuation; //We're converted to a rate of 960 cb!
#endif

Then I adjusted the midiAttenuate function to work with the new scale(instead of the old 1440cB scale):

OPTINLINE float MIDIattenuate(float value)
{
value *= (1440.0f / (1440.0f + 1000.0f)); //Reduce to be within range!
if (value > 1440.0f) value = 1440.0f; //Limit to max!
if (value < 0.0f) value = 0.0f; //Limit to min!
return (float)powf(10.0f, value / -200.0f); //Generate default attenuation!
}

It seems about right now, but some test MIDI files I'm using suddenly start playing staccato instead of normal notes? Perhaps some unknown issue with the volume envelope(it's actually linear ramping from 0 to 1000(attack), 1000(hold), 1000-sustain for sustain and back to 0 for release).
Perhaps there's an issue with the sustain/release algorithm? Some songs seem fine, but others mess up big time(in the timing and volume envelope department). And some instruments mess up pitch as well(like it's playing the wrong octave, being changed to an extremely high octave instead)?

Btw, I'm testing using the plain AWEROMGM soundfont.

UniPCemu Git repository
UniPCemu for Android, Windows and PSP on itch.io
Older UniPCemu PC/Android/PSP releases