📄 playmidi.c
字号:
return NULL; return ip;}#if 0/* reduce_voice_CPU() may not have any speed advantage over reduce_voice(). * So this function is not used, now. *//* The goal of this routine is to free as much CPU as possible without loosing too much sound quality. We would like to know how long a note has been playing, but since we usually can't calculate this, we guess at the value instead. A bad guess is better than nothing. Notes which have been playing a short amount of time are killed first. This causes decays and notes to be cut earlier, saving more CPU time. It also causes notes which are closer to ending not to be cut as often, so it cuts a different note instead and saves more CPU in the long run. ON voices are treated a little differently, since sound quality is more important than saving CPU at this point. Duration guesses for loop regions are very crude, but are still better than nothing, they DO help. Non-looping ON notes are cut before looping ON notes. Since a looping ON note is more likely to have been playing for a long time, we want to keep it because it sounds better to keep long notes.*/static int reduce_voice_CPU(void){ int32 lv, v, vr; int i, j, lowest=-0x7FFFFFFF; int32 duration; i = upper_voices; lv = 0x7FFFFFFF; /* Look for the decaying note with the longest remaining decay time */ /* Protect drum decays. They do not take as much CPU (?) and truncating them early sounds bad, especially on snares and cymbals */ for(j = 0; j < i; j++) { if(voice[j].status & VOICE_FREE || voice[j].cache != NULL) continue; /* skip notes that don't need resampling (most drums) */ if (voice[j].sample->note_to_use) continue; if(voice[j].status & ~(VOICE_ON | VOICE_DIE | VOICE_SUSTAINED)) { /* Choose note with longest decay time remaining */ /* This frees more CPU than choosing lowest volume */ if (!voice[j].envelope_increment) duration = 0; else duration = (voice[j].envelope_target - voice[j].envelope_volume) / voice[j].envelope_increment; v = -duration; if(v < lv) { lv = v; lowest = j; } } } if(lowest != -0x7FFFFFFF) { /* This can still cause a click, but if we had a free voice to spare for ramping down this note, we wouldn't need to kill it in the first place... Still, this needs to be fixed. Perhaps we could use a reserve of voices to play dying notes only. */ cut_notes++; return lowest; } /* try to remove VOICE_DIE before VOICE_ON */ lv = 0x7FFFFFFF; lowest = -1; for(j = 0; j < i; j++) { if(voice[j].status & VOICE_FREE || voice[j].cache != NULL) continue; if(voice[j].status & ~(VOICE_ON | VOICE_SUSTAINED)) { /* continue protecting non-resample decays */ if (voice[j].status & ~(VOICE_DIE) && voice[j].sample->note_to_use) continue; /* choose note which has been on the shortest amount of time */ /* this is a VERY crude estimate... */ if (voice[j].sample->modes & MODES_LOOPING) duration = voice[j].sample_offset - voice[j].sample->loop_start; else duration = voice[j].sample_offset; if (voice[j].sample_increment > 0) duration /= voice[j].sample_increment; v = duration; if(v < lv) { lv = v; lowest = j; } } } if(lowest != -1) { cut_notes++; return lowest; } /* try to remove VOICE_SUSTAINED before VOICE_ON */ lv = 0x7FFFFFFF; lowest = -0x7FFFFFFF; for(j = 0; j < i; j++) { if(voice[j].status & VOICE_FREE || voice[j].cache != NULL) continue; if(voice[j].status & VOICE_SUSTAINED) { /* choose note which has been on the shortest amount of time */ /* this is a VERY crude estimate... */ if (voice[j].sample->modes & MODES_LOOPING) duration = voice[j].sample_offset - voice[j].sample->loop_start; else duration = voice[j].sample_offset; if (voice[j].sample_increment > 0) duration /= voice[j].sample_increment; v = duration; if(v < lv) { lv = v; lowest = j; } } } if(lowest != -0x7FFFFFFF) { cut_notes++; return lowest; } /* try to remove chorus before VOICE_ON */ lv = 0x7FFFFFFF; lowest = -0x7FFFFFFF; for(j = 0; j < i; j++) { if(voice[j].status & VOICE_FREE || voice[j].cache != NULL) continue; if(voice[j].chorus_link < j) { /* score notes based on both volume AND duration */ /* this scoring function needs some more tweaking... */ if (voice[j].sample->modes & MODES_LOOPING) duration = voice[j].sample_offset - voice[j].sample->loop_start; else duration = voice[j].sample_offset; if (voice[j].sample_increment > 0) duration /= voice[j].sample_increment; v = voice[j].left_mix * duration; vr = voice[j].right_mix * duration; if(voice[j].panned == PANNED_MYSTERY && vr > v) v = vr; if(v < lv) { lv = v; lowest = j; } } } if(lowest != -0x7FFFFFFF) { cut_notes++; /* hack - double volume of chorus partner, fix pan */ j = voice[lowest].chorus_link; voice[j].velocity <<= 1; voice[j].panning = channel[voice[lowest].channel].panning; recompute_amp(j); apply_envelope_to_amp(j); return lowest; } lost_notes++; /* try to remove non-looping voices first */ lv = 0x7FFFFFFF; lowest = -0x7FFFFFFF; for(j = 0; j < i; j++) { if(voice[j].status & VOICE_FREE || voice[j].cache != NULL) continue; if(!(voice[j].sample->modes & MODES_LOOPING)) { /* score notes based on both volume AND duration */ /* this scoring function needs some more tweaking... */ duration = voice[j].sample_offset; if (voice[j].sample_increment > 0) duration /= voice[j].sample_increment; v = voice[j].left_mix * duration; vr = voice[j].right_mix * duration; if(voice[j].panned == PANNED_MYSTERY && vr > v) v = vr; if(v < lv) { lv = v; lowest = j; } } } if(lowest != -0x7FFFFFFF) { return lowest; } lv = 0x7FFFFFFF; lowest = 0; for(j = 0; j < i; j++) { if(voice[j].status & VOICE_FREE || voice[j].cache != NULL) continue; if (!(voice[j].sample->modes & MODES_LOOPING)) continue; /* score notes based on both volume AND duration */ /* this scoring function needs some more tweaking... */ duration = voice[j].sample_offset - voice[j].sample->loop_start; if (voice[j].sample_increment > 0) duration /= voice[j].sample_increment; v = voice[j].left_mix * duration; vr = voice[j].right_mix * duration; if(voice[j].panned == PANNED_MYSTERY && vr > v) v = vr; if(v < lv) { lv = v; lowest = j; } } return lowest;}#endif/* this reduces voices while maintaining sound quality */static int reduce_voice(void){ int32 lv, v; int i, j, lowest=-0x7FFFFFFF; i = upper_voices; lv = 0x7FFFFFFF; /* Look for the decaying note with the smallest volume */ /* Protect drum decays. Truncating them early sounds bad, especially on snares and cymbals */ for(j = 0; j < i; j++) { if(voice[j].status & VOICE_FREE || (voice[j].sample->note_to_use && ISDRUMCHANNEL(voice[j].channel))) continue; if(voice[j].status & ~(VOICE_ON | VOICE_DIE | VOICE_SUSTAINED)) { /* find lowest volume */ v = voice[j].left_mix; if(voice[j].panned == PANNED_MYSTERY && voice[j].right_mix > v) v = voice[j].right_mix; if(v < lv) { lv = v; lowest = j; } } } if(lowest != -0x7FFFFFFF) { /* This can still cause a click, but if we had a free voice to spare for ramping down this note, we wouldn't need to kill it in the first place... Still, this needs to be fixed. Perhaps we could use a reserve of voices to play dying notes only. */ cut_notes++; free_voice(lowest); if(!prescanning_flag) ctl_note_event(lowest); return lowest; } /* try to remove VOICE_DIE before VOICE_ON */ lv = 0x7FFFFFFF; lowest = -1; for(j = 0; j < i; j++) { if(voice[j].status & VOICE_FREE) continue; if(voice[j].status & ~(VOICE_ON | VOICE_SUSTAINED)) { /* continue protecting drum decays */ if (voice[j].status & ~(VOICE_DIE) && (voice[j].sample->note_to_use && ISDRUMCHANNEL(voice[j].channel))) continue; /* find lowest volume */ v = voice[j].left_mix; if(voice[j].panned == PANNED_MYSTERY && voice[j].right_mix > v) v = voice[j].right_mix; if(v < lv) { lv = v; lowest = j; } } } if(lowest != -1) { cut_notes++; free_voice(lowest); if(!prescanning_flag) ctl_note_event(lowest); return lowest; } /* try to remove VOICE_SUSTAINED before VOICE_ON */ lv = 0x7FFFFFFF; lowest = -0x7FFFFFFF; for(j = 0; j < i; j++) { if(voice[j].status & VOICE_FREE) continue; if(voice[j].status & VOICE_SUSTAINED) { /* find lowest volume */ v = voice[j].left_mix; if(voice[j].panned == PANNED_MYSTERY && voice[j].right_mix > v) v = voice[j].right_mix; if(v < lv) { lv = v; lowest = j; } } } if(lowest != -0x7FFFFFFF) { cut_notes++; free_voice(lowest); if(!prescanning_flag) ctl_note_event(lowest); return lowest; } /* try to remove chorus before VOICE_ON */ lv = 0x7FFFFFFF; lowest = -0x7FFFFFFF; for(j = 0; j < i; j++) { if(voice[j].status & VOICE_FREE) continue; if(voice[j].chorus_link < j) { /* find lowest volume */ v = voice[j].left_mix; if(voice[j].panned == PANNED_MYSTERY && voice[j].right_mix > v) v = voice[j].right_mix; if(v < lv) { lv = v; lowest = j; } } } if(lowest != -0x7FFFFFFF) { cut_notes++; /* hack - double volume of chorus partner, fix pan */ j = voice[lowest].chorus_link; voice[j].velocity <<= 1; voice[j].panning = channel[voice[lowest].channel].panning; recompute_amp(j); apply_envelope_to_amp(j); free_voice(lowest); if(!prescanning_flag) ctl_note_event(lowest); return lowest; } lost_notes++; /* remove non-drum VOICE_ON */ lv = 0x7FFFFFFF; lowest = -0x7FFFFFFF; for(j = 0; j < i; j++) { if(voice[j].status & VOICE_FREE || (voice[j].sample->note_to_use && ISDRUMCHANNEL(voice[j].channel))) continue; /* find lowest volume */ v = voice[j].left_mix; if(voice[j].panned == PANNED_MYSTERY && voice[j].right_mix > v) v = voice[j].right_mix; if(v < lv) { lv = v; lowest = j; } } if(lowest != -0x7FFFFFFF) { free_voice(lowest); if(!prescanning_flag) ctl_note_event(lowest); return lowest; } /* remove all other types of notes */ lv = 0x7FFFFFFF; lowest = 0; for(j = 0; j < i; j++) { if(voice[j].status & VOICE_FREE) continue; /* find lowest volume */ v = voice[j].left_mix; if(voice[j].panned == PANNED_MYSTERY && voice[j].right_mix > v) v = voice[j].right_mix; if(v < lv) { lv = v; lowest = j; } } free_voice(lowest); if(!prescanning_flag) ctl_note_event(lowest); return lowest;}void free_voice(int v1){ int v2;#ifdef ENABLE_PAN_DELAY if (voice[v1].pan_delay_buf != NULL) { free(voice[v1].pan_delay_buf); voice[v1].pan_delay_buf = NULL; }#endif /* ENABLE_PAN_DELAY */ v2 = voice[v1].chorus_link; if(v1 != v2)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -