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"
2
3#include "absl/strings/str_format.h"
6#include "app/emu/emulator.h"
7#include "app/gui/icons.h"
8#include "app/gui/input.h"
9#include "imgui/imgui.h"
10#include "util/log.h"
11
12namespace yaze {
13namespace editor {
14
16
17absl::Status MusicEditor::Load() {
18 gfx::ScopedTimer timer("MusicEditor::Load");
19 return absl::OkStatus();
20}
21
22absl::Status MusicEditor::Update() {
23 if (ImGui::BeginTable("MusicEditorColumns", 2, music_editor_flags_,
24 ImVec2(0, 0))) {
25 ImGui::TableSetupColumn("Assembly");
26 ImGui::TableSetupColumn("Composition");
27 ImGui::TableHeadersRow();
28 ImGui::TableNextRow();
29
30 ImGui::TableNextColumn();
32
33 ImGui::TableNextColumn();
35 // TODO: Add music channel view
36 ImGui::Text("Music channels coming soon...");
37
38 ImGui::EndTable();
39 }
40
41 return absl::OkStatus();
42}
43
44static const int NUM_KEYS = 25;
45static bool keys[NUM_KEYS];
46
47static void DrawPianoStaff() {
48 if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9);
49 ImGui::BeginChild(child_id, ImVec2(0, 170), false)) {
50 const int NUM_LINES = 5;
51 const int LINE_THICKNESS = 2;
52 const int LINE_SPACING = 40;
53
54 // Get the draw list for the current window
55 ImDrawList* draw_list = ImGui::GetWindowDrawList();
56
57 // Draw the staff lines
58 ImVec2 canvas_p0 =
59 ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y);
60 ImVec2 canvas_p1 = ImVec2(canvas_p0.x + ImGui::GetContentRegionAvail().x,
61 canvas_p0.y + ImGui::GetContentRegionAvail().y);
62 draw_list->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(32, 32, 32, 255));
63 for (int i = 0; i < NUM_LINES; i++) {
64 auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING);
65 auto line_end = ImVec2(canvas_p1.x + ImGui::GetContentRegionAvail().x,
66 canvas_p0.y + i * LINE_SPACING);
67 draw_list->AddLine(line_start, line_end, IM_COL32(200, 200, 200, 255),
68 LINE_THICKNESS);
69 }
70
71 // Draw the ledger lines
72 const int NUM_LEDGER_LINES = 3;
73 for (int i = -NUM_LEDGER_LINES; i <= NUM_LINES + NUM_LEDGER_LINES; i++) {
74 if (i % 2 == 0) continue; // skip every other line
75 auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING / 2);
76 auto line_end = ImVec2(canvas_p1.x + ImGui::GetContentRegionAvail().x,
77 canvas_p0.y + i * LINE_SPACING / 2);
78 draw_list->AddLine(line_start, line_end, IM_COL32(150, 150, 150, 255),
79 LINE_THICKNESS);
80 }
81 }
82 ImGui::EndChild();
83}
84
85static void DrawPianoRoll() {
86 // Render the piano roll
87 float key_width = ImGui::GetContentRegionAvail().x / NUM_KEYS;
88 float white_key_height = ImGui::GetContentRegionAvail().y * 0.8f;
89 float black_key_height = ImGui::GetContentRegionAvail().y * 0.5f;
90 ImGui::Text("Piano Roll");
91 ImGui::Separator();
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(200, 200, 200, 255));
100
101 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.f, 0.f));
102 ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.f);
103 for (int i = 0; i < NUM_KEYS; i++) {
104 // Calculate the position and size of the key
105 ImVec2 key_pos = ImVec2(i * key_width, 0.0f);
106 ImVec2 key_size;
107 ImVec4 key_color;
108 ImVec4 text_color;
109 if (i % 12 == 1 || i % 12 == 3 || i % 12 == 6 || i % 12 == 8 ||
110 i % 12 == 10) {
111 // This is a black key
112 key_size = ImVec2(key_width * 0.6f, black_key_height);
113 key_color = ImVec4(0, 0, 0, 255);
114 text_color = ImVec4(255, 255, 255, 255);
115 } else {
116 // This is a white key
117 key_size = ImVec2(key_width, white_key_height);
118 key_color = ImVec4(255, 255, 255, 255);
119 text_color = ImVec4(0, 0, 0, 255);
120 }
121
122 ImGui::PushID(i);
123 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f));
124 ImGui::PushStyleColor(ImGuiCol_Button, key_color);
125 ImGui::PushStyleColor(ImGuiCol_Text, text_color);
126 if (ImGui::Button(kSongNotes[i].data(), key_size)) {
127 keys[i] ^= 1;
128 }
129 ImGui::PopStyleColor();
130 ImGui::PopStyleColor();
131 ImGui::PopStyleVar();
132
133 ImVec2 button_pos = ImGui::GetItemRectMin();
134 ImVec2 button_size = ImGui::GetItemRectSize();
135 if (keys[i]) {
136 ImVec2 dest;
137 dest.x = button_pos.x + button_size.x;
138 dest.y = button_pos.y + button_size.y;
139 ImGui::GetWindowDrawList()->AddRectFilled(button_pos, dest,
140 IM_COL32(200, 200, 255, 200));
141 }
142 ImGui::PopID();
143 ImGui::SameLine();
144 }
145 ImGui::PopStyleVar();
146 ImGui::PopStyleVar();
147}
148
150 static bool is_playing = false;
151 static int selected_option = 0;
152 static int current_volume = 0;
153 static bool has_loaded_song = false;
154 const int MAX_VOLUME = 100;
155
156 if (is_playing && !has_loaded_song) {
157 has_loaded_song = true;
158 }
159
160 gui::ItemLabel("Select a song to edit: ", gui::ItemLabelFlags::Left);
161 ImGui::Combo("#songs_in_game", &selected_option, kGameSongs, 30);
162
163 gui::ItemLabel("Controls: ", gui::ItemLabelFlags::Left);
164 if (ImGui::BeginTable("SongToolset", 6, toolset_table_flags_, ImVec2(0, 0))) {
165 ImGui::TableSetupColumn("#play");
166 ImGui::TableSetupColumn("#rewind");
167 ImGui::TableSetupColumn("#fastforward");
168 ImGui::TableSetupColumn("#volume");
169 ImGui::TableSetupColumn("#debug");
170
171 ImGui::TableSetupColumn("#slider");
172
173 ImGui::TableNextColumn();
174 if (ImGui::Button(is_playing ? ICON_MD_STOP : ICON_MD_PLAY_ARROW)) {
175 if (is_playing) {
176 has_loaded_song = false;
177 }
178 is_playing = !is_playing;
179 }
180
181 ImGui::TableNextColumn();
182 if (ImGui::Button(ICON_MD_FAST_REWIND)) {
183 // Handle rewind button click
184 }
185
186 ImGui::TableNextColumn();
187 if (ImGui::Button(ICON_MD_FAST_FORWARD)) {
188 // Handle fast forward button click
189 }
190
191 ImGui::TableNextColumn();
192 if (ImGui::Button(ICON_MD_VOLUME_UP)) {
193 // Handle volume up button click
194 }
195
196 if (ImGui::Button(ICON_MD_ACCESS_TIME)) {
198 }
199 ImGui::TableNextColumn();
200 ImGui::SliderInt("Volume", &current_volume, 0, 100);
201 ImGui::EndTable();
202 }
203
204 const int SONG_DURATION = 120; // duration of the song in seconds
205 static int current_time = 0; // current time in the song in seconds
206
207 // Display the current time in the song
208 gui::ItemLabel("Current Time: ", gui::ItemLabelFlags::Left);
209 ImGui::Text("%d:%02d", current_time / 60, current_time % 60);
210 ImGui::SameLine();
211 // Display the song duration/progress using a progress bar
212 ImGui::ProgressBar((float)current_time / SONG_DURATION);
213}
214
215// ============================================================================
216// Audio Control Methods (Emulator Integration)
217// ============================================================================
218
219void MusicEditor::PlaySong(int song_id) {
220 if (!emulator_) {
221 LOG_WARN("MusicEditor", "No emulator instance - cannot play song");
222 return;
223 }
224
225 if (!emulator_->snes().running()) {
226 LOG_WARN("MusicEditor", "Emulator not running - cannot play song");
227 return;
228 }
229
230 // Write song request to game memory ($7E012C)
231 // This triggers the NMI handler to send the song to APU
232 try {
233 emulator_->snes().Write(0x7E012C, static_cast<uint8_t>(song_id));
234 LOG_INFO("MusicEditor", "Requested song %d (%s)", song_id,
235 song_id < 30 ? kGameSongs[song_id] : "Unknown");
236
237 // Ensure audio backend is playing
238 if (auto* audio = emulator_->audio_backend()) {
239 auto status = audio->GetStatus();
240 if (!status.is_playing) {
241 audio->Play();
242 LOG_INFO("MusicEditor", "Started audio backend playback");
243 }
244 }
245
246 is_playing_ = true;
247 } catch (const std::exception& e) {
248 LOG_ERROR("MusicEditor", "Failed to play song: %s", e.what());
249 }
250}
251
253 if (!emulator_) return;
254
255 // Write stop command to game memory
256 try {
257 emulator_->snes().Write(0x7E012C, 0xFF); // 0xFF = stop music
258 LOG_INFO("MusicEditor", "Stopped music playback");
259
260 // Optional: pause audio backend to save CPU
261 if (auto* audio = emulator_->audio_backend()) {
262 audio->Pause();
263 }
264
265 is_playing_ = false;
266 } catch (const std::exception& e) {
267 LOG_ERROR("MusicEditor", "Failed to stop song: %s", e.what());
268 }
269}
270
271void MusicEditor::SetVolume(float volume) {
272 if (!emulator_) return;
273
274 // Clamp volume to valid range
275 volume = std::clamp(volume, 0.0f, 1.0f);
276
277 if (auto* audio = emulator_->audio_backend()) {
278 audio->SetVolume(volume);
279 LOG_DEBUG("MusicEditor", "Set volume to %.2f", volume);
280 } else {
281 LOG_WARN("MusicEditor", "No audio backend available");
282 }
283}
284
285} // namespace editor
286} // namespace yaze
zelda3::music::Tracker music_tracker_
emu::Emulator * emulator_
void SetVolume(float volume)
void Initialize() override
absl::Status Load() override
absl::Status Update() override
ImGuiTableFlags music_editor_flags_
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.
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_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_STOP
Definition icons.h:1860
#define ICON_MD_FAST_REWIND
Definition icons.h:723
#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.