yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
music_editor.cc
Go to the documentation of this file.
1#include "music_editor.h"
3
4#include "absl/strings/str_format.h"
7#include "app/emu/emulator.h"
10#include "imgui/imgui.h"
11#include "util/log.h"
12
13namespace yaze {
14namespace editor {
15
17 if (!dependencies_.card_registry) return;
18 auto* card_registry = dependencies_.card_registry;
19
20 card_registry->RegisterCard({.card_id = "music.tracker", .display_name = "Music Tracker",
21 .icon = ICON_MD_MUSIC_NOTE, .category = "Music",
22 .shortcut_hint = "Ctrl+Shift+M", .priority = 10});
23 card_registry->RegisterCard({.card_id = "music.instrument_editor", .display_name = "Instrument Editor",
24 .icon = ICON_MD_PIANO, .category = "Music",
25 .shortcut_hint = "Ctrl+Shift+I", .priority = 20});
26 card_registry->RegisterCard({.card_id = "music.assembly", .display_name = "Assembly View",
27 .icon = ICON_MD_CODE, .category = "Music",
28 .shortcut_hint = "Ctrl+Shift+A", .priority = 30});
29
30 // Show tracker by default
31 card_registry->ShowCard("music.tracker");
32}
33
34absl::Status MusicEditor::Load() {
35 gfx::ScopedTimer timer("MusicEditor::Load");
36 return absl::OkStatus();
37}
38
39absl::Status MusicEditor::Update() {
40 if (!dependencies_.card_registry) return absl::OkStatus();
41 auto* card_registry = dependencies_.card_registry;
42
43 static gui::EditorCard tracker_card("Music Tracker", ICON_MD_MUSIC_NOTE);
44 static gui::EditorCard instrument_card("Instrument Editor", ICON_MD_PIANO);
45 static gui::EditorCard assembly_card("Assembly View", ICON_MD_CODE);
46
47 tracker_card.SetDefaultSize(900, 700);
48 instrument_card.SetDefaultSize(600, 500);
49 assembly_card.SetDefaultSize(700, 600);
50
51 // Music Tracker Card - Check visibility flag exists and is true before rendering
52 bool* tracker_visible = card_registry->GetVisibilityFlag("music.tracker");
53 if (tracker_visible && *tracker_visible) {
54 if (tracker_card.Begin(tracker_visible)) {
56 }
57 tracker_card.End();
58 }
59
60 // Instrument Editor Card - Check visibility flag exists and is true before rendering
61 bool* instrument_visible = card_registry->GetVisibilityFlag("music.instrument_editor");
62 if (instrument_visible && *instrument_visible) {
63 if (instrument_card.Begin(instrument_visible)) {
65 }
66 instrument_card.End();
67 }
68
69 // Assembly View Card - Check visibility flag exists and is true before rendering
70 bool* assembly_visible = card_registry->GetVisibilityFlag("music.assembly");
71 if (assembly_visible && *assembly_visible) {
72 if (assembly_card.Begin(assembly_visible)) {
74 }
75 assembly_card.End();
76 }
77
78 return absl::OkStatus();
79}
80
81static const int NUM_KEYS = 25;
82static bool keys[NUM_KEYS];
83
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;
90
91 // Get the draw list for the current window
92 ImDrawList* draw_list = ImGui::GetWindowDrawList();
93
94 // Draw the staff lines
95 ImVec2 canvas_p0 =
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),
105 LINE_THICKNESS);
106 }
107
108 // Draw the ledger lines
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; // skip every other line
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),
116 LINE_THICKNESS);
117 }
118 }
119 ImGui::EndChild();
120}
121
122static void DrawPianoRoll() {
123 // Render the piano roll
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");
128 ImGui::Separator();
129 ImDrawList* draw_list = ImGui::GetWindowDrawList();
130
131 // Draw the staff lines
132 ImVec2 canvas_p0 =
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));
137
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++) {
141 // Calculate the position and size of the key
142 ImVec2 key_pos = ImVec2(i * key_width, 0.0f);
143 ImVec2 key_size;
144 ImVec4 key_color;
145 ImVec4 text_color;
146 if (i % 12 == 1 || i % 12 == 3 || i % 12 == 6 || i % 12 == 8 ||
147 i % 12 == 10) {
148 // This is a black key
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);
152 } else {
153 // This is a white key
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);
157 }
158
159 ImGui::PushID(i);
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)) {
164 keys[i] ^= 1;
165 }
166 ImGui::PopStyleColor();
167 ImGui::PopStyleColor();
168 ImGui::PopStyleVar();
169
170 ImVec2 button_pos = ImGui::GetItemRectMin();
171 ImVec2 button_size = ImGui::GetItemRectSize();
172 if (keys[i]) {
173 ImVec2 dest;
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));
178 }
179 ImGui::PopID();
180 ImGui::SameLine();
181 }
182 ImGui::PopStyleVar();
183 ImGui::PopStyleVar();
184}
185
187 DrawToolset();
188 DrawPianoRoll();
189 DrawPianoStaff();
190 // TODO: Add music channel view
191 ImGui::Text("Music channels coming soon...");
192}
193
195 ImGui::Text("Instrument Editor");
196 ImGui::Separator();
197 // TODO: Implement instrument editor UI
198 ImGui::Text("Coming soon...");
199}
200
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;
207
208 if (is_playing && !has_loaded_song) {
209 has_loaded_song = true;
210 }
211
212 gui::ItemLabel("Select a song to edit: ", gui::ItemLabelFlags::Left);
213 ImGui::Combo("#songs_in_game", &selected_option, kGameSongs, 30);
214
215 gui::ItemLabel("Controls: ", gui::ItemLabelFlags::Left);
216 if (ImGui::BeginTable("SongToolset", 6, toolset_table_flags_, ImVec2(0, 0))) {
217 ImGui::TableSetupColumn("#play");
218 ImGui::TableSetupColumn("#rewind");
219 ImGui::TableSetupColumn("#fastforward");
220 ImGui::TableSetupColumn("#volume");
221 ImGui::TableSetupColumn("#debug");
222
223 ImGui::TableSetupColumn("#slider");
224
225 ImGui::TableNextColumn();
226 if (ImGui::Button(is_playing ? ICON_MD_STOP : ICON_MD_PLAY_ARROW)) {
227 if (is_playing) {
228 has_loaded_song = false;
229 }
230 is_playing = !is_playing;
231 }
232
233 ImGui::TableNextColumn();
234 if (ImGui::Button(ICON_MD_FAST_REWIND)) {
235 // Handle rewind button click
236 }
237
238 ImGui::TableNextColumn();
239 if (ImGui::Button(ICON_MD_FAST_FORWARD)) {
240 // Handle fast forward button click
241 }
242
243 ImGui::TableNextColumn();
244 if (ImGui::Button(ICON_MD_VOLUME_UP)) {
245 // Handle volume up button click
246 }
247
248 if (ImGui::Button(ICON_MD_ACCESS_TIME)) {
250 }
251 ImGui::TableNextColumn();
252 ImGui::SliderInt("Volume", &current_volume, 0, 100);
253 ImGui::EndTable();
254 }
255
256 const int SONG_DURATION = 120; // duration of the song in seconds
257 static int current_time = 0; // current time in the song in seconds
258
259 // Display the current time in the song
260 gui::ItemLabel("Current Time: ", gui::ItemLabelFlags::Left);
261 ImGui::Text("%d:%02d", current_time / 60, current_time % 60);
262 ImGui::SameLine();
263 // Display the song duration/progress using a progress bar
264 ImGui::ProgressBar((float)current_time / SONG_DURATION);
265}
266
267// ============================================================================
268// Audio Control Methods (Emulator Integration)
269// ============================================================================
270
271void MusicEditor::PlaySong(int song_id) {
272 if (!emulator_) {
273 LOG_WARN("MusicEditor", "No emulator instance - cannot play song");
274 return;
275 }
276
277 if (!emulator_->snes().running()) {
278 LOG_WARN("MusicEditor", "Emulator not running - cannot play song");
279 return;
280 }
281
282 // Write song request to game memory ($7E012C)
283 // This triggers the NMI handler to send the song to APU
284 try {
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");
288
289 // Ensure audio backend is playing
290 if (auto* audio = emulator_->audio_backend()) {
291 auto status = audio->GetStatus();
292 if (!status.is_playing) {
293 audio->Play();
294 LOG_INFO("MusicEditor", "Started audio backend playback");
295 }
296 }
297
298 is_playing_ = true;
299 } catch (const std::exception& e) {
300 LOG_ERROR("MusicEditor", "Failed to play song: %s", e.what());
301 }
302}
303
305 if (!emulator_) return;
306
307 // Write stop command to game memory
308 try {
309 emulator_->snes().Write(0x7E012C, 0xFF); // 0xFF = stop music
310 LOG_INFO("MusicEditor", "Stopped music playback");
311
312 // Optional: pause audio backend to save CPU
313 if (auto* audio = emulator_->audio_backend()) {
314 audio->Pause();
315 }
316
317 is_playing_ = false;
318 } catch (const std::exception& e) {
319 LOG_ERROR("MusicEditor", "Failed to stop song: %s", e.what());
320 }
321}
322
323void MusicEditor::SetVolume(float volume) {
324 if (!emulator_) return;
325
326 // Clamp volume to valid range
327 volume = std::clamp(volume, 0.0f, 1.0f);
328
329 if (auto* audio = emulator_->audio_backend()) {
330 audio->SetVolume(volume);
331 LOG_DEBUG("MusicEditor", "Set volume to %.2f", volume);
332 } else {
333 LOG_WARN("MusicEditor", "No audio backend available");
334 }
335}
336
337} // namespace editor
338} // namespace yaze
void RegisterCard(size_t session_id, const CardInfo &base_info)
Register a card for a specific session.
EditorDependencies dependencies_
Definition editor.h:165
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()
Definition emulator.h:52
auto snes() -> Snes &
Definition emulator.h:47
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...
Definition tracker.cc:431
#define ICON_MD_PIANO
Definition icons.h:1460
#define ICON_MD_VOLUME_UP
Definition icons.h:2109
#define ICON_MD_FAST_FORWARD
Definition icons.h:722
#define ICON_MD_PLAY_ARROW
Definition icons.h:1477
#define ICON_MD_CODE
Definition icons.h:432
#define ICON_MD_STOP
Definition icons.h:1860
#define ICON_MD_FAST_REWIND
Definition icons.h:723
#define ICON_MD_MUSIC_NOTE
Definition icons.h:1262
#define ICON_MD_ACCESS_TIME
Definition icons.h:71
#define LOG_DEBUG(category, format,...)
Definition log.h:104
#define LOG_ERROR(category, format,...)
Definition log.h:110
#define LOG_WARN(category, format,...)
Definition log.h:108
#define LOG_INFO(category, format,...)
Definition log.h:106
void ItemLabel(absl::string_view title, ItemLabelFlags flags)
Definition input.cc:268
Main namespace for the application.
Definition controller.cc:20
EditorCardRegistry * card_registry
Definition editor.h:80