VOGONS


First post, by superfury

User metadata
Rank l33t++
Rank
l33t++

I know that the specification has parameters that specify percentages(0-100%) of Chorus and Reverb, but how do I apply them to a running sample? I would need to render the samples and volume negatively compared to the original stream(the sample stream read from the loaded soundfont that has the volume envelope and looping applied to it. This one has to be the first to finish making sound(during release/finish) in order for the volume envelope to create valid tails using the same volume envelope.

Thus:
Original stream(raw sound) sample&volume envelope position >= duplicate(delayed/chorus/reverb) streams sample&volume envelope position.

Of course, the samples themselves can be modulated according to pitch and effects, but the origin position must still apply(the position which isn't modulated, i.o.w. the output sample position).

The problem is that the Soundfont 2.04 spec only specifies a number that goes from 0%-100%. How do I convert the sample stream to use that value(combined with the continuous controllers for reverb and chorus) to produce the correct output stream with the effects? I can multiply the origin position with a <=1.0 number to apply them, as well as substract time(as long as it's lower or equal to the origin stream position), but what is the correct way to handle it?

MIDI renderer: https://bitbucket.org/superfury/unipcemu/src/ … ice.c?at=master
DADSR calculations: https://bitbucket.org/superfury/unipcemu/src/ … dsr.c?at=master

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

Reply 1 of 1, by superfury

User metadata
Rank l33t++
Rank
l33t++

I've tried to implement the chorus and reverb into my Soundfont sound generation module, but I keep getting sound which sounds strange:

Initialization executed during when a note ON event is received:

	//Chorus percentage
panningtemp = 0.0f; //Default to none!
if (lookupSFInstrumentModGlobal(soundfont, instrumentptr.genAmount.wAmount, ibag, 0x00DD, &applymod)) //Gotten panning modulator?
{
panningtemp = (float)applymod.modAmount; //Get the amount specified!
panningtemp *= 0.0002f; //Make into a percentage, it's in 0.02% units!
}

for (chorusreverbdepth=1;chorusreverbdepth<0x100;chorusreverbdepth++) //Process all possible chorus depths!
{
voice->chorusdepth[chorusreverbdepth] = powf(panningtemp,(float)chorusreverbdepth); //Apply the volume!
}
voice->chorusdepth[0] = 0.0; //Always none at the original level!

//Reverb percentage
panningtemp = 0.0f; //Default to none!
if (lookupSFInstrumentModGlobal(soundfont, instrumentptr.genAmount.wAmount, ibag, 0x00DB, &applymod)) //Gotten panning modulator?
{
panningtemp = (float)applymod.modAmount; //Get the amount specified!
panningtemp *= 0.0002f; //Make into a percentage, it's in 0.02% units!
}
for (chorusreverbdepth=0;chorusreverbdepth<0x100;chorusreverbdepth++) //Process all possible chorus depths!
{
for (chorusreverbchannel=0;chorusreverbchannel<4;chorusreverbchannel++) //Process all channels!
{
if (chorusreverbdepth==0)
{
voice->reverbdepth[chorusreverbdepth][chorusreverbchannel] = 0.0; //Nothing at the main channel!
}
else //Valid depth?
{
voice->reverbdepth[chorusreverbdepth][chorusreverbchannel] = (float)dB2factor((pow(panningtemp, chorusreverbdepth),chorusreverbchannel),1.0); //Apply the volume!
}
}
}

voice->currentchorusdepth = channel->choruslevel; //Current chorus depth!
voice->currentreverbdepth = channel->reverblevel; //Curernt reverb depth!

for (chorusreverbchannel=0;chorusreverbchannel<4;++chorusreverbchannel) //Process all reverb&chorus channels, precalculating every used value!
{
voice->activechorusdepth[chorusreverbchannel] = voice->chorusdepth[voice->currentchorusdepth]; //The chorus feedback strength for that channel!
voice->activereverbdepth[chorusreverbchannel] = voice->reverbdepth[voice->currentreverbdepth][chorusreverbchannel]; //The
}

//Setup default channel chorus/reverb!
voice->activechorusdepth[0] = 1.0; //Always the same: produce full sound!
voice->activereverbdepth[0] = 1.0; //Always the same: produce full sound!

Voice rendering routine:

/*

Voice support

*/

OPTINLINE static void MIDIDEVICE_getsample(int_32 *leftsample, int_32 *rightsample, int_64 play_counter, float samplerate, float samplespeedup, MIDIDEVICE_VOICE *voice, float Volume, float Modulation, byte chorus, byte reverb, float chorusvol, float reverbvol) //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!

samplepos = play_counter; //Load the current play counter!
samplepos -= (int_64)(chorus_delay[chorus]*samplerate); //Apply specified chorus delay!
samplepos -= (int_64)(reverb_delay[reverb]*samplerate); //Apply specified reverb delay!
samplepos = (int_64)(samplepos*samplespeedup); //Affect speed through cents and other global factors!
if (voice->modenv_pitchfactor) //Gotten a modulation envelope to process to the pitch?
{
samplepos = (int_64)(samplepos*cents2samplesfactor(voice->modenv_pitchfactor)); //Apply the pitch bend to the sample to retrieve!
}
if (chorus) //Chorus has modulation of the pitch as well?
{
samplepos = (int_64)(samplepos*cents2samplesfactor((float)sin((double)play_counter*0.01)*(chorus*(1.0/256.0))+(1200.0f-1.0f))); //Apply the pitch bend to the sample to retrieve!
}

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!
Show last 180 lines
			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); //Low pass filter!
lchannel *= Volume; //Apply ADSR Volume envelope!
lchannel *= voice->initialAttenuation; //The volume of the samples!
//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!

lchannel *= VOLUME;
rchannel *= VOLUME;

//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;

lchannel *= chorusvol; //Apply chorus volume for this stream!
rchannel *= chorusvol; //Apply chorus volume for this stream!

lchannel *= reverbvol; //Apply reverb volume for this stream!
rchannel *= reverbvol; //Apply reverb volume for this stream!

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

byte MIDIDEVICE_renderer(void* buf, uint_32 length, byte stereo, void *userdata) //Sound output renderer!
{
#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!

#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 *= (float)cents2samplesfactor(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 *= (float)cents2samplesfactor(pitchcents); //Calculate the sample speedup!; //Apply pitch bend!
voice->effectivesamplespeedup = 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!

//Now produce the sound itself!
for (; --numsamples;) //Produce the samples!
{
lchannel = 0; //Reset left channel!
rchannel = 0; //Reset right channel!
for (currentchorusreverb=0;currentchorusreverb<16;++currentchorusreverb) //Process all reverb&chorus used(4 chorus channels within 4 reverb channels)!
{
VolumeEnvelope = ADSR_tick(VolumeADSR,voice->play_counter,((voice->currentloopflags & 0xC0) != 0x80),voice->note->noteon_velocity, voice->note->noteoff_velocity); //Apply Volume Envelope!
ModulationEnvelope = ADSR_tick(ModulationADSR,voice->play_counter,((voice->currentloopflags & 0xC0) != 0x80),voice->note->noteon_velocity, voice->note->noteoff_velocity); //Apply Modulation Envelope!
MIDIDEVICE_getsample(&lchannel,&rchannel, voice->play_counter, (float)voice->sample.dwSampleRate, voice->effectivesamplespeedup, voice, VolumeEnvelope, ModulationEnvelope, (byte)(currentchorusreverb&0x3), (byte)(currentchorusreverb>>2),voice->activechorusdepth[(currentchorusreverb&3)],voice->activereverbdepth[(currentchorusreverb>>3)]); //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!
}

Anyone can see what's going wrong here?

The constants and related saved data used:

//Reverb delay in seconds
#define REVERB_DELAY 0.25f

//Chorus delay in seconds
#define CHORUS_DELAY 0.001f

float reverb_delay[0x100];
float chorus_delay[0x100];

Which is initialized during device reset:

OPTINLINE static void reset_MIDIDEVICE() //Reset the MIDI device for usage!
{
//First, our variables!
byte channel;
word notes;

lockaudio();
memset(&MIDI_channels,0,sizeof(MIDI_channels)); //Clear our data!
memset(&activevoices,0,sizeof(activevoices)); //Clear our active voices!
for (channel=0;channel<0x10;)
{
for (notes=0;notes<0x100;)
{
MIDI_channels[channel].notes[notes].channel = channel;
MIDI_channels[channel].notes[notes].note = (byte)notes;

//Also apply delays while we're at it(also 256 values)!
reverb_delay[notes] = REVERB_DELAY*(float)notes; //The reverb delay to use for this stream!
chorus_delay[notes] = CHORUS_DELAY*(float)notes; //The chorus delay to use for this stream!

++notes; //Next note!
}
MIDI_channels[channel].bank = MIDI_channels[channel].activebank = 0; //Reset!
MIDI_channels[channel].channelrangemin = MIDI_channels[channel].channelrangemax = channel; //We respond to this channel only!
MIDI_channels[channel].control = 0; //First instrument!
MIDI_channels[channel].pitch = 0x2000; //Centered pitch = Default pitch!
MIDI_channels[channel].pressure = 0x40; //Centered pressure!
MIDI_channels[channel].program = 0; //First program!
MIDI_channels[channel].sustain = 0; //Disable sustain!
MIDI_channels[channel].volume = 0x2000; //Centered volume as the default volume!
MIDI_channels[channel].panposition = 0x2000; //Centered pan position as the default pan!
MIDI_channels[channel].lvolume = MIDI_channels[channel].rvolume = 0.5; //Accompanying the pan position: centered volume!
MIDI_channels[channel++].mode = MIDIDEVICE_DEFAULTMODE; //Use the default mode!
}
MIDI_channels[MIDI_DRUMCHANNEL].bank = MIDI_channels[MIDI_DRUMCHANNEL].activebank = 0x80; //We're locked to a drum set!
unlockaudio();
}

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