VOGONS


First post, by canadacow

User metadata
Rank Member
Rank
Member

New version of the emulator at:

http://www.artworxinn.com/alex

Some timing fixes, tuning, new low pass filter, etc. Sounds a good deal better I think. Reverb is again turned off. I need a better method. What I need even more is optimization suggestions. I've cleaned up the code and documented more of it, so hopefully people will be able to contribute suggestions as to where to optimize the most. Have fun. Let me know what you think!

Snover, again, why no mention of my progress on your MT-32 Emulation page?

Reply 1 of 15, by teamster1975

User metadata
Rank Member
Rank
Member

Sounding pretty good Canadacow 😀 Just a few instruments slightly off pitch.
I agree, your project should be linked to on the MT32 project page, maybe the admins are too busy in real life? I'm sure they wouldn't be snubbing you.
All the best and have a great weekend.

Matthew

Reply 2 of 15, by Spikey

User metadata
Rank Oldbie
Rank
Oldbie

Just listened to it..

Certainly an improvement, now actually almost, almost sounds like the game's rendition. Keep it up. 😀

Obviously you still have a way to go. I wish you luck, keep us informed.

- Alistair

Reply 3 of 15, by ih8registrations

User metadata
Rank Oldbie
Rank
Oldbie

Here's a quick optimization to the inner loop of MidiChannel::setPitchCache,

This:

 				startp = pcache[partNum].pitchEnv.level[m]-50;
endp = pcache[partNum].pitchEnv.level[1+m]-50;
startt = 0;
endt = envtable[pcache[partNum].pitchEnv.time[m]];

is equivalent to this:

				switch(m) {
case 0:
startp = pcache[partNum].pitchEnv.level[0]-50;
endp = pcache[partNum].pitchEnv.level[1]-50;
startt = 0;
endt = envtable[pcache[partNum].pitchEnv.time[0]];
break;
case 1:
startp = pcache[partNum].pitchEnv.level[1]-50;
endp = pcache[partNum].pitchEnv.level[2]-50;
startt = 0; //pcache[partNum].pitchEnv.time[0];
endt = envtable[pcache[partNum].pitchEnv.time[1]];
break;
case 2:
startp = pcache[partNum].pitchEnv.level[2]-50;
endp = pcache[partNum].pitchEnv.level[3]-50;
startt = 0; //pcache[partNum].pitchEnv.time[1];
endt = envtable[pcache[partNum].pitchEnv.time[2]];
break;
default:
printf("What the heck!\n");
break;
}

It doesn't include default but then based on your printf comment it's superfluous.

update; don't need to do 0+m. just m.

Last edited by ih8registrations on 2003-07-26, 07:50. Edited 1 time in total.

Reply 4 of 15, by canadacow

User metadata
Rank Member
Rank
Member

Wow... very good point. Didn't realize that I had made it much more complicated for myself than it needed to be. Thanks. I'm going to change this (and the other envelope caching routines) immediately to use this instead!

Reply 6 of 15, by ih8registrations

User metadata
Rank Oldbie
Rank
Oldbie

Same function at the end;

This:

	for(sampoff=0;sampoff<endt;sampoff++) {	
pitchEnvlevel = (int)((float)startp + (distp / distt) * (float)(sampoff));
pcache[partNum].pEnvCache.decay[sampoff] = pitchEnvlevel;
}

for(sampoff=endt;sampoff<100;sampoff++) {
pitchEnvlevel = endp;
pcache[partNum].pEnvCache.decay[sampoff] = pitchEnvlevel;
}

is faster than this:

	for(sampoff=0;sampoff<100;sampoff++) {	
if(sampoff<endt) {
pitchEnvlevel = (int)((float)startp + (distp / distt) * (float)(sampoff));
} else {
pitchEnvlevel = endp;
}

pcache[partNum].pEnvCache.decay[sampoff] = pitchEnvlevel;
}

update: superfluous assignment in 2nd loop removed:

	for(sampoff=endt;sampoff<100;sampoff++) {	
pcache[partNum].pEnvCache.decay[sampoff] = endp;
}

update: with the conditional removed, neither need the assignment. it becomes:

	for(sampoff=0;sampoff<endt;sampoff++) 	
pcache[partNum].pEnvCache.decay[sampoff] = (int)((float)startp + (distp / distt) * (float)(sampoff));

for(sampoff=endt;sampoff<100;sampoff++)
pcache[partNum].pEnvCache.decay[sampoff] = endp;
Last edited by ih8registrations on 2003-07-26, 07:49. Edited 1 time in total.

Reply 7 of 15, by ih8registrations

User metadata
Rank Oldbie
Rank
Oldbie

A cleaned up MidiChannel::setPitchCache:

