1#ifndef YAZE_APP_EDITOR_MUSIC_PANELS_MUSIC_PLAYBACK_CONTROL_PANEL_H_
2#define YAZE_APP_EDITOR_MUSIC_PANELS_MUSIC_PLAYBACK_CONTROL_PANEL_H_
20#include "imgui/imgui.h"
33 int* current_song_index,
43 std::string
GetId()
const override {
return "music.tracker"; }
65 void Draw(
bool* p_open)
override {
67 ImGui::TextDisabled(tr(
"Music system not initialized"));
82 ImGui::BulletText(tr(
"Space: Play/Pause toggle"));
83 ImGui::BulletText(tr(
"Escape: Stop playback"));
84 ImGui::BulletText(tr(
"+/-: Increase/decrease speed"));
85 ImGui::BulletText(tr(
"Arrow keys: Navigate in tracker/piano roll"));
86 ImGui::BulletText(tr(
"Z,S,X,D,C,V,G,B,H,N,J,M: Piano keyboard (C to B)"));
87 ImGui::BulletText(tr(
"Ctrl+Wheel: Zoom (Piano Roll)"));
99 ImGui::BeginDisabled();
102 if (state.is_playing && !state.is_paused) {
107 if (ImGui::IsItemHovered())
108 ImGui::SetTooltip(tr(
"Pause (Space)"));
109 }
else if (state.is_paused) {
114 if (ImGui::IsItemHovered())
115 ImGui::SetTooltip(tr(
"Resume (Space)"));
119 if (ImGui::IsItemHovered())
120 ImGui::SetTooltip(tr(
"Play (Space)"));
126 if (ImGui::IsItemHovered())
127 ImGui::SetTooltip(tr(
"Stop (Escape)"));
130 ImGui::EndDisabled();
135 if (state.is_playing && !state.is_paused) {
136 float t =
static_cast<float>(ImGui::GetTime() * 3.0);
137 float alpha = 0.5f + 0.5f * std::sin(t);
141 }
else if (state.is_paused) {
145 ImGui::Text(
"%s", song->name.c_str());
146 if (song->modified) {
151 ImGui::TextDisabled(tr(
"No song selected"));
155 if (state.is_playing || state.is_paused) {
157 float seconds = state.ticks_per_second > 0
158 ? state.current_tick / state.ticks_per_second
160 int mins =
static_cast<int>(seconds) / 60;
161 int secs =
static_cast<int>(seconds) % 60;
166 float right_offset = ImGui::GetWindowWidth() - 200;
167 if (right_offset > 200) {
168 ImGui::SameLine(right_offset);
172 ImGui::SetNextItemWidth(70);
173 float speed = state.playback_speed;
179 if (ImGui::IsItemHovered())
180 ImGui::SetTooltip(tr(
"Playback speed (+/- keys)"));
185 ImGui::SetNextItemWidth(60);
190 if (ImGui::IsItemHovered())
191 ImGui::SetTooltip(tr(
"Volume"));
199 ImGui::Text(tr(
"Selected Song:"));
205 ImGui::TextDisabled(tr(
"| %zu segments"), song->segments.size());
206 if (song->modified) {
218 if (state.is_playing || state.is_paused) {
222 if (song && !song->segments.empty()) {
223 uint32_t total_duration = 0;
224 for (
const auto& seg : song->segments) {
225 total_duration += seg.GetDuration();
230 ?
static_cast<float>(state.current_tick) / total_duration
232 progress = std::clamp(progress, 0.0f, 1.0f);
234 float current_seconds =
235 state.ticks_per_second > 0
236 ? state.current_tick / state.ticks_per_second
238 float total_seconds = state.ticks_per_second > 0
239 ? total_duration / state.ticks_per_second
242 int cur_min =
static_cast<int>(current_seconds) / 60;
243 int cur_sec =
static_cast<int>(current_seconds) % 60;
244 int tot_min =
static_cast<int>(total_seconds) / 60;
245 int tot_sec =
static_cast<int>(total_seconds) % 60;
247 ImGui::Text(
"%d:%02d / %d:%02d", cur_min, cur_sec, tot_min, tot_sec);
249 ImGui::ProgressBar(progress, ImVec2(-1, 0),
"");
252 ImGui::Text(tr(
"Segment: %d | Tick: %u"), state.current_segment_index + 1,
255 ImGui::TextDisabled(tr(
"| %.1f ticks/sec | %.2fx speed"),
256 state.ticks_per_second, state.playback_speed);
267 if (ImGui::IsItemHovered())
268 ImGui::SetTooltip(tr(
"Open song in dedicated tracker window"));
275 if (ImGui::IsItemHovered())
276 ImGui::SetTooltip(tr(
"Open piano roll view for this song"));
291 if (ImGui::Button(tr(
"Snapshot"))) {
296 ImGui::TextDisabled(tr(
"(Freeze display to read values)"));
307 auto now = std::chrono::steady_clock::now();
320 if (elapsed >= 0.5) {
322 int32_t queue_delta =
345 ImGui::TextColored(status_color,
368 ImGui::Text(tr(
"APU Rate: %.2fx expected"), rate_ratio);
369 if (rate_ratio > 1.1f) {
378 if (ImGui::TreeNode(
"DSP Buffer")) {
381 ImGui::Text(tr(
"Sample Offset: %u / 2048"), dsp.sample_offset);
382 ImGui::Text(tr(
"Frame Boundary: %u"), dsp.frame_boundary);
385 float fill = dsp.sample_offset / 2048.0f;
387 snprintf(overlay,
sizeof(overlay),
"%.1f%%", fill * 100.0f);
388 ImGui::ProgressBar(fill, ImVec2(-1, 0), overlay);
391 int32_t drift =
static_cast<int32_t
>(dsp.sample_offset) -
392 static_cast<int32_t
>(dsp.frame_boundary);
395 ImGui::TextColored(drift_color, tr(
"Drift: %+d samples"), drift);
397 ImGui::Text(tr(
"Master Vol: L=%d R=%d"), dsp.master_vol_l,
409 if (dsp.echo_enabled) {
419 if (ImGui::TreeNode(
"Audio Queue")) {
423 if (audio.is_playing) {
431 ImGui::Text(tr(
"Queued: %u frames (%u bytes)"), audio.queued_frames,
433 ImGui::Text(tr(
"Sample Rate: %d Hz"), audio.sample_rate);
434 ImGui::Text(tr(
"Backend: %s"), audio.backend_name.c_str());
437 if (audio.has_underrun) {
443 float queue_level = audio.queued_frames / 6000.0f;
444 queue_level = std::clamp(queue_level, 0.0f, 1.0f);
449 ImGui::ProgressBar(queue_level, ImVec2(-1, 0),
"Queue Level");
456 if (ImGui::TreeNode(
"APU Timing")) {
459 ImGui::Text(tr(
"Cycles: %llu"),
460 static_cast<unsigned long long>(apu.cycles));
463 if (ImGui::BeginTable(
"Timers", 4, ImGuiTableFlags_Borders)) {
464 ImGui::TableSetupColumn(
"Timer");
465 ImGui::TableSetupColumn(
"Enabled");
466 ImGui::TableSetupColumn(
"Counter");
467 ImGui::TableSetupColumn(
"Target");
468 ImGui::TableHeadersRow();
471 ImGui::TableNextRow();
472 ImGui::TableNextColumn();
473 ImGui::Text(tr(
"T0"));
474 ImGui::TableNextColumn();
477 apu.timer0_enabled ?
"ON" :
"off");
478 ImGui::TableNextColumn();
479 ImGui::Text(
"%u", apu.timer0_counter);
480 ImGui::TableNextColumn();
481 ImGui::Text(
"%u", apu.timer0_target);
484 ImGui::TableNextRow();
485 ImGui::TableNextColumn();
486 ImGui::Text(tr(
"T1"));
487 ImGui::TableNextColumn();
490 apu.timer1_enabled ?
"ON" :
"off");
491 ImGui::TableNextColumn();
492 ImGui::Text(
"%u", apu.timer1_counter);
493 ImGui::TableNextColumn();
494 ImGui::Text(
"%u", apu.timer1_target);
497 ImGui::TableNextRow();
498 ImGui::TableNextColumn();
499 ImGui::Text(tr(
"T2"));
500 ImGui::TableNextColumn();
503 apu.timer2_enabled ?
"ON" :
"off");
504 ImGui::TableNextColumn();
505 ImGui::Text(
"%u", apu.timer2_counter);
506 ImGui::TableNextColumn();
507 ImGui::Text(
"%u", apu.timer2_target);
513 ImGui::Text(tr(
"Ports IN: [0]=%02X [1]=%02X"), apu.port0_in,
515 ImGui::Text(tr(
"Ports OUT: [0]=%02X [1]=%02X"), apu.port0_out,
522 if (ImGui::TreeNode(
"Channels")) {
525 ImGui::Text(tr(
"Key Status:"));
527 for (
int i = 0; i < 8; i++) {
530 ImGui::TextColored(color,
"%d", i);
536 if (ImGui::BeginTable(
"ChannelDetails", 6,
537 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
538 ImGui::TableSetupColumn(
"Ch", ImGuiTableColumnFlags_WidthFixed, 25);
539 ImGui::TableSetupColumn(
"Key");
540 ImGui::TableSetupColumn(
"Sample");
541 ImGui::TableSetupColumn(
"Pitch");
542 ImGui::TableSetupColumn(
"Vol L/R");
543 ImGui::TableSetupColumn(
"ADSR");
544 ImGui::TableHeadersRow();
546 const char* adsr_names[] = {
"Atk",
"Dec",
"Sus",
"Rel"};
547 for (
int i = 0; i < 8; i++) {
548 ImGui::TableNextRow();
549 ImGui::TableNextColumn();
550 ImGui::Text(
"%d", i);
551 ImGui::TableNextColumn();
554 channels[i].key_on ?
"ON" :
"--");
555 ImGui::TableNextColumn();
556 ImGui::Text(
"%02X", channels[i].sample_index);
557 ImGui::TableNextColumn();
558 ImGui::Text(
"%04X", channels[i].pitch);
559 ImGui::TableNextColumn();
560 ImGui::Text(
"%02X/%02X", channels[i].volume_l, channels[i].volume_r);
561 ImGui::TableNextColumn();
562 int state = channels[i].adsr_state & 0x03;
563 ImGui::Text(
"%s", adsr_names[state]);
574 ImGui::Text(tr(
"Actions:"));
579 if (ImGui::IsItemHovered())
580 ImGui::SetTooltip(tr(
"Clear SDL audio queue immediately"));
586 if (ImGui::IsItemHovered())
587 ImGui::SetTooltip(tr(
"Reset DSP sample ring buffer"));
593 if (ImGui::IsItemHovered())
594 ImGui::SetTooltip(tr(
"Force DSP NewFrame() call"));
600 if (ImGui::IsItemHovered())
601 ImGui::SetTooltip(tr(
"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.