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)");
98 ImGui::BeginDisabled();
101 if (state.is_playing && !state.is_paused) {
106 if (ImGui::IsItemHovered())
107 ImGui::SetTooltip(
"Pause (Space)");
108 }
else if (state.is_paused) {
113 if (ImGui::IsItemHovered())
114 ImGui::SetTooltip(
"Resume (Space)");
118 if (ImGui::IsItemHovered())
119 ImGui::SetTooltip(
"Play (Space)");
125 if (ImGui::IsItemHovered())
126 ImGui::SetTooltip(
"Stop (Escape)");
129 ImGui::EndDisabled();
134 if (state.is_playing && !state.is_paused) {
135 float t =
static_cast<float>(ImGui::GetTime() * 3.0);
136 float alpha = 0.5f + 0.5f * std::sin(t);
140 }
else if (state.is_paused) {
144 ImGui::Text(
"%s", song->name.c_str());
145 if (song->modified) {
150 ImGui::TextDisabled(
"No song selected");
154 if (state.is_playing || state.is_paused) {
156 float seconds = state.ticks_per_second > 0
157 ? state.current_tick / state.ticks_per_second
159 int mins =
static_cast<int>(seconds) / 60;
160 int secs =
static_cast<int>(seconds) % 60;
165 float right_offset = ImGui::GetWindowWidth() - 200;
166 if (right_offset > 200) {
167 ImGui::SameLine(right_offset);
171 ImGui::SetNextItemWidth(70);
172 float speed = state.playback_speed;
178 if (ImGui::IsItemHovered())
179 ImGui::SetTooltip(
"Playback speed (+/- keys)");
184 ImGui::SetNextItemWidth(60);
189 if (ImGui::IsItemHovered())
190 ImGui::SetTooltip(
"Volume");
198 ImGui::Text(
"Selected Song:");
204 ImGui::TextDisabled(
"| %zu segments", song->segments.size());
205 if (song->modified) {
217 if (state.is_playing || state.is_paused) {
221 if (song && !song->segments.empty()) {
222 uint32_t total_duration = 0;
223 for (
const auto& seg : song->segments) {
224 total_duration += seg.GetDuration();
229 ?
static_cast<float>(state.current_tick) / total_duration
231 progress = std::clamp(progress, 0.0f, 1.0f);
233 float current_seconds =
234 state.ticks_per_second > 0
235 ? state.current_tick / state.ticks_per_second
237 float total_seconds = state.ticks_per_second > 0
238 ? total_duration / state.ticks_per_second
241 int cur_min =
static_cast<int>(current_seconds) / 60;
242 int cur_sec =
static_cast<int>(current_seconds) % 60;
243 int tot_min =
static_cast<int>(total_seconds) / 60;
244 int tot_sec =
static_cast<int>(total_seconds) % 60;
246 ImGui::Text(
"%d:%02d / %d:%02d", cur_min, cur_sec, tot_min, tot_sec);
248 ImGui::ProgressBar(progress, ImVec2(-1, 0),
"");
251 ImGui::Text(
"Segment: %d | Tick: %u", state.current_segment_index + 1,
254 ImGui::TextDisabled(
"| %.1f ticks/sec | %.2fx speed",
255 state.ticks_per_second, state.playback_speed);
266 if (ImGui::IsItemHovered())
267 ImGui::SetTooltip(
"Open song in dedicated tracker window");
274 if (ImGui::IsItemHovered())
275 ImGui::SetTooltip(
"Open piano roll view for this song");
290 if (ImGui::Button(
"Snapshot")) {
295 ImGui::TextDisabled(
"(Freeze display to read values)");
306 auto now = std::chrono::steady_clock::now();
319 if (elapsed >= 0.5) {
321 int32_t queue_delta =
344 ImGui::TextColored(status_color,
367 ImGui::Text(
"APU Rate: %.2fx expected", rate_ratio);
368 if (rate_ratio > 1.1f) {
377 if (ImGui::TreeNode(
"DSP Buffer")) {
380 ImGui::Text(
"Sample Offset: %u / 2048", dsp.sample_offset);
381 ImGui::Text(
"Frame Boundary: %u", dsp.frame_boundary);
384 float fill = dsp.sample_offset / 2048.0f;
386 snprintf(overlay,
sizeof(overlay),
"%.1f%%", fill * 100.0f);
387 ImGui::ProgressBar(fill, ImVec2(-1, 0), overlay);
390 int32_t drift =
static_cast<int32_t
>(dsp.sample_offset) -
391 static_cast<int32_t
>(dsp.frame_boundary);
394 ImGui::TextColored(drift_color,
"Drift: %+d samples", drift);
396 ImGui::Text(
"Master Vol: L=%d R=%d", dsp.master_vol_l, dsp.master_vol_r);
407 if (dsp.echo_enabled) {
417 if (ImGui::TreeNode(
"Audio Queue")) {
421 if (audio.is_playing) {
429 ImGui::Text(
"Queued: %u frames (%u bytes)", audio.queued_frames,
431 ImGui::Text(
"Sample Rate: %d Hz", audio.sample_rate);
432 ImGui::Text(
"Backend: %s", audio.backend_name.c_str());
435 if (audio.has_underrun) {
441 float queue_level = audio.queued_frames / 6000.0f;
442 queue_level = std::clamp(queue_level, 0.0f, 1.0f);
447 ImGui::ProgressBar(queue_level, ImVec2(-1, 0),
"Queue Level");
454 if (ImGui::TreeNode(
"APU Timing")) {
457 ImGui::Text(
"Cycles: %llu",
static_cast<unsigned long long>(apu.cycles));
460 if (ImGui::BeginTable(
"Timers", 4, ImGuiTableFlags_Borders)) {
461 ImGui::TableSetupColumn(
"Timer");
462 ImGui::TableSetupColumn(
"Enabled");
463 ImGui::TableSetupColumn(
"Counter");
464 ImGui::TableSetupColumn(
"Target");
465 ImGui::TableHeadersRow();
468 ImGui::TableNextRow();
469 ImGui::TableNextColumn();
471 ImGui::TableNextColumn();
474 apu.timer0_enabled ?
"ON" :
"off");
475 ImGui::TableNextColumn();
476 ImGui::Text(
"%u", apu.timer0_counter);
477 ImGui::TableNextColumn();
478 ImGui::Text(
"%u", apu.timer0_target);
481 ImGui::TableNextRow();
482 ImGui::TableNextColumn();
484 ImGui::TableNextColumn();
487 apu.timer1_enabled ?
"ON" :
"off");
488 ImGui::TableNextColumn();
489 ImGui::Text(
"%u", apu.timer1_counter);
490 ImGui::TableNextColumn();
491 ImGui::Text(
"%u", apu.timer1_target);
494 ImGui::TableNextRow();
495 ImGui::TableNextColumn();
497 ImGui::TableNextColumn();
500 apu.timer2_enabled ?
"ON" :
"off");
501 ImGui::TableNextColumn();
502 ImGui::Text(
"%u", apu.timer2_counter);
503 ImGui::TableNextColumn();
504 ImGui::Text(
"%u", apu.timer2_target);
510 ImGui::Text(
"Ports IN: [0]=%02X [1]=%02X", apu.port0_in, apu.port1_in);
511 ImGui::Text(
"Ports OUT: [0]=%02X [1]=%02X", apu.port0_out, apu.port1_out);
517 if (ImGui::TreeNode(
"Channels")) {
520 ImGui::Text(
"Key Status:");
522 for (
int i = 0; i < 8; i++) {
525 ImGui::TextColored(color,
"%d", i);
531 if (ImGui::BeginTable(
"ChannelDetails", 6,
532 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
533 ImGui::TableSetupColumn(
"Ch", ImGuiTableColumnFlags_WidthFixed, 25);
534 ImGui::TableSetupColumn(
"Key");
535 ImGui::TableSetupColumn(
"Sample");
536 ImGui::TableSetupColumn(
"Pitch");
537 ImGui::TableSetupColumn(
"Vol L/R");
538 ImGui::TableSetupColumn(
"ADSR");
539 ImGui::TableHeadersRow();
541 const char* adsr_names[] = {
"Atk",
"Dec",
"Sus",
"Rel"};
542 for (
int i = 0; i < 8; i++) {
543 ImGui::TableNextRow();
544 ImGui::TableNextColumn();
545 ImGui::Text(
"%d", i);
546 ImGui::TableNextColumn();
549 channels[i].key_on ?
"ON" :
"--");
550 ImGui::TableNextColumn();
551 ImGui::Text(
"%02X", channels[i].sample_index);
552 ImGui::TableNextColumn();
553 ImGui::Text(
"%04X", channels[i].pitch);
554 ImGui::TableNextColumn();
555 ImGui::Text(
"%02X/%02X", channels[i].volume_l, channels[i].volume_r);
556 ImGui::TableNextColumn();
557 int state = channels[i].adsr_state & 0x03;
558 ImGui::Text(
"%s", adsr_names[state]);
569 ImGui::Text(
"Actions:");
574 if (ImGui::IsItemHovered())
575 ImGui::SetTooltip(
"Clear SDL audio queue immediately");
581 if (ImGui::IsItemHovered())
582 ImGui::SetTooltip(
"Reset DSP sample ring buffer");
588 if (ImGui::IsItemHovered())
589 ImGui::SetTooltip(
"Force DSP NewFrame() call");
595 if (ImGui::IsItemHovered())
596 ImGui::SetTooltip(
"Full audio system reinitialization");
WindowContent 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_
Base interface for all logical window content components.
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.