ADSR包络Release阶段失效,按键松开后声音直接切断的问题求助
ADSR包络Release阶段失效,按键松开后声音直接切断的问题求助
看起来你的问题出在两个核心地方:包络的设计逻辑和按键松开后的波形生成处理,我帮你拆解一下并给出修复方案:
一、核心问题分析
1. 全局共享包络导致多音符Release异常
你现在用了一个全局的ADSREnvelope,但每个音符的按下、松开时间都是独立的——比如按下A键再按下B键,松开A键时A键应该进入自己的Release阶段衰减,而B键继续保持Sustain阶段。但全局包络只能对所有音符的总振幅进行控制,根本无法处理单个音符的独立包络逻辑,这是最关键的错误。
2. 按键松开时直接停止波形生成
当你松开某个按键,直接将keysPressed[i]设为false,导致该音符的波形立即停止生成,完全没有机会通过Release阶段衰减振幅,声音自然会直接切断。
二、具体修复方案
1. 给每个音符分配独立的ADSREnvelope
把原来的全局包络改成每个音符对应一个包络实例,这样每个音符的ADSR阶段都能独立控制。
2. 调整按键松开后的逻辑
按键松开时不直接停止波形生成,而是触发对应音符的Release阶段,直到该音符的振幅衰减到0后再停止生成波形。
三、修改后的关键代码片段
1. 全局变量部分修改
#include <iostream> #include <cmath> #include <vector> #include <algorithm> // 用于max/min函数 #include "raylib/raylib.h" using namespace std; #define SIGNAL_SIZE 1024 #define SAMPLE_RATE 44100 #define BITS_PER_SAMPLE 16 #define NUM_KEYS 15 #define AMPLITUDE 8000 const float BASE_FREQUENCY = 220.0f; const float twelfthRootOf2 = pow(2.0f, 1.0f / 12.0f); float signal[SIGNAL_SIZE]; const float deltaTime = 1 / (float)SAMPLE_RATE; double elapsedTime = 0.0; struct ADSREnvelope { double attackTime; double decayTime; double releaseTime; double startAmplitude; double sustainAmplitude; double keydownTime; double keyupTime; bool notePressed; ADSREnvelope() { attackTime = 0.1; // 缩短Attack时间,更贴近真实乐器 decayTime = 0.2; releaseTime = 1.0; startAmplitude = 1.0; sustainAmplitude = 0.7; keydownTime = 0.0; keyupTime = 0.0; notePressed = false; } void keydown(double _keydownTime) { keydownTime = _keydownTime; notePressed = true; } void keyup(double _keyupTime) { keyupTime = _keyupTime; notePressed = false; } double getAmplitude(double currTime) { double currAmp = 0.0; double lifetime = currTime - keydownTime; if (notePressed) { if (lifetime <= attackTime) { currAmp = (lifetime / attackTime) * startAmplitude; } else if (lifetime > attackTime && lifetime <= (attackTime + decayTime)) { currAmp = startAmplitude - ((lifetime - attackTime) / decayTime) * (startAmplitude - sustainAmplitude); } else { currAmp = sustainAmplitude; } } else { double releaseElapsed = currTime - keyupTime; if (releaseElapsed > releaseTime) { currAmp = 0.0; } else { currAmp = sustainAmplitude - (releaseElapsed / releaseTime) * sustainAmplitude; } } // 确保振幅在合法范围内 currAmp = max(0.0, min(currAmp, startAmplitude)); return currAmp; } }; // 每个音符对应独立的包络、激活状态和相位 vector<ADSREnvelope> envelopes(NUM_KEYS); vector<bool> keysDown(NUM_KEYS, false); vector<bool> notesActive(NUM_KEYS, false); vector<float> phases(NUM_KEYS, 0.0f);
2. 音频流更新函数修改
float sawWave(float phase) { return (2 * phase) - 1; } void updateStream(void* buffer, unsigned int frames) { short* d = (short*)buffer; for (unsigned int n = 0; n < frames; n++) { float sample = 0.0f; int activeNotes = 0; for (int i = 0; i < NUM_KEYS; i++) { if (notesActive[i]) { double amp = envelopes[i].getAmplitude(elapsedTime); // 振幅降到0,停止该音符的波形生成 if (amp <= 0.00001) { notesActive[i] = false; continue; } float currFrequency = BASE_FREQUENCY * pow(twelfthRootOf2, i); float increment = currFrequency / SAMPLE_RATE; // 用当前音符的包络振幅混合波形 sample += sawWave(phases[i]) * amp; phases[i] += increment; if (phases[i] > 1.0f) { phases[i] -= 1.0f; } activeNotes++; } } // 混合多音符样本,避免音量过载 if (activeNotes > 0) { sample /= activeNotes; sample *= 1.3; // 轻微补偿音量衰减 } else { sample = 0.0f; } d[n] = static_cast<short>(sample * AMPLITUDE); elapsedTime += deltaTime; } }
3. 主循环按键逻辑修改
int main(void) { const int screenWidth = 800; const int screenHeight = 450; InitWindow(screenWidth, screenHeight, "noyz"); SetTargetFPS(60); // AUDIO初始化 InitAudioDevice(); SetAudioStreamBufferSizeDefault(SIGNAL_SIZE); AudioStream stream = LoadAudioStream(SAMPLE_RATE, BITS_PER_SAMPLE, 1); SetAudioStreamCallback(stream, updateStream); PlayAudioStream(stream); vector<string> notes = { "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; KeyboardKey keys[] = { KEY_Z, KEY_S, KEY_X, KEY_C, KEY_F, KEY_V, KEY_G, KEY_B, KEY_N, KEY_J, KEY_M, KEY_K, KEY_COMMA, KEY_L, KEY_PERIOD }; while (!WindowShouldClose()) { bool play = false; string chord = ""; for (int i = 0; i < NUM_KEYS; i++) { if (IsKeyDown(keys[i])) { play = true; chord += notes[i] + " - "; // 按键刚刚按下时触发包络的keydown if (!keysDown[i]) { keysDown[i] = true; envelopes[i].keydown(elapsedTime); notesActive[i] = true; } } else { // 按键刚刚松开时触发包络的keyup if (keysDown[i]) { keysDown[i] = false; envelopes[i].keyup(elapsedTime); } } } // 绘制UI BeginDrawing(); ClearBackground(BLACK); if (!chord.empty()) { chord.pop_back(); chord.pop_back(); chord.pop_back(); // 移除最后的" - " } Vector2 txtDim = MeasureTextEx(GetFontDefault(), chord.c_str(), 32, 0); DrawText(chord.c_str(), 400 - (txtDim.x / 2), 225 - (txtDim.y / 2), 32, RAYWHITE); EndDrawing(); } CloseAudioDevice(); CloseWindow(); return 0; }
四、修复后的效果
- 每个音符拥有独立的ADSR包络,按下时自然触发Attack->Decay->Sustain,松开时进入Release阶段,直到振幅降到0才停止发声,不会直接切断。
- 多音符同时按下、松开时,每个音符的衰减过程互不干扰,更贴近真实合成器的表现。
- 优化了ADSR参数的默认值,让声音过渡更自然。
你可以把这些修改应用到代码里,如果还有细节问题(比如波形类型调整、音量过载优化),随时再提出来~




