VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

I'm trying to implement MIDI reverb and chorus effects into UniPCemu's MPU(Using the SoundFont 2.04 format). For some reason, when enabling the effect(by removing/commenting out the break statement in the loop calling MIDIDEVICE_getsample), it sounds very weird when the effects are used.

https://bitbucket.org/superfury/unipcemu/src/ … ice.c?at=master

Btw, I've noticed that it uses one low pass filter state for all four reverb&chorus streams(4 reverb times 4 chorus is 16 streams in total). This might add some noise to the sound, but it shouldn't have such a strange fm-modulation-like effect?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 1 of 6, by superfury

User metadata
Rank l33t++
Rank
l33t++

I think I've managed to fix the chorus/reverb effects(might be very heavy on the host CPU though, leading to slow processing and audio dropouts due to that):

https://bitbucket.org/superfury/unipcemu/src/ … ice.c?at=master

Is the way it's handled correct? Anyone has a simple chorus and/or reverb effect test file to throw against it?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 2 of 6, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've improved the effects a bit, while also adding a buffer (actually some kind of timeline log) containing the different reverb/chorus's pitch bended state of the main channel at the different points in time(with ~0.06 seconds of reverb leading at an extra buffer of 20MB in total memory required for all 24 voices together to be logged(around 700KB for each voice), with channels having a quality of maximum 50kHz samplerate).

https://bitbucket.org/superfury/unipcemu/src/ … ice.c?at=master

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 3 of 6, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've managed to optimize it somewhat, but it's still reasonably slow, even on a 4.0GHz CPU. Anyone knows any way to speed it up further?

https://bitbucket.org/superfury/unipcemu/src/ … ice.c?at=master

The main loop is at rows 332(applyMIDIlowpassfilter) and 254("modulationratio = MIDIDEVICE_chorussinf(chorusreverbreversesamplerate,chorus,0); //Pitch bend default!"), according to the profiler. They take 10% and 14-15% of CPU time, respectively. Anyone knows if there's a way to speed that up further? It's already using lookup tables as much as I could think of (MIDIDEVICE_chorussinf function), precalculated in MIDIDEVICE_generateSinusTable(). The low pass filter is mainly heavy due to it's calculations:

OPTINLINE static float calcSoundLowpassFilter(float cutoff_freq, float samplerate, float currentsample, float previousresult)
{
INLINEREGISTER float dt = (1.0f / samplerate); //DT is used multiple times, calculate once!
return previousresult + ((dt / ((1.0f / (cutoff_freq * (2.0f * (float)PI))) + dt))*(currentsample - previousresult));
}

Is there a way to optimize this for speed?

Also, it still seems to have little problems with applying volume using a factor? Some stuff sounds too loud and other instruments sound too soft? It mainly runs using the dB2factor and factor2dB for it's volume calculations:
https://bitbucket.org/superfury/unipcemu/src/ … und.h?at=master

Anyone can see if those calculations are correct?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 4 of 6, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've just modified the cents conversion to apply it's conversion in one go:
https://bitbucket.org/superfury/unipcemu/src/ … ice.c?at=master

But for some reason it results in sound not being played (some notes disappear)? (Compared to commit: https://bitbucket.org/superfury/unipcemu/src/ … ice.c?at=master).

Anyone can see what's going wrong?

Diff: https://bitbucket.org/superfury/unipcemu/diff … 63b7f&at=master

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 5 of 6, by superfury

User metadata
Rank l33t++
Rank
l33t++

My latest source code, with various bugfixes (singed vs unsigned problems mainly):
https://bitbucket.org/superfury/unipcemu/src/ … ice.c?at=master

For some reason some of the channels gets (+)inf inside it's saved modulation information(voice->modulationratiosamples[chorus] entries 0-1 and at some point 0-4)? Anyone can explain why this is happening?

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io

Reply 6 of 6, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've improved and optimized the effects somewhat:
https://bitbucket.org/superfury/unipcemu/src/ … ice.c?at=master
https://bitbucket.org/superfury/unipcemu/src/ … ice.h?at=master

Although it's running faster now, I notice that songs using reverb pull the CPU emulation down from 100% (at 4.77MHz accuracy) to 80-90% with reverberation used only (playing Jazz Jackrabbit MIDI tracks using the MIDI player). Playing songs that use a combination of reverb and chorus even pull it down much further: down to up to 18% speed(huge Soundfont rendering latency). Anyone can see why this is so heavy? As usual the profiler tells me that the heavy lines are mostly the MIDIDEVICE_chorusinf macro and low pass filter being applied(lines 245&240(8.1% at 245, with 1.9% being at line 240) and line 318 being 2.6%). Is there any way to speed this up further?

The current rendering algorithm (for every of the 24 MIDI channels):

//How many steps to keep!
#define SINUSTABLE_PERCISION 3600
#define SINUSTABLE_PERCISION_FLT 3600.0f
#define SINUSTABLE_PERCISION_REVERSE (1.0f/SINUSTABLE_PERCISION_FLT)

float sinustable[SINUSTABLE_PERCISION*2]; //10x percision steps of sinus!
sword chorussinustable[SINUSTABLE_PERCISION*2][4][2]; //10x percision steps of sinus! With 1.0 added always!
float sinustable_percision_reverse = 0.5f; //Reverse lookup!

void MIDIDEVICE_generateSinusTable()
{
word x;
byte choruschannel;
for (x=0;x<NUMITEMS(sinustable);++x)
{
sinustable[x] = sinf((float)((x/SINUSTABLE_PERCISION_FLT))*360.0f); //Generate sinus lookup table!
sinustable[x+SINUSTABLE_PERCISION] = sinf((float)((x/SINUSTABLE_PERCISION_FLT))*360.0f); //Generate sinus lookup table!
for (choruschannel=0;choruschannel<4;++choruschannel) //All channels!
{
chorussinustable[x][choruschannel][0] = (sword)((sinf((float)((x/SINUSTABLE_PERCISION_FLT))*360.0f)+1.0f)*choruscents[choruschannel]); //Generate sinus lookup table, negative!
chorussinustable[x][choruschannel][1] = (sword)(chorussinustable[x][choruschannel][0]+1200.0f); //Generate sinus lookup table, with cents base added, negative!
chorussinustable[x+SINUSTABLE_PERCISION][choruschannel][0] = chorussinustable[x][choruschannel][0]; //Generate sinus lookup table, positive!
chorussinustable[x+SINUSTABLE_PERCISION][choruschannel][1] = chorussinustable[x][choruschannel][1]; //Generate sinus lookup table, with cents base added, positive!
}
}
sinustable_percision_reverse = SINUSTABLE_PERCISION_REVERSE; //Our percise value, reverse lookup!
}

//Absolute to get the amount of degrees, converted to a -0.0 to 2.0 scale!
#define MIDIDEVICE_sinf(value) sinustable[(uint_32)(((fmodf(value,360.0f)*sinustable_percision_reverse)+1.0f)*SINUSTABLE_PERCISION_FLT)];

//Absolute to get the amount of degrees, converted to a -1.0 to 1.0 scale!
#define MIDIDEVICE_chorussinf(value, choruschannel, add1200centsbase) chorussinustable[(uint_32)(((fmodf(value,360.0f)*sinustable_percision_reverse)+1.0f)*SINUSTABLE_PERCISION_FLT)][choruschannel][add1200centsbase]

OPTINLINE static void MIDIDEVICE_getsample(int_32 *leftsample, int_32 *rightsample, int_64 play_counter, uint_32 totaldelay, float samplerate, sword samplespeedup, MIDIDEVICE_VOICE *voice, float Volume, float Modulation, byte chorus, byte reverb, float chorusreverbvol, float chorusreverbreversesamplerate, byte filterindex) //Get a sample from an MIDI note!
{
//Our current rendering routine:
INLINEREGISTER uint_32 temp;
INLINEREGISTER int_64 samplepos;
float lchannel, rchannel; //Both channels to use!
byte loopflags; //Flags used during looping!
static sword readsample = 0; //The sample retrieved!
sword modulationratiocents;
word speedupbuffer;

if (chorus==0) //Main channel? Log the current sample speedup!
{
writefifobuffer16(voice->effect_backtrace_samplespeedup,signed2unsigned16(samplespeedup)); //Log a history of this!
}

if (play_counter>=0) //Are we a running channel?
{
if (readfifobuffer16_backtrace(voice->effect_backtrace_samplespeedup,&speedupbuffer,(uint_32)totaldelay,(filterindex==(CHORUSREVERBSIZE-1))) && chorus) //Try to read from history! Only apply the value when not the originating channel!
{
samplespeedup = unsigned2signed16(speedupbuffer); //Apply the sample speedup from that point in time! Not for the originating channel!
}
}

samplepos = play_counter; //Load the current play counter!
if (voice->modenv_pitchfactor && (chorus==0)) //Gotten a modulation envelope to process to the pitch?
Show last 252 lines
	{
modulationratiocents = voice->modenv_pitchfactor; //Apply the pitch bend to the sample to retrieve!
}
else if (voice->modenv_pitchfactor && chorus) //Both?
{
modulationratiocents = MIDIDEVICE_chorussinf(chorusreverbreversesamplerate,chorus,0); //Pitch bend default!
modulationratiocents += voice->modenv_pitchfactor; //Apply pitch bend as well! This also adds the base of 1200 cents required to work!
}
else if (chorus) //Chorus only has modulation of the pitch as well?
{
modulationratiocents = MIDIDEVICE_chorussinf(chorusreverbreversesamplerate,chorus,1); //Current modulation ratio!
}
else
{
modulationratiocents = 1200; //No modulation by default!
}

//Apply pitch bend to the current factor too!
modulationratiocents += samplespeedup; //Speedup according to pitch bend!
modulationratiocents -= 1200; //Make us correct!

//Apply the new modulation ratio, if needed!
if (modulationratiocents!=voice->modulationratiocents[chorus]) //Different ratio?
{
voice->modulationratiocents[chorus] = modulationratiocents; //Update the last ratio!
voice->modulationratiosamples[chorus] = cents2samplesfactorf((float)modulationratiocents); //Calculate the pitch bend and modulation ratio to apply!
}

samplepos = (int_64)(samplepos*voice->modulationratiosamples[chorus]); //Apply the pitch bend to the sample to retrieve!

//Now, calculate the start offset to start looping!
samplepos += voice->startaddressoffset; //The start of the sample!

//First: apply looping!
loopflags = voice->currentloopflags;
if (voice->has_finallooppos && (play_counter >= voice->finallooppos)) //Executing final loop?
{
samplepos -= voice->finallooppos; //Take the relative offset to the start of the final loop!
samplepos += voice->finallooppos_playcounter; //Add the relative offset to the start of our data of the final loop!
}
else if (loopflags & 1) //Currently looping and active?
{
if (samplepos >= voice->endloopaddressoffset) //Past/at the end of the loop!
{
if ((loopflags & 0xC2) == 0x82) //We're depressed, depress action is allowed (not holding) and looping until depressed?
{
if (!voice->has_finallooppos) //No final loop position set yet?
{
voice->currentloopflags &= ~0x80; //Clear depress bit!
//Loop for the last time!
voice->finallooppos = samplepos; //Our new position for our final execution till the end!
voice->has_finallooppos = 1; //We have a final loop position set!
loopflags |= 0x20; //We're to update our final loop start!
}
}

//Loop according to loop data!
temp = voice->startloopaddressoffset; //The actual start of the loop!
//Loop the data!
samplepos -= temp; //Take the ammount past the start of the loop!
samplepos %= voice->loopsize; //Loop past startloop by endloop!
samplepos += temp; //The destination position within the loop!
//Check for depress special actions!
if (loopflags&0x20) //Extra information needed for the final loop?
{
voice->finallooppos_playcounter = samplepos; //The start position within the loop to use at this point in time!
}
}
}

//Next, apply finish!
loopflags = (samplepos >= voice->endaddressoffset) || (play_counter<0); //Expired or not started yet?
if (loopflags) //Sound is finished?
{
//No sample!
return; //Done!
}

if (getSFSample16(soundfont, (uint_32)samplepos, &readsample)) //Sample found?
{
lchannel = (float)readsample; //Convert to floating point for our calculations!

//First, apply filters and current envelope!
applyMIDILowpassFilter(voice, &lchannel, Modulation, filterindex); //Low pass filter!
lchannel *= Volume; //Apply ADSR Volume envelope!
lchannel *= voice->initialAttenuation; //The volume of the samples!
lchannel *= chorusreverbvol; //Apply chorus&reverb volume for this stream!
lchannel *= VOLUME; //Apply general volume!
//Now the sample is ready for output into the actual final volume!

rchannel = lchannel; //Load into both channels!
//Now, apply panning!
lchannel *= voice->lvolume; //Apply left panning, also according to the CC!
rchannel *= voice->rvolume; //Apply right panning, also according to the CC!

//Clip the samples to prevent overflow!
if (lchannel>SHRT_MAX) lchannel = (float)SHRT_MAX;
else if (lchannel<SHRT_MIN) lchannel = (float)SHRT_MIN;
if (rchannel>SHRT_MAX) rchannel = (float)SHRT_MAX;
else if (rchannel<SHRT_MIN) rchannel = (float)SHRT_MIN;

//Give the result!
*leftsample += (int_32)lchannel; //LChannel!
*rightsample += (int_32)rchannel; //RChannel!
}
}

byte MIDIDEVICE_renderer(void* buf, uint_32 length, byte stereo, void *userdata) //Sound output renderer!
{
const float MIDI_CHORUS_SINUS_BASE = 2.0f*(float)PI*CHORUS_LFO_FREQUENCY; //MIDI Sinus Base for chorus effects!
#ifdef __HW_DISABLED
return 0; //We're disabled!
#endif
if (!stereo) return 0; //Can't handle non-stereo output!
//Initialisation info
float pitchcents, currentsamplespeedup, lvolume, rvolume, panningtemp;
INLINEREGISTER float VolumeEnvelope=0; //Current volume envelope data!
INLINEREGISTER float ModulationEnvelope=0; //Current modulation envelope data!
//Initialised values!
MIDIDEVICE_VOICE *voice = (MIDIDEVICE_VOICE *)userdata;
sample_stereo_t* ubuf = (sample_stereo_t *)buf; //Our sample buffer!
ADSR *VolumeADSR = &voice->VolumeEnvelope; //Our used volume envelope ADSR!
ADSR *ModulationADSR = &voice->ModulationEnvelope; //Our used modulation envelope ADSR!
MIDIDEVICE_CHANNEL *channel = voice->channel; //Get the channel to use!
uint_32 numsamples = length; //How many samples to buffer!
++numsamples; //Take one sample more!
byte currentchorusreverb; //Current chorus and reverb levels we're processing!
int_64 chorusreverbsamplepos;

#ifdef MIDI_LOCKSTART
//lock(voice->locknumber); //Lock us!
#endif

if (voice->VolumeEnvelope.active==0) //Simple check!
{
#ifdef MIDI_LOCKSTART
//unlock(voice->locknumber); //Lock us!
#endif
return SOUNDHANDLER_RESULT_NOTFILLED; //Empty buffer: we're unused!
}
if (memprotect(soundfont,sizeof(*soundfont),"RIFF_FILE")!=soundfont)
{
#ifdef MIDI_LOCKSTART
//unlock(voice->locknumber); //Lock us!
#endif
return SOUNDHANDLER_RESULT_NOTFILLED; //Empty buffer: we're unable to render anything!
}
if (!soundfont)
{
#ifdef MIDI_LOCKSTART
//unlock(voice->locknumber); //Lock us!
#endif
return SOUNDHANDLER_RESULT_NOTFILLED; //The same!
}
if (!channel) //Unknown channel?
{
#ifdef MIDI_LOCKSTART
//unlock(voice->locknumber); //Lock us!
#endif
return SOUNDHANDLER_RESULT_NOTFILLED; //The same!
}


#ifdef MIDI_LOCKSTART
lock(voice->locknumber); //Actually check!
#endif

if (voice->VolumeEnvelope.active == 0) //Not active after all?
{
#ifdef MIDI_LOCKSTART
unlock(voice->locknumber);
#endif
return SOUNDHANDLER_RESULT_NOTFILLED; //Empty buffer: we're unused!
}
//Calculate the pitch bend speedup!
pitchcents = (float)(channel->pitch%0x1FFF); //Load active pitch bend (unsigned), Only low 14 bits are used!
pitchcents -= (float)0x2000; //Convert to a signed value!
pitchcents /= 128.0f; //Create a value between -1 and 1!
pitchcents *= cents2samplesfactorf(voice->pitchwheelmod*pitchcents); //Influence by pitch wheel!

//Now apply to the default speedup!
currentsamplespeedup = voice->initsamplespeedup; //Load the default sample speedup for our tone!
currentsamplespeedup *= cents2samplesfactorf(pitchcents); //Calculate the sample speedup!; //Apply pitch bend!
voice->effectivesamplespeedup = (sword)currentsamplespeedup; //Load the speedup of the samples we need!

//Determine panning!
lvolume = rvolume = 0.5f; //Default to 50% each (center)!
panningtemp = voice->initpanning; //Get the panning specified!
panningtemp += voice->panningmod*((float)(voice->channel->panposition-0x2000)/128); //Apply panning CC!
lvolume -= panningtemp; //Left percentage!
rvolume += panningtemp; //Right percentage!
voice->lvolume = lvolume; //Left panning!
voice->rvolume = rvolume; //Right panning!

if (voice->request_off) //Requested turn off?
{
voice->currentloopflags |= 0x80; //Request quit looping if needed: finish sound!
} //Requested off?

//Apply sustain
voice->currentloopflags &= ~0x40; //Sustain disabled by default!
voice->currentloopflags |= (channel->sustain << 6); //Sustaining?

VolumeEnvelope = voice->CurrentVolumeEnvelope; //Make sure we don't clear!
ModulationEnvelope = voice->CurrentModulationEnvelope; //Make sure we don't clear!

int_32 lchannel, rchannel; //Left&right samples, big enough for all chorus and reverb to be applied!

float samplerate = (float)LE32(voice->sample.dwSampleRate); //The samplerate we use!

float chorusreverbreversesamplerate, playcounterfltchorusreverbreversesamplerate;
chorusreverbreversesamplerate = MIDI_CHORUS_SINUS_BASE*(1.0f/samplerate); //To multiple to divide by the sample rate(conversion from sample to seconds)

byte chorus,reverb;
uint_32 totaldelay;

//Now produce the sound itself!
for (; --numsamples;) //Produce the samples!
{
lchannel = 0; //Reset left channel!
rchannel = 0; //Reset right channel!
playcounterfltchorusreverbreversesamplerate = (float)voice->play_counter*chorusreverbreversesamplerate; //Preconvert the play counter to floating point!
for (currentchorusreverb=0;currentchorusreverb<CHORUSREVERBSIZE;++currentchorusreverb) //Process all reverb&chorus used(4 chorus channels within 4 reverb channels)!
{
chorusreverbsamplepos = voice->play_counter; //Load the current play counter!
totaldelay = voice->totaldelay[currentchorusreverb]; //Load the total delay!
chorusreverbsamplepos -= (int_64)totaldelay; //Apply specified chorus&reverb delay!
VolumeEnvelope = ADSR_tick(VolumeADSR,chorusreverbsamplepos,((voice->currentloopflags & 0xC0) != 0x80),voice->note->noteon_velocity, voice->note->noteoff_velocity); //Apply Volume Envelope!
chorus = (currentchorusreverb&0x3); //Current chorus channel!
reverb = (currentchorusreverb>>2); //Current reverb channel!
ModulationEnvelope = ADSR_tick(ModulationADSR,chorusreverbsamplepos,((voice->currentloopflags & 0xC0) != 0x80),voice->note->noteon_velocity, voice->note->noteoff_velocity); //Apply Modulation Envelope!
MIDIDEVICE_getsample(&lchannel,&rchannel, chorusreverbsamplepos, totaldelay, samplerate, voice->effectivesamplespeedup, voice, VolumeEnvelope, ModulationEnvelope, chorus, reverb,voice->chorusreverbvol[currentchorusreverb],playcounterfltchorusreverbreversesamplerate, currentchorusreverb); //Get the sample from the MIDI device!
}
//Clip the samples to prevent overflow!
if (lchannel>SHRT_MAX) lchannel = SHRT_MAX;
if (lchannel<SHRT_MIN) lchannel = SHRT_MIN;
if (rchannel>SHRT_MAX) rchannel = SHRT_MAX;
if (rchannel<SHRT_MIN) rchannel = SHRT_MIN;
ubuf->l = lchannel; //Left sample!
ubuf->r = rchannel; //Right sample!
++voice->play_counter; //Next sample!
++ubuf; //Prepare for the next sample!
}

voice->CurrentVolumeEnvelope = VolumeEnvelope; //Current volume envelope updated!
voice->CurrentModulationEnvelope = ModulationEnvelope; //Current volume envelope updated!

#ifdef MIDI_LOCKSTART
unlock(voice->locknumber); //Lock us!
#endif
return SOUNDHANDLER_RESULT_FILLED; //We're filled!
}

Used FIFO reading back pitch bend data(16-bit value):

byte readfifobuffer16_backtrace(FIFOBUFFER *buffer, word *result, uint_32 backtrace, byte finalbacktrace)
{
uint_32 readposhistory;
if (__HW_DISABLED) return 0; //Abort!
if (buffer==0) return 0; //Error: invalid buffer!
if (buffer->buffer==0) return 0; //Error invalid: buffer!
if (allcleared) return 0; //Abort: invalid buffer!

fifobuffer_save(buffer); //Save the current status for restoring, if needed!

if (buffer->lock)
{
WaitSem(buffer->lock)
if (fifobuffer_INTERNAL_freesize(buffer)<((buffer->size-1)-(backtrace<<1))) //Filled enough?
{
readposhistory = (int_64)buffer->readpos; //Save the read position!
readposhistory -= (int_64)(backtrace<<1); //Trace this far back!
for (;readposhistory<0;) //Invalid?
{
readposhistory += buffer->size; //Convert into valid range!
}
readposhistory = SAFEMOD(readposhistory,buffer->size); //Make sure we don't get past the end of the buffer!
buffer->readpos = readposhistory; //Patch the read position to the required state!
readfifobuffer16unlocked(buffer,result); //Read the FIFO buffer without lock!
fifobuffer_restore(buffer); //Restore the saved state, we haven't changed yet!
if (finalbacktrace) //Finished?
{
readfifobuffer16unlocked(buffer,result); //Read the FIFO buffer without lock normally!
}
PostSem(buffer->lock)
return 1; //Read!
}
PostSem(buffer->lock)
}
else
{
if (fifobuffer_INTERNAL_freesize(buffer)<((buffer->size-1)-(backtrace<<1))) //Filled enough?
{
readposhistory = (int_64)buffer->readpos; //Save the read position!
readposhistory -= (int_64)(backtrace<<1); //Trace this far back!
for (;readposhistory<0;) //Invalid?
{
readposhistory += buffer->size; //Convert into valid range!
}
readposhistory = SAFEMOD(readposhistory,buffer->size); //Make sure we don't get past the end of the buffer!
buffer->readpos = readposhistory; //Patch the read position to the required state!
readfifobuffer16unlocked(buffer,result); //Read the FIFO buffer without lock!
fifobuffer_restore(buffer); //Restore the saved state, we haven't changed yet!
if (finalbacktrace) //Finished?
{
readfifobuffer16unlocked(buffer,result); //Read the FIFO buffer without lock normally!
}
return 1; //Read!
}
}
return 0; //Nothing to read!
}

(from the FIFO buffer code: https://bitbucket.org/superfury/unipcemu/src/ … fer.c?at=master )

For some reason, enabling this rendering corrupts the CPU emulation in some way. Anyone can see what's going wrong here?

Edit: Disabling the readfifobuffer16_backtrace by making it return 0 always(adding a "return 0;" statement at it's start) makes Prince of Persia continue, but with a buggy screen instead of crashing at the point the flames are displayed.

Edit: Disabling the chorus/reverb effects and the readfifobuffer16_backtrace functions at the same time (making them always return 0) doesn't fix the CPU bugging out. So the problem shouldn't be at the renderer, but instead in something that has to do with MIDI(maybe the new voice allocation?)

Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io