INLINE void MidiChannel::setPitchCache(int partNum) {
Bit16s found, tmp, score, startp, endp, pitchEnvlevel, sampoff, pitchEnvoff, m;
Bit32s realtime, distt, effectors[4][2];

for(m=0;m<4;m++) {
effectors[m][0] = pcache[partNum].pitchEnv.level[m];
effectors[m][1] = 0;
}

pcache[partNum].pEnvCache.sustainpoint = 300;
for(sampoff=0;sampoff<300;sampoff++) {
pitchEnvoff = sampoff;
found = realtime = 0;

for(m=0;m<3;m++) {
if(pitchEnvoff < envtable[pcache[partNum].pitchEnv.time[m]]) {
startp = pcache[partNum].pitchEnv.level[m]-50;
endp = pcache[partNum].pitchEnv.level[1+m]-50;
distt = envtable[pcache[partNum].pitchEnv.time[m]];

if(distt)
pitchEnvlevel = (int)((float)startp + ((endp - startp) / distt) * (float)(pitchEnvoff))
else pitchEnvlevel = (int)endp;

if ((m>0) && (realtime >0)){
score = effectors[m][1] = realtime;
pitchEnvlevel *= realtime;

for(int q=0;q<m;q++) {
if(realtime>effectors[q][1]) {
tmp = (100-(realtime-effectors[q][1]))>>4;
pitchEnvlevel += (effectors[q][0] * tmp);
score+=tmp;
}
}
if(score>0) pitchEnvlevel = pitchEnvlevel / score;
}
pcache[partNum].pEnvCache.attack[sampoff] = pitchEnvlevel;
found++;
break;
}
pitchEnvoff-=envtable[pcache[partNum].pitchEnv.time[m]];
realtime +=pcache[partNum].pitchEnv.time[m];
)
if(found) continue;
if(sampoff<pcache[partNum].pEnvCache.sustainpoint) pcache[partNum].pEnvCache.sustainpoint = sampoff;
pcache[partNum].pEnvCache.attack[sampoff] = (int)(pcache[partNum].pitchEnv.level[3]-50);
}
startp = pcache[partNum].pitchEnv.level[3]-50;
endp = pcache[partNum].pitchEnv.level[4]-50;
distt = envtable[pcache[partNum].pitchEnv.time[3]];

for(sampoff=0;sampoff<distt;sampoff++)
pcache[partNum].pEnvCache.decay[sampoff] = (int)((float)startp + ((endp - startp) / distt) * (float)(sampoff));

for(sampoff=distt;sampoff<100;sampoff++)
pcache[partNum].pEnvCache.decay[sampoff] = endp;

pcache[partNum].pEnvCache.sustainval = (int)(pcache[partNum].pitchEnv.level[3]-50);
}

Almost fits in one screen(vs 3 pages of the old code).

ps. Is there a way to keep this board from mangling indentions?
It left justifies everything.

Last edited by ih8registrations on 2003-07-26, 08:10. Edited 1 time in total.

Reply 8 of 15, by ih8registrations

User metadata
Rank Oldbie
Rank
Oldbie

Cleaned up getPitchEnvelope:

INLINE Bit16s MidiChannel::getPitchEnvelope(int partNum, dpoly *poly, bool inDecay) {
patchCache *tcache = &pcache[partNum];
Bit16s destin, tc;
Bit32u sampoff;

if(inDecay) {
destin = poly->decay[partNum][PITCHENV];
sampoff = (destin) >> ENVSPEED;
if(sampoff>100) return tcache->pitchEnv.level[5];
} else {
int timekey = fildeptable[tcache->pitchEnv.timekeyfollow][poly->freqnum];
destin = (poly->envpos * timekey) >> 8;
sampoff = (destin) >> ENVSPEED;

if(sampoff>=tcache->pEnvCache.sustainpoint) {
if(tcache->sustain) {
tc = tcache->pEnvCache.sustainval;
poly->pitchsustain = true;
} else {
tc = tcache->pEnvCache.decay[0];
poly->decaying[partNum][PITCHENV] = true;
poly->decay[partNum][PITCHENV] = 0;
}
return tc;
}
}
tc = tcache->pEnvCache.attack[sampoff];
int tca = tcache->pEnvCache.attack[sampoff+1];
tc = (tc + (((tca - tc) * (destin & ENVPART)) >>ENVSPEED));
poly->pitchsustain = false;

return tc;
}

Duplicated the return tc rather than the last big block of code, got rid of unnecessary temp variables; what they held easily fits into the equation for tc & is still ledgible.

Like the cache functions, the other envelope functions can be similarly optimized.

Last edited by ih8registrations on 2003-07-26, 07:47. Edited 1 time in total.

Reply 9 of 15, by Zorbid

User metadata
Rank Member
Rank
Member
ih8registrations wrote:

