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"; }
68 void Draw(
bool* p_open)
override {
70 ImGui::TextDisabled(tr(
"Music system not initialized"));
85 ImGui::BulletText(tr(
"Space: Play/Pause toggle"));
86 ImGui::BulletText(tr(
"Escape: Stop playback"));
87 ImGui::BulletText(tr(
"+/-: Increase/decrease speed"));
88 ImGui::BulletText(tr(
"Arrow keys: Navigate in tracker/piano roll"));
89 ImGui::BulletText(tr(
"Z,S,X,D,C,V,G,B,H,N,J,M: Piano keyboard (C to B)"));
90 ImGui::BulletText(tr(
"Ctrl+Wheel: Zoom (Piano Roll)"));
102 ImGui::BeginDisabled();
105 if (state.is_playing && !state.is_paused) {
110 if (ImGui::IsItemHovered())
111 ImGui::SetTooltip(tr(
"Pause (Space)"));
112 }
else if (state.is_paused) {
117 if (ImGui::IsItemHovered())
118 ImGui::SetTooltip(tr(
"Resume (Space)"));
122 if (ImGui::IsItemHovered())
123 ImGui::SetTooltip(tr(
"Play (Space)"));
129 if (ImGui::IsItemHovered())
130 ImGui::SetTooltip(tr(
"Stop (Escape)"));
133 ImGui::EndDisabled();
138 if (state.is_playing && !state.is_paused) {
139 float t =
static_cast<float>(ImGui::GetTime() * 3.0);
140 float alpha = 0.5f + 0.5f * std::sin(t);
144 }
else if (state.is_paused) {
148 ImGui::Text(
"%s", song->name.c_str());
149 if (song->modified) {
154 ImGui::TextDisabled(tr(
"No song selected"));
158 if (state.is_playing || state.is_paused) {
160 float seconds = state.ticks_per_second > 0
161 ? state.current_tick / state.ticks_per_second
163 int mins =
static_cast<int>(seconds) / 60;
164 int secs =
static_cast<int>(seconds) % 60;
169 float right_offset = ImGui::GetWindowWidth() - 200;
170 if (right_offset > 200) {
171 ImGui::SameLine(right_offset);
175 ImGui::SetNextItemWidth(70);
176 float speed = state.playback_speed;
182 if (ImGui::IsItemHovered())
183 ImGui::SetTooltip(tr(
"Playback speed (+/- keys)"));
188 ImGui::SetNextItemWidth(60);
193 if (ImGui::IsItemHovered())
194 ImGui::SetTooltip(tr(
"Volume"));
202 ImGui::Text(tr(
"Selected Song:"));
208 ImGui::TextDisabled(tr(
"| %zu segments"), song->segments.size());
209 if (song->modified) {
221 if (state.is_playing || state.is_paused) {
225 if (song && !song->segments.empty()) {
226 uint32_t total_duration = 0;
227 for (
const auto& seg : song->segments) {
228 total_duration += seg.GetDuration();
233 ?
static_cast<float>(state.current_tick) / total_duration
235 progress = std::clamp(progress, 0.0f, 1.0f);
237 float current_seconds =
238 state.ticks_per_second > 0
239 ? state.current_tick / state.ticks_per_second
241 float total_seconds = state.ticks_per_second > 0
242 ? total_duration / state.ticks_per_second
245 int cur_min =
static_cast<int>(current_seconds) / 60;
246 int cur_sec =
static_cast<int>(current_seconds) % 60;
247 int tot_min =
static_cast<int>(total_seconds) / 60;
248 int tot_sec =
static_cast<int>(total_seconds) % 60;
250 ImGui::Text(
"%d:%02d / %d:%02d", cur_min, cur_sec, tot_min, tot_sec);
252 ImGui::ProgressBar(progress, ImVec2(-1, 0),
"");
255 ImGui::Text(tr(
"Segment: %d | Tick: %u"), state.current_segment_index + 1,
258 ImGui::TextDisabled(tr(
"| %.1f ticks/sec | %.2fx speed"),
259 state.ticks_per_second, state.playback_speed);
270 if (ImGui::IsItemHovered())
271 ImGui::SetTooltip(tr(
"Open song in dedicated tracker window"));
278 if (ImGui::IsItemHovered())
279 ImGui::SetTooltip(tr(
"Open piano roll view for this song"));
294 if (ImGui::Button(tr(
"Snapshot"))) {
299 ImGui::TextDisabled(tr(
"(Freeze display to read values)"));
310 auto now = std::chrono::steady_clock::now();
323 if (elapsed >= 0.5) {
325 int32_t queue_delta =
348 ImGui::TextColored(status_color,
371 ImGui::Text(tr(
"APU Rate: %.2fx expected"), rate_ratio);
372 if (rate_ratio > 1.1f) {
381 if (ImGui::TreeNode(
"DSP Buffer")) {
384 ImGui::Text(tr(
"Sample Offset: %u / 2048"), dsp.sample_offset);
385 ImGui::Text(tr(
"Frame Boundary: %u"), dsp.frame_boundary);
388 float fill = dsp.sample_offset / 2048.0f;
390 snprintf(overlay,
sizeof(overlay),
"%.1f%%", fill * 100.0f);
391 ImGui::ProgressBar(fill, ImVec2(-1, 0), overlay);
394 int32_t drift =
static_cast<int32_t
>(dsp.sample_offset) -
395 static_cast<int32_t
>(dsp.frame_boundary);
398 ImGui::TextColored(drift_color, tr(
"Drift: %+d samples"), drift);
400 ImGui::Text(tr(
"Master Vol: L=%d R=%d"), dsp.master_vol_l,
412 if (dsp.echo_enabled) {
422 if (ImGui::TreeNode(
"Audio Queue")) {
426 if (audio.is_playing) {
434 ImGui::Text(tr(
"Queued: %u frames (%u bytes)"), audio.queued_frames,
436 ImGui::Text(tr(
"Sample Rate: %d Hz"), audio.sample_rate);
437 ImGui::Text(tr(
"Backend: %s"), audio.backend_name.c_str());
440 if (audio.has_underrun) {
446 float queue_level = audio.queued_frames / 6000.0f;
447 queue_level = std::clamp(queue_level, 0.0f, 1.0f);
452 ImGui::ProgressBar(queue_level, ImVec2(-1, 0),
"Queue Level");
459 if (ImGui::TreeNode(
"APU Timing")) {
462 ImGui::Text(tr(
"Cycles: %llu"),
463 static_cast<unsigned long long>(apu.cycles));
466 if (ImGui::BeginTable(
"Timers", 4, ImGuiTableFlags_Borders)) {
467 ImGui::TableSetupColumn(
"Timer");
468 ImGui::TableSetupColumn(
"Enabled");
469 ImGui::TableSetupColumn(
"Counter");
470 ImGui::TableSetupColumn(
"Target");
471 ImGui::TableHeadersRow();
474 ImGui::TableNextRow();
475 ImGui::TableNextColumn();
476 ImGui::Text(tr(
"T0"));
477 ImGui::TableNextColumn();
480 apu.timer0_enabled ?
"ON" :
"off");
481 ImGui::TableNextColumn();
482 ImGui::Text(
"%u", apu.timer0_counter);
483 ImGui::TableNextColumn();
484 ImGui::Text(
"%u", apu.timer0_target);
487 ImGui::TableNextRow();
488 ImGui::TableNextColumn();
489 ImGui::Text(tr(
"T1"));
490 ImGui::TableNextColumn();
493 apu.timer1_enabled ?
"ON" :
"off");
494 ImGui::TableNextColumn();
495 ImGui::Text(
"%u", apu.timer1_counter);
496 ImGui::TableNextColumn();
497 ImGui::Text(
"%u", apu.timer1_target);
500 ImGui::TableNextRow();
501 ImGui::TableNextColumn();
502 ImGui::Text(tr(
"T2"));
503 ImGui::TableNextColumn();
506 apu.timer2_enabled ?
"ON" :
"off");
507 ImGui::TableNextColumn();
508 ImGui::Text(
"%u", apu.timer2_counter);
509 ImGui::TableNextColumn();
510 ImGui::Text(
"%u", apu.timer2_target);
516 ImGui::Text(tr(
"Ports IN: [0]=%02X [1]=%02X"), apu.port0_in,
518 ImGui::Text(tr(
"Ports OUT: [0]=%02X [1]=%02X"), apu.port0_out,
525 if (ImGui::TreeNode(
"Channels")) {
528 ImGui::Text(tr(
"Key Status:"));
530 for (
int i = 0; i < 8; i++) {
533 ImGui::TextColored(color,
"%d", i);
539 if (ImGui::BeginTable(
"ChannelDetails", 6,
540 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
541 ImGui::TableSetupColumn(
"Ch", ImGuiTableColumnFlags_WidthFixed, 25);
542 ImGui::TableSetupColumn(
"Key");
543 ImGui::TableSetupColumn(
"Sample");
544 ImGui::TableSetupColumn(
"Pitch");
545 ImGui::TableSetupColumn(
"Vol L/R");
546 ImGui::TableSetupColumn(
"ADSR");
547 ImGui::TableHeadersRow();
549 const char* adsr_names[] = {
"Atk",
"Dec",
"Sus",
"Rel"};
550 for (
int i = 0; i < 8; i++) {
551 ImGui::TableNextRow();
552 ImGui::TableNextColumn();
553 ImGui::Text(
"%d", i);
554 ImGui::TableNextColumn();
557 channels[i].key_on ?
"ON" :
"--");
558 ImGui::TableNextColumn();
559 ImGui::Text(
"%02X", channels[i].sample_index);
560 ImGui::TableNextColumn();
561 ImGui::Text(
"%04X", channels[i].pitch);
562 ImGui::TableNextColumn();
563 ImGui::Text(
"%02X/%02X", channels[i].volume_l, channels[i].volume_r);
564 ImGui::TableNextColumn();
565 int state = channels[i].adsr_state & 0x03;
566 ImGui::Text(
"%s", adsr_names[state]);
577 ImGui::Text(tr(
"Actions:"));
582 if (ImGui::IsItemHovered())
583 ImGui::SetTooltip(tr(
"Clear SDL audio queue immediately"));
589 if (ImGui::IsItemHovered())
590 ImGui::SetTooltip(tr(
"Reset DSP sample ring buffer"));
596 if (ImGui::IsItemHovered())
597 ImGui::SetTooltip(tr(
"Force DSP NewFrame() call"));
603 if (ImGui::IsItemHovered())
604 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.
WindowPresentationPolicy GetPresentationPolicy() const override
How this content is hosted and advertised by workspace chrome.
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()
static WindowPresentationPolicy OptionalPopOut()
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.