1#ifndef YAZE_APP_EDITOR_MUSIC_PANELS_MUSIC_PLAYBACK_CONTROL_PANEL_H_
2#define YAZE_APP_EDITOR_MUSIC_PANELS_MUSIC_PLAYBACK_CONTROL_PANEL_H_
19#include "imgui/imgui.h"
32 int* current_song_index,
42 std::string
GetId()
const override {
return "music.tracker"; }
64 void Draw(
bool* p_open)
override {
66 ImGui::TextDisabled(
"Music system not initialized");
81 ImGui::BulletText(
"Space: Play/Pause toggle");
82 ImGui::BulletText(
"Escape: Stop playback");
83 ImGui::BulletText(
"+/-: Increase/decrease speed");
84 ImGui::BulletText(
"Arrow keys: Navigate in tracker/piano roll");
85 ImGui::BulletText(
"Z,S,X,D,C,V,G,B,H,N,J,M: Piano keyboard (C to B)");
86 ImGui::BulletText(
"Ctrl+Wheel: Zoom (Piano Roll)");
97 if (!can_play) ImGui::BeginDisabled();
100 if (state.is_playing && !state.is_paused) {
104 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Pause (Space)");
105 }
else if (state.is_paused) {
109 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Resume (Space)");
113 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Play (Space)");
118 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Stop (Escape)");
120 if (!can_play) ImGui::EndDisabled();
125 if (state.is_playing && !state.is_paused) {
126 float t =
static_cast<float>(ImGui::GetTime() * 3.0);
127 float alpha = 0.5f + 0.5f * std::sin(t);
131 }
else if (state.is_paused) {
136 ImGui::Text(
"%s", song->name.c_str());
137 if (song->modified) {
142 ImGui::TextDisabled(
"No song selected");
146 if (state.is_playing || state.is_paused) {
148 float seconds = state.ticks_per_second > 0
149 ? state.current_tick / state.ticks_per_second
151 int mins =
static_cast<int>(seconds) / 60;
152 int secs =
static_cast<int>(seconds) % 60;
158 float right_offset = ImGui::GetWindowWidth() - 200;
159 if (right_offset > 200) {
160 ImGui::SameLine(right_offset);
164 ImGui::SetNextItemWidth(70);
165 float speed = state.playback_speed;
170 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Playback speed (+/- keys)");
175 ImGui::SetNextItemWidth(60);
179 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Volume");
187 ImGui::Text(
"Selected Song:");
193 ImGui::TextDisabled(
"| %zu segments", song->segments.size());
194 if (song->modified) {
207 if (state.is_playing || state.is_paused) {
211 if (song && !song->segments.empty()) {
212 uint32_t total_duration = 0;
213 for (
const auto& seg : song->segments) {
214 total_duration += seg.GetDuration();
217 float progress = (total_duration > 0)
218 ?
static_cast<float>(state.current_tick) /
221 progress = std::clamp(progress, 0.0f, 1.0f);
223 float current_seconds =
224 state.ticks_per_second > 0
225 ? state.current_tick / state.ticks_per_second
227 float total_seconds = state.ticks_per_second > 0
228 ? total_duration / state.ticks_per_second
231 int cur_min =
static_cast<int>(current_seconds) / 60;
232 int cur_sec =
static_cast<int>(current_seconds) % 60;
233 int tot_min =
static_cast<int>(total_seconds) / 60;
234 int tot_sec =
static_cast<int>(total_seconds) % 60;
236 ImGui::Text(
"%d:%02d / %d:%02d", cur_min, cur_sec, tot_min, tot_sec);
238 ImGui::ProgressBar(progress, ImVec2(-1, 0),
"");
241 ImGui::Text(
"Segment: %d | Tick: %u", state.current_segment_index + 1,
244 ImGui::TextDisabled(
"| %.1f ticks/sec | %.2fx speed",
245 state.ticks_per_second, state.playback_speed);
255 if (ImGui::IsItemHovered())
256 ImGui::SetTooltip(
"Open song in dedicated tracker window");
262 if (ImGui::IsItemHovered())
263 ImGui::SetTooltip(
"Open piano roll view for this song");
276 if (ImGui::Button(
"Snapshot")) {
281 ImGui::TextDisabled(
"(Freeze display to read values)");
292 auto now = std::chrono::steady_clock::now();
301 auto elapsed = std::chrono::duration<double>(now -
last_stats_time_).count();
304 if (elapsed >= 0.5) {
351 ImGui::Text(
"APU Rate: %.2fx expected", rate_ratio);
352 if (rate_ratio > 1.1f) {
355 "(APU running too fast!)");
362 if (ImGui::TreeNode(
"DSP Buffer")) {
365 ImGui::Text(
"Sample Offset: %u / 2048", dsp.sample_offset);
366 ImGui::Text(
"Frame Boundary: %u", dsp.frame_boundary);
369 float fill = dsp.sample_offset / 2048.0f;
371 snprintf(overlay,
sizeof(overlay),
"%.1f%%", fill * 100.0f);
372 ImGui::ProgressBar(fill, ImVec2(-1, 0), overlay);
375 int32_t drift =
static_cast<int32_t
>(dsp.sample_offset) -
376 static_cast<int32_t
>(dsp.frame_boundary);
377 ImVec4 drift_color = (std::abs(drift) > 100)
380 ImGui::TextColored(drift_color,
"Drift: %+d samples", drift);
382 ImGui::Text(
"Master Vol: L=%d R=%d", dsp.master_vol_l, dsp.master_vol_r);
393 if (dsp.echo_enabled) {
402 if (ImGui::TreeNode(
"Audio Queue")) {
406 if (audio.is_playing) {
414 ImGui::Text(
"Queued: %u frames (%u bytes)",
415 audio.queued_frames, audio.queued_bytes);
416 ImGui::Text(
"Sample Rate: %d Hz", audio.sample_rate);
417 ImGui::Text(
"Backend: %s", audio.backend_name.c_str());
420 if (audio.has_underrun) {
426 float queue_level = audio.queued_frames / 6000.0f;
427 queue_level = std::clamp(queue_level, 0.0f, 1.0f);
428 ImVec4 queue_color = (queue_level < 0.2f)
433 ImGui::ProgressBar(queue_level, ImVec2(-1, 0),
"Queue Level");
440 if (ImGui::TreeNode(
"APU Timing")) {
443 ImGui::Text(
"Cycles: %llu",
static_cast<unsigned long long>(apu.cycles));
446 if (ImGui::BeginTable(
"Timers", 4, ImGuiTableFlags_Borders)) {
447 ImGui::TableSetupColumn(
"Timer");
448 ImGui::TableSetupColumn(
"Enabled");
449 ImGui::TableSetupColumn(
"Counter");
450 ImGui::TableSetupColumn(
"Target");
451 ImGui::TableHeadersRow();
454 ImGui::TableNextRow();
455 ImGui::TableNextColumn(); ImGui::Text(
"T0");
456 ImGui::TableNextColumn();
459 apu.timer0_enabled ?
"ON" :
"off");
460 ImGui::TableNextColumn(); ImGui::Text(
"%u", apu.timer0_counter);
461 ImGui::TableNextColumn(); ImGui::Text(
"%u", apu.timer0_target);
464 ImGui::TableNextRow();
465 ImGui::TableNextColumn(); ImGui::Text(
"T1");
466 ImGui::TableNextColumn();
469 apu.timer1_enabled ?
"ON" :
"off");
470 ImGui::TableNextColumn(); ImGui::Text(
"%u", apu.timer1_counter);
471 ImGui::TableNextColumn(); ImGui::Text(
"%u", apu.timer1_target);
474 ImGui::TableNextRow();
475 ImGui::TableNextColumn(); ImGui::Text(
"T2");
476 ImGui::TableNextColumn();
479 apu.timer2_enabled ?
"ON" :
"off");
480 ImGui::TableNextColumn(); ImGui::Text(
"%u", apu.timer2_counter);
481 ImGui::TableNextColumn(); ImGui::Text(
"%u", apu.timer2_target);
487 ImGui::Text(
"Ports IN: [0]=%02X [1]=%02X", apu.port0_in, apu.port1_in);
488 ImGui::Text(
"Ports OUT: [0]=%02X [1]=%02X", apu.port0_out, apu.port1_out);
494 if (ImGui::TreeNode(
"Channels")) {
497 ImGui::Text(
"Key Status:");
499 for (
int i = 0; i < 8; i++) {
500 ImVec4 color = channels[i].key_on
503 ImGui::TextColored(color,
"%d", i);
504 if (i < 7) ImGui::SameLine();
508 if (ImGui::BeginTable(
"ChannelDetails", 6,
509 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
510 ImGui::TableSetupColumn(
"Ch", ImGuiTableColumnFlags_WidthFixed, 25);
511 ImGui::TableSetupColumn(
"Key");
512 ImGui::TableSetupColumn(
"Sample");
513 ImGui::TableSetupColumn(
"Pitch");
514 ImGui::TableSetupColumn(
"Vol L/R");
515 ImGui::TableSetupColumn(
"ADSR");
516 ImGui::TableHeadersRow();
518 const char* adsr_names[] = {
"Atk",
"Dec",
"Sus",
"Rel"};
519 for (
int i = 0; i < 8; i++) {
520 ImGui::TableNextRow();
521 ImGui::TableNextColumn(); ImGui::Text(
"%d", i);
522 ImGui::TableNextColumn();
525 channels[i].key_on ?
"ON" :
"--");
526 ImGui::TableNextColumn(); ImGui::Text(
"%02X", channels[i].sample_index);
527 ImGui::TableNextColumn(); ImGui::Text(
"%04X", channels[i].pitch);
528 ImGui::TableNextColumn();
529 ImGui::Text(
"%02X/%02X", channels[i].volume_l, channels[i].volume_r);
530 ImGui::TableNextColumn();
531 int state = channels[i].adsr_state & 0x03;
532 ImGui::Text(
"%s", adsr_names[state]);
543 ImGui::Text(
"Actions:");
548 if (ImGui::IsItemHovered())
549 ImGui::SetTooltip(
"Clear SDL audio queue immediately");
555 if (ImGui::IsItemHovered())
556 ImGui::SetTooltip(
"Reset DSP sample ring buffer");
562 if (ImGui::IsItemHovered())
563 ImGui::SetTooltip(
"Force DSP NewFrame() call");
569 if (ImGui::IsItemHovered())
570 ImGui::SetTooltip(
"Full audio system reinitialization");
Base interface for all logical panel components.
EditorPanel for music playback controls and status display.
void SetOnOpenPianoRoll(std::function< void(int)> callback)
uint64_t last_cycles_for_rate_
music::AudioQueueStatus cached_audio_
int GetPriority() const override
Get display priority for menu ordering.
std::string GetIcon() const override
Material Design icon for this panel.
zelda3::music::MusicBank * music_bank_
music::MusicPlayer * music_player_
music::ApuDebugStatus cached_apu_
uint32_t last_queued_for_rate_
std::string GetEditorCategory() const override
Editor category this panel belongs to.
void SetOnOpenSong(std::function< void(int)> callback)
int * current_song_index_
void Draw(bool *p_open) override
Draw the panel content.
std::chrono::steady_clock::time_point last_stats_time_
std::function< void(int)> on_open_song_
music::DspDebugStatus cached_dsp_
std::string GetId() const override
Unique identifier for this panel.
std::string GetDisplayName() const override
Human-readable name shown in menus and title bars.
MusicPlaybackControlPanel(zelda3::music::MusicBank *music_bank, int *current_song_index, music::MusicPlayer *music_player)
void DrawPlaybackStatus()
std::array< music::ChannelState, 8 > cached_channels_
std::function< void(int)> on_open_piano_roll_
Handles audio playback for the music editor using the SNES APU emulator.
ApuDebugStatus GetApuStatus() const
Get APU timing diagnostic status.
void Stop()
Stop playback completely.
void ReinitAudio()
Reinitialize the audio system.
void SetPlaybackSpeed(float speed)
Set the playback speed (0.25x to 2.0x).
void ForceNewFrame()
Force a DSP NewFrame() call.
void SetVolume(float volume)
Set the master volume (0.0 to 1.0).
void Pause()
Pause the current playback.
AudioQueueStatus GetAudioQueueStatus() const
Get audio queue diagnostic status.
std::array< ChannelState, 8 > GetChannelStates() const
void PlaySong(int song_index)
Start playing a song by index.
DspDebugStatus GetDspStatus() const
Get DSP buffer diagnostic status.
void Resume()
Resume paused playback.
bool IsAudioReady() const
Check if the audio system is ready for playback.
PlaybackState GetState() const
void ResetDspBuffer()
Reset the DSP sample buffer.
void ClearAudioQueue()
Clear the audio queue (stops sound immediately).
RAII guard for ImGui style colors.
Manages the collection of songs, instruments, and samples from a ROM.
MusicSong * GetSong(int index)
Get a song by index.
#define ICON_MD_PAUSE_CIRCLE
#define ICON_MD_VOLUME_UP
#define ICON_MD_TRENDING_DOWN
#define ICON_MD_TRENDING_UP
#define ICON_MD_PLAY_ARROW
#define ICON_MD_BUG_REPORT
#define ICON_MD_GRAPHIC_EQ
#define ICON_MD_STOP_CIRCLE
#define ICON_MD_CLEAR_ALL
#define ICON_MD_PLAY_CIRCLE
#define ICON_MD_SKIP_NEXT
#define ICON_MD_SURROUND_SOUND
#define ICON_MD_OPEN_IN_NEW
#define ICON_MD_VOLUME_OFF
#define ICON_MD_RESTART_ALT
#define ICON_MD_TRENDING_FLAT
bool SliderIntWheel(const char *label, int *v, int v_min, int v_max, const char *format, int wheel_step, ImGuiSliderFlags flags)
bool SliderFloatWheel(const char *label, float *v, float v_min, float v_max, const char *format, float wheel_step, ImGuiSliderFlags flags)
ButtonColorSet GetSuccessButtonColors()
ImVec4 GetDisabledColor()
APU timing diagnostic status for debug UI.
Audio queue diagnostic status for debug UI.
DSP buffer diagnostic status for debug UI.
Represents the current playback state of the music player.