4#include "absl/strings/str_format.h"
10#include "imgui/imgui.h"
20 card_registry->
RegisterCard({.card_id =
"music.tracker", .display_name =
"Music Tracker",
22 .shortcut_hint =
"Ctrl+Shift+M", .priority = 10});
23 card_registry->RegisterCard({.card_id =
"music.instrument_editor", .display_name =
"Instrument Editor",
25 .shortcut_hint =
"Ctrl+Shift+I", .priority = 20});
26 card_registry->RegisterCard({.card_id =
"music.assembly", .display_name =
"Assembly View",
28 .shortcut_hint =
"Ctrl+Shift+A", .priority = 30});
31 card_registry->ShowCard(
"music.tracker");
36 return absl::OkStatus();
52 bool* tracker_visible = card_registry->GetVisibilityFlag(
"music.tracker");
53 if (tracker_visible && *tracker_visible) {
54 if (tracker_card.
Begin(tracker_visible)) {
61 bool* instrument_visible = card_registry->GetVisibilityFlag(
"music.instrument_editor");
62 if (instrument_visible && *instrument_visible) {
63 if (instrument_card.
Begin(instrument_visible)) {
66 instrument_card.
End();
70 bool* assembly_visible = card_registry->GetVisibilityFlag(
"music.assembly");
71 if (assembly_visible && *assembly_visible) {
72 if (assembly_card.
Begin(assembly_visible)) {
78 return absl::OkStatus();
81static const int NUM_KEYS = 25;
82static bool keys[NUM_KEYS];
84static void DrawPianoStaff() {
85 if (ImGuiID child_id = ImGui::GetID((
void*)(intptr_t)9);
86 ImGui::BeginChild(child_id, ImVec2(0, 170),
false)) {
87 const int NUM_LINES = 5;
88 const int LINE_THICKNESS = 2;
89 const int LINE_SPACING = 40;
92 ImDrawList* draw_list = ImGui::GetWindowDrawList();
96 ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y);
97 ImVec2 canvas_p1 = ImVec2(canvas_p0.x + ImGui::GetContentRegionAvail().x,
98 canvas_p0.y + ImGui::GetContentRegionAvail().y);
99 draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(32, 32, 32, 255));
100 for (
int i = 0; i < NUM_LINES; i++) {
101 auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING);
102 auto line_end = ImVec2(canvas_p1.x + ImGui::GetContentRegionAvail().x,
103 canvas_p0.y + i * LINE_SPACING);
104 draw_list->AddLine(line_start, line_end, IM_COL32(200, 200, 200, 255),
109 const int NUM_LEDGER_LINES = 3;
110 for (
int i = -NUM_LEDGER_LINES; i <= NUM_LINES + NUM_LEDGER_LINES; i++) {
111 if (i % 2 == 0)
continue;
112 auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING / 2);
113 auto line_end = ImVec2(canvas_p1.x + ImGui::GetContentRegionAvail().x,
114 canvas_p0.y + i * LINE_SPACING / 2);
115 draw_list->AddLine(line_start, line_end, IM_COL32(150, 150, 150, 255),
122static void DrawPianoRoll() {
124 float key_width = ImGui::GetContentRegionAvail().x / NUM_KEYS;
125 float white_key_height = ImGui::GetContentRegionAvail().y * 0.8f;
126 float black_key_height = ImGui::GetContentRegionAvail().y * 0.5f;
127 ImGui::Text(
"Piano Roll");
129 ImDrawList* draw_list = ImGui::GetWindowDrawList();
133 ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y);
134 ImVec2 canvas_p1 = ImVec2(canvas_p0.x + ImGui::GetContentRegionAvail().x,
135 canvas_p0.y + ImGui::GetContentRegionAvail().y);
136 draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(200, 200, 200, 255));
138 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.f, 0.f));
139 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.f);
140 for (
int i = 0; i < NUM_KEYS; i++) {
142 ImVec2 key_pos = ImVec2(i * key_width, 0.0f);
146 if (i % 12 == 1 || i % 12 == 3 || i % 12 == 6 || i % 12 == 8 ||
149 key_size = ImVec2(key_width * 0.6f, black_key_height);
150 key_color = ImVec4(0, 0, 0, 255);
151 text_color = ImVec4(255, 255, 255, 255);
154 key_size = ImVec2(key_width, white_key_height);
155 key_color = ImVec4(255, 255, 255, 255);
156 text_color = ImVec4(0, 0, 0, 255);
160 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f));
161 ImGui::PushStyleColor(ImGuiCol_Button, key_color);
162 ImGui::PushStyleColor(ImGuiCol_Text, text_color);
163 if (ImGui::Button(kSongNotes[i].data(), key_size)) {
166 ImGui::PopStyleColor();
167 ImGui::PopStyleColor();
168 ImGui::PopStyleVar();
170 ImVec2 button_pos = ImGui::GetItemRectMin();
171 ImVec2 button_size = ImGui::GetItemRectSize();
174 dest.x = button_pos.x + button_size.x;
175 dest.y = button_pos.y + button_size.y;
176 ImGui::GetWindowDrawList()->AddRectFilled(button_pos, dest,
177 IM_COL32(200, 200, 255, 200));
182 ImGui::PopStyleVar();
183 ImGui::PopStyleVar();
191 ImGui::Text(
"Music channels coming soon...");
195 ImGui::Text(
"Instrument Editor");
198 ImGui::Text(
"Coming soon...");
202 static bool is_playing =
false;
203 static int selected_option = 0;
204 static int current_volume = 0;
205 static bool has_loaded_song =
false;
206 const int MAX_VOLUME = 100;
208 if (is_playing && !has_loaded_song) {
209 has_loaded_song =
true;
212 gui::ItemLabel(
"Select a song to edit: ", gui::ItemLabelFlags::Left);
213 ImGui::Combo(
"#songs_in_game", &selected_option, kGameSongs, 30);
217 ImGui::TableSetupColumn(
"#play");
218 ImGui::TableSetupColumn(
"#rewind");
219 ImGui::TableSetupColumn(
"#fastforward");
220 ImGui::TableSetupColumn(
"#volume");
221 ImGui::TableSetupColumn(
"#debug");
223 ImGui::TableSetupColumn(
"#slider");
225 ImGui::TableNextColumn();
228 has_loaded_song =
false;
230 is_playing = !is_playing;
233 ImGui::TableNextColumn();
238 ImGui::TableNextColumn();
243 ImGui::TableNextColumn();
251 ImGui::TableNextColumn();
252 ImGui::SliderInt(
"Volume", ¤t_volume, 0, 100);
256 const int SONG_DURATION = 120;
257 static int current_time = 0;
261 ImGui::Text(
"%d:%02d", current_time / 60, current_time % 60);
264 ImGui::ProgressBar((
float)current_time / SONG_DURATION);
273 LOG_WARN(
"MusicEditor",
"No emulator instance - cannot play song");
278 LOG_WARN(
"MusicEditor",
"Emulator not running - cannot play song");
285 emulator_->
snes().Write(0x7E012C,
static_cast<uint8_t
>(song_id));
286 LOG_INFO(
"MusicEditor",
"Requested song %d (%s)", song_id,
287 song_id < 30 ? kGameSongs[song_id] :
"Unknown");
291 auto status = audio->GetStatus();
292 if (!status.is_playing) {
294 LOG_INFO(
"MusicEditor",
"Started audio backend playback");
299 }
catch (
const std::exception& e) {
300 LOG_ERROR(
"MusicEditor",
"Failed to play song: %s", e.what());
310 LOG_INFO(
"MusicEditor",
"Stopped music playback");
318 }
catch (
const std::exception& e) {
319 LOG_ERROR(
"MusicEditor",
"Failed to stop song: %s", e.what());
327 volume = std::clamp(volume, 0.0f, 1.0f);
330 audio->SetVolume(volume);
331 LOG_DEBUG(
"MusicEditor",
"Set volume to %.2f", volume);
333 LOG_WARN(
"MusicEditor",
"No audio backend available");
void RegisterCard(size_t session_id, const CardInfo &base_info)
Register a card for a specific session.
EditorDependencies dependencies_
void DrawInstrumentEditor()
zelda3::music::Tracker music_tracker_
emu::Emulator * emulator_
void SetVolume(float volume)
void Initialize() override
absl::Status Load() override
absl::Status Update() override
void PlaySong(int song_id)
ImGuiTableFlags toolset_table_flags_
AssemblyEditor assembly_editor_
audio::IAudioBackend * audio_backend()
RAII timer for automatic timing management.
Draggable, dockable card for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
void LoadSongs(Rom &rom)
High-level function to load all song data from the ROM. (Currently commented out, but this would be t...
#define ICON_MD_VOLUME_UP
#define ICON_MD_FAST_FORWARD
#define ICON_MD_PLAY_ARROW
#define ICON_MD_FAST_REWIND
#define ICON_MD_MUSIC_NOTE
#define ICON_MD_ACCESS_TIME
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
void ItemLabel(absl::string_view title, ItemLabelFlags flags)
Main namespace for the application.
EditorCardRegistry * card_registry