@@ -270,26 +270,103 @@ void gui_draw(Synth *synth, SDL_Window *window, SDL_GLContext gl_context,
270270
271271 // ADSR Envelope
272272 if (ImGui::CollapsingHeader (" ADSR Envelope" , ImGuiTreeNodeFlags_DefaultOpen)) {
273- // Single column layout - Attack, Decay, Sustain, Release stacked vertically
273+ // 4x1 layout with envelope visualization
274+ ImGui::Columns (5 , " adsr_columns" , true ); // 4 parameters + 1 for envelope visualization
275+
276+ // Attack column
274277 ImGui::Text (" Attack" );
275278 if (ImGui::SliderFloat (" ##adsrattack" , &synth->adsr .attack , 0 .001f , 5 .0f , " %.3f s" , 0 )) {
276279 synth_set_param (synth, " adsr.attack" , synth->adsr .attack );
277280 }
281+ ImGui::NextColumn ();
278282
283+ // Decay column
279284 ImGui::Text (" Decay" );
280285 if (ImGui::SliderFloat (" ##adsrdecay" , &synth->adsr .decay , 0 .001f , 5 .0f , " %.3f s" , 0 )) {
281286 synth_set_param (synth, " adsr.decay" , synth->adsr .decay );
282287 }
288+ ImGui::NextColumn ();
283289
290+ // Sustain column
284291 ImGui::Text (" Sustain" );
285292 if (ImGui::SliderFloat (" ##adsrsustain" , &synth->adsr .sustain , 0 .0f , 1 .0f , " %.2f" , 0 )) {
286293 synth_set_param (synth, " adsr.sustain" , synth->adsr .sustain );
287294 }
295+ ImGui::NextColumn ();
288296
297+ // Release column
289298 ImGui::Text (" Release" );
290299 if (ImGui::SliderFloat (" ##adsrrelease" , &synth->adsr .release , 0 .001f , 10 .0f , " %.3f s" , 0 )) {
291300 synth_set_param (synth, " adsr.release" , synth->adsr .release );
292301 }
302+ ImGui::NextColumn ();
303+
304+ // Envelope visualization column
305+ ImGui::Text (" Envelope" );
306+ ImDrawList* draw_list = ImGui::GetWindowDrawList ();
307+ ImVec2 canvas_pos = ImGui::GetCursorScreenPos ();
308+ ImVec2 canvas_size = ImVec2 (120 , 80 ); // Fixed size for envelope display
309+
310+ // Draw background
311+ ImVec2 bg_end = ImVec2 (canvas_pos.x + canvas_size.x , canvas_pos.y + canvas_size.y );
312+ draw_list->AddRectFilled (canvas_pos, bg_end, IM_COL32 (20 , 25 , 35 , 255 ));
313+ draw_list->AddRect (canvas_pos, bg_end, IM_COL32 (100 , 100 , 120 , 255 ));
314+
315+ // Calculate envelope curve points
316+ const int num_points = 100 ;
317+ float total_time = synth->adsr .attack + synth->adsr .decay + synth->adsr .release + 1 .0f ; // Add 1 second for sustain phase
318+ float max_time = fmaxf (total_time, 2 .0f ); // At least 2 seconds for display
319+
320+ // Calculate envelope points and clip to canvas bounds
321+ ImVec2 points[num_points];
322+ int valid_points = 0 ;
323+
324+ for (int i = 0 ; i < num_points; i++) {
325+ float t = (float )i / (float )(num_points - 1 ) * max_time;
326+ float amplitude = 0 .0f ;
327+
328+ if (t < synth->adsr .attack ) {
329+ // Attack phase: linear ramp from 0 to 1
330+ amplitude = t / synth->adsr .attack ;
331+ } else if (t < synth->adsr .attack + synth->adsr .decay ) {
332+ // Decay phase: exponential decay from 1 to sustain
333+ float decay_progress = (t - synth->adsr .attack ) / synth->adsr .decay ;
334+ amplitude = 1 .0f - (1 .0f - synth->adsr .sustain ) * decay_progress;
335+ } else if (t < synth->adsr .attack + synth->adsr .decay + 1 .0f ) {
336+ // Sustain phase: constant
337+ amplitude = synth->adsr .sustain ;
338+ } else {
339+ // Release phase: exponential decay from sustain to 0
340+ float release_progress = (t - synth->adsr .attack - synth->adsr .decay - 1 .0f ) / synth->adsr .release ;
341+ amplitude = synth->adsr .sustain * (1 .0f - release_progress);
342+ }
343+
344+ // Calculate screen position and clip to canvas bounds
345+ float x = canvas_pos.x + (float )i / (float )(num_points - 1 ) * canvas_size.x ;
346+ float y = canvas_pos.y + canvas_size.y - amplitude * canvas_size.y ;
347+
348+ // Clamp coordinates to canvas bounds
349+ x = fmaxf (canvas_pos.x , fminf (x, canvas_pos.x + canvas_size.x ));
350+ y = fmaxf (canvas_pos.y , fminf (y, canvas_pos.y + canvas_size.y ));
351+
352+ points[i] = ImVec2 (x, y);
353+ valid_points++;
354+ }
355+
356+ // Draw clipped polyline using the calculated points
357+ if (valid_points > 1 ) {
358+ draw_list->AddPolyline (points, valid_points, IM_COL32 (100 , 255 , 100 , 255 ), 0 , 2 .0f );
359+ }
360+
361+ // Draw phase labels
362+ draw_list->AddText (ImVec2 (canvas_pos.x + 2 , canvas_pos.y + 2 ), IM_COL32 (180 , 180 , 200 , 255 ), " A" );
363+ draw_list->AddText (ImVec2 (canvas_pos.x + canvas_size.x / 4 + 2 , canvas_pos.y + canvas_size.y - 15 ), IM_COL32 (180 , 180 , 200 , 255 ), " D" );
364+ draw_list->AddText (ImVec2 (canvas_pos.x + canvas_size.x / 2 + 2 , canvas_pos.y + canvas_size.y - 15 ), IM_COL32 (180 , 180 , 200 , 255 ), " S" );
365+ draw_list->AddText (ImVec2 (canvas_pos.x + 3 * canvas_size.x / 4 + 2 , canvas_pos.y + canvas_size.y - 15 ), IM_COL32 (180 , 180 , 200 , 255 ), " R" );
366+
367+ ImGui::Dummy (canvas_size); // Reserve space for the drawing
368+
369+ ImGui::Columns (1 , " " , false );
293370
294371 // Preset buttons
295372 ImGui::Separator ();
0 commit comments