ps. Is there a way to keep this board from mangling indentions?
It left justifies everything.

You can use

.

If you click on "quote", to reply, the text in the input field still has the indentation. They are just removed when they are displayed, not erased.

Reply 10 of 15, by ih8registrations

User metadata
Rank Oldbie
Rank
Oldbie

Thanks Zorbid, and now for another function update:

MidiChannel::MidiChannel(int samp, int cnum) {
isRy = holdpedal = isPlaying = false;
rightvol = leftvol = 255;
patch = storedpatch = 0;
sampRate = samp;
channum = cnum;
volume = 102;
panpot = 64;

if(cnum==8) {
isRy = true;
int pan;
volume = 127;
// Cache drum patches
for(int q=0;q<30;q++) {
SetPatch(0,q);
for(int m=0;m<42;m++) {
if(DrumTable[m]==q) {
pan = drmPanTable[m];
if(pan<64) {
drumPan[q][0] = 255; // lv
drumPan[q][1] = pan << 2; // rv
} else {
drumPan[q][0] = (63-(pan-63)) << 2; // lv
drumPan[q][1] = 255; // rv
}
break;
}
}
}
}
memset(notepoly,0,sizeof(notepoly));
memset(reverb,0,sizeof(reverb));
reverbat = 0;
}

pulled out variable init & removed unnecessary temp variables & 2nd compare from the inner loop.

Reply 11 of 15, by canadacow

User metadata
Rank Member
Rank
Member

ih8registrations, I hope you're not offended, but as you were posting those optimizations to my envelope caching routines, I was actually in the process of removing the envelope caching from my code. They were detrimental to the sound (not your improvements, my envelope cacher). You see, they were based on my incorrect assumption that the envelope times were linear (i.e. 50 = 50 samples, 100 = 100 samples, etc.) That was not the case. As with a lot of things on the MT-32, the timing is logarithmic. As such 50 = .297 seconds where as 100 = 22 seconds. Since I was limiting my envelope to just 400 "samples" though, in the smaller timing numbers like 10 or 20, the values would be 0 when on the MT-32 this results in a very fast, though noticable envelope (of only 20-50 actual PCM samples). So, tonight I've spent my time eliminating the envelope caching code and replacing it with an on the fly envelope calculator. Due to some heavy thinking, its actually ended up more streamlined than the original table based envelope code.

During the sample generation the "cache code" required several branches, multiplications, and arithmatic shifts. My "on the fly code", on the other hand, normally requires one multiplication (imul) and one divide (idiv). I feel like such an idiot for leaving that code in there as long as I did. Thanks for your improvements. It wasn't for nothing. Really. Because you got me looking at the caching code again (which I had ignored in favor of the filter code) and I realized it needed a replacement. Furthermore, you inspired me to think more in a more optimized manner (i.e. no more unnecessary branching).

Reply 12 of 15, by ih8registrations

User metadata
Rank Oldbie
Rank
Oldbie

No problem, I only went over the pitch caching function.
Also, for a cheap gain I was using bits16s, 32, etc as equivalents to int & float which is wrong looking at your definition of them. It's minor.

And speaking of avoiding jumps, in fixkeyfollow, I've replaced the big case statement with an index lookup.

INLINE int MidiChannel::FixKeyfollow(int srckey, int *dir) {
if (srckey>=0 && srckey<=16) {
int keyfix[17] = { 256, 128, 64, 0, 32, 64, 96, 128, 128+32, 192, 192+32, 256, 256+64, 256+128, 512, 259, 269 };

if (srckey<3)
*dir = -1
else if (srckey=3)
*dir = 0
else
*dir = 1;

return keyfix[srckey];
} else {
//LOG(LOG_ERROR|LOG_MISC,"Missed key: %d", srckey);
return 256;
}
}
Last edited by ih8registrations on 2003-07-26, 13:16. Edited 1 time in total.

Reply 13 of 15, by canadacow

User metadata
Rank Member
Rank
Member

Wow, again thanks a million. I guess when I decided to rely on switches I must have been trying to convince myself it was for code clarification reasons. Clearly not needed! 😜

Reply 14 of 15, by ih8registrations

User metadata
Rank Oldbie
Rank
Oldbie

Code clarification is what comments are for:) You know, clarification without the cycle penalty:) Since most coders don't like commenting or documentation, only code, I suppose teaching the idea of self documenting code is the busines worlds way of tricking coders into doing so:)

ps. If you'll notice my post on updating the MidiChannel function,
you'll see I left comments for lv, rv for reference, having removed them from the inner loop. The comments are just as descriptive as the lv, rv variables were but without the cycle cost.

Last edited by ih8registrations on 2003-07-26, 09:59. Edited 1 time in total.