From deaeedfc877b44c9a2379d10b26a3e24b0335493 Mon Sep 17 00:00:00 2001 From: scawful Date: Fri, 30 Dec 2022 16:40:58 -0600 Subject: [PATCH] add music editor gui elements --- assets/asm/template_song.asm | 160 +++++++++++++++++++++++++++++ src/CMakeLists.txt | 116 ++++++++++++--------- src/app/editor/music_editor.cc | 177 +++++++++++++++++++++++++++++++++ src/app/editor/music_editor.h | 69 +++++++++++++ 4 files changed, 473 insertions(+), 49 deletions(-) create mode 100644 assets/asm/template_song.asm create mode 100644 src/app/editor/music_editor.cc create mode 100644 src/app/editor/music_editor.h diff --git a/assets/asm/template_song.asm b/assets/asm/template_song.asm new file mode 100644 index 00000000..01c33d20 --- /dev/null +++ b/assets/asm/template_song.asm @@ -0,0 +1,160 @@ +; +; Credit to Zarby89 +; +lorom + +!End = $00 +!Rest = $C9 +!Tie = $C8 + +macro SetChannelVolume(v) +db $ED, +endmacro + +macro SetMasterVolume(v) +db $E5, +endmacro + +macro SetTempo(v) +db $E7, +endmacro + +macro SetInstrument(v) +db $E0, +endmacro + +macro CallSubroutine(addr, repeat) +db $EF +dw +db +endmacro + +;1/4 = $48 +;1/4 double = $6C +;1/4 triplet = $30 +;1/8 = $24 +;1/8 double = $36 +;1/8 triplet = $18 +;1/16 = $12 +;1/16 double = $1B +;1/32 = $09 +; To make a whole note you tie 4 1/4 so something like +;%SetDuration(48) +;db !C4, !Tie, !Tie, !Tie ; will play a whole note (1/1) +;db !C4, !Tie ; will play a half note (1/2) + +macro SetDuration(v) +db , $7F +endmacro + + +!C1 = $80 +!C1s = $81 +!D1 = $82 +!D1s = $83 +!E1 = $84 +!F1 = $85 +!F1s = $86 +!G1 = $87 +!G1s = $88 +!A1 = $89 +!A1s = $8A +!B1 = $8B + + +!C2 = $8C +!C2s = $8D +!D2 = $8E +!D2s = $8F +!E2 = $90 +!F2 = $91 +!F2s = $92 +!G2 = $93 +!G2s = $94 +!A2 = $95 +!A2s = $96 +!B2 = $97 + + +!C3 = $98 +!C3s = $99 +!D3 = $9A +!D3s = $9B +!E3 = $9C +!F3 = $9D +!F3s = $9E +!G3 = $9F +!G3s = $A0 +!A3 = $A1 +!A3s = $A2 +!B3 = $A3 + +!C4 = $A4 +!C4s = $A5 +!D4 = $A6 +!D4s = $A7 +!E4 = $A8 +!F4 = $A9 +!F4s = $AA +!G4 = $AB +!G4s = $AC +!A4 = $AD +!A4s = $AE +!B4 = $AF + +!C5 = $B0 +!C5s = $B1 +!D5 = $B2 +!D5s = $B3 +!E5 = $B4 +!F5 = $B5 +!F5s = $B6 +!G5 = $B7 +!G5s = $B8 +!A5 = $B9 +!A5s = $BA +!B5 = $BB + +!C6 = $BC +!C6s = $BD +!D6 = $BE +!D6s = $BF +!E6 = $C0 +!F6 = $C1 +!F6s = $C2 +!G6 = $C3 +!G6s = $C4 +!A6 = $C5 +!A6s = $C6 +!B6 = $C7 + +org $1A9FF8 ; Hyrule Castle (Song Header information) +Sections: +!ARAMAddr = $D0FF +!StartingAddr = Sections +dw !ARAMAddr+$0A +dw !ARAMAddr+$0A +dw $00FF +dw !ARAMAddr +dw $0000 + +Channels: +!ARAMC = !ARAMAddr-Sections +dw Channel0+!ARAMC +dw $0000 +dw $0000 +dw $0000 +dw $0000 +dw $0000 +dw $0000 +dw $0000 + + +Channel0: +SetMasterVolume($80) +SetTempo($40) +SetInstrument($17) + +db !Rest, !Rest, !Rest + +db !End \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f7de2660..65d7709f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,7 @@ set( app/editor/assembly_editor.cc app/editor/dungeon_editor.cc app/editor/master_editor.cc + app/editor/music_editor.cc app/editor/overworld_editor.cc app/editor/palette_editor.cc app/editor/screen_editor.cc @@ -45,6 +46,8 @@ set( gui/color.cc ) +# executable creation --------------------------------------------------------- + add_executable( yaze app/yaze.cc @@ -59,6 +62,8 @@ add_executable( ${ASAR_STATIC_SRC} ) +# including libraries --------------------------------------------------------- + target_include_directories( yaze PUBLIC lib/ @@ -78,6 +83,8 @@ if(WIN32 OR MINGW) add_definitions(-DSDL_MAIN_HANDLED) endif() +# linking libraries ----------------------------------------------------------- + target_link_libraries( yaze PUBLIC ${ABSL_TARGETS} @@ -99,7 +106,17 @@ if (UNIX) target_compile_definitions(yaze PRIVATE "stricmp=strcasecmp") endif() - +if(MACOS) + set(MACOSX_BUNDLE_ICON_FILE ${CMAKE_SOURCE_DIR}/yaze.ico) + set_target_properties(yaze + PROPERTIES + BUNDLE True + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" + # MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/yaze.plist.in + ) +else() set_target_properties(yaze PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" @@ -107,56 +124,57 @@ set_target_properties(yaze RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/yaze.res" ) +endif() -add_subdirectory(app/delta) +# add_subdirectory(app/delta) -add_executable( - yaze_delta - app/delta/delta.cc - app/delta/viewer.cc - app/delta/service.cc - app/delta/client.cc - app/rom.cc - ${YAZE_APP_ASM_SRC} - ${YAZE_APP_CORE_SRC} - ${YAZE_APP_EDITOR_SRC} - ${YAZE_APP_GFX_SRC} - ${YAZE_APP_ZELDA3_SRC} - ${YAZE_GUI_SRC} - ${IMGUI_SRC} - ${ASAR_STATIC_SRC} -) +# add_executable( +# yaze_delta +# app/delta/delta.cc +# app/delta/viewer.cc +# app/delta/service.cc +# app/delta/client.cc +# app/rom.cc +# ${YAZE_APP_ASM_SRC} +# ${YAZE_APP_CORE_SRC} +# ${YAZE_APP_EDITOR_SRC} +# ${YAZE_APP_GFX_SRC} +# ${YAZE_APP_ZELDA3_SRC} +# ${YAZE_GUI_SRC} +# ${IMGUI_SRC} +# ${ASAR_STATIC_SRC} +# ) -target_include_directories( - yaze_delta PUBLIC - lib/ - app/ - lib/asar/src/ - ${ASAR_INCLUDE_DIR} - ${CMAKE_SOURCE_DIR}/src/ - ${PNG_INCLUDE_DIRS} - ${SDL2_INCLUDE_DIR} - ${GLEW_INCLUDE_DIRS} -) +# target_include_directories( +# yaze_delta PUBLIC +# lib/ +# app/ +# lib/asar/src/ +# ${ASAR_INCLUDE_DIR} +# ${CMAKE_SOURCE_DIR}/src/ +# ${PNG_INCLUDE_DIRS} +# ${SDL2_INCLUDE_DIR} +# ${GLEW_INCLUDE_DIRS} +# ) -target_link_libraries( - yaze_delta PUBLIC - ${ABSL_TARGETS} - ${SDL_TARGETS} - ${PNG_LIBRARIES} - ${GLEW_LIBRARIES} - ${OPENGL_LIBRARIES} - ${CMAKE_DL_LIBS} - delta-service - asar-static - ImGui -) -target_compile_definitions(yaze_delta PRIVATE "linux") -target_compile_definitions(yaze_delta PRIVATE "stricmp=strcasecmp") +# target_link_libraries( +# yaze_delta PUBLIC +# ${ABSL_TARGETS} +# ${SDL_TARGETS} +# ${PNG_LIBRARIES} +# ${GLEW_LIBRARIES} +# ${OPENGL_LIBRARIES} +# ${CMAKE_DL_LIBS} +# delta-service +# asar-static +# ImGui +# ) +# target_compile_definitions(yaze_delta PRIVATE "linux") +# target_compile_definitions(yaze_delta PRIVATE "stricmp=strcasecmp") -set (source "${CMAKE_SOURCE_DIR}/assets") -set (destination "${CMAKE_CURRENT_BINARY_DIR}/assets") -add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${source} ${destination} - DEPENDS ${destination} - COMMENT "symbolic link resources folder from ${source} => ${destination}") \ No newline at end of file +# set (source "${CMAKE_SOURCE_DIR}/assets") +# set (destination "${CMAKE_CURRENT_BINARY_DIR}/assets") +# add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD +# COMMAND ${CMAKE_COMMAND} -E create_symlink ${source} ${destination} +# DEPENDS ${destination} +# COMMENT "symbolic link resources folder from ${source} => ${destination}") \ No newline at end of file diff --git a/src/app/editor/music_editor.cc b/src/app/editor/music_editor.cc new file mode 100644 index 00000000..2f82d797 --- /dev/null +++ b/src/app/editor/music_editor.cc @@ -0,0 +1,177 @@ +#include "music_editor.h" + +#include "gui/icons.h" +#include "imgui.h" + +namespace yaze { +namespace app { +namespace editor { + +void MusicEditor::Update() { + ImGui::Text("Preview:"); + DrawToolset(); + + if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9); + ImGui::BeginChild(child_id, ImVec2(0, 90), false)) { + DrawPianoStaff(); + } + ImGui::EndChild(); + + ImGui::Separator(); + if (ImGui::BeginTable("MusicEditorColumns", 2, toolset_table_flags_, + ImVec2(0, 0))) { + ImGui::TableSetupColumn("#SongList"); + ImGui::TableSetupColumn("#EditorArea"); + + ImGui::TableNextColumn(); + DrawSongList(); + + ImGui::TableNextColumn(); + assembly_editor_.InlineUpdate(); + DrawPianoRoll(); + + ImGui::EndTable(); + } +} + +static const int NUM_KEYS = 25; +static bool keys[NUM_KEYS]; + +void MusicEditor::DrawPianoStaff() { + const int NUM_LINES = 5; + const int LINE_THICKNESS = 1; + const int LINE_SPACING = 20; + + // Get the draw list for the current window + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // Draw the staff lines + ImVec2 canvas_p0 = + ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y); + for (int i = 0; i < NUM_LINES; i++) { + auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING); + auto line_end = ImVec2(ImGui::GetContentRegionAvail().x, canvas_p0.y + i * LINE_SPACING); + draw_list->AddLine(line_start, line_end, IM_COL32(255, 255, 255, 255), + LINE_THICKNESS); + } + + // Draw the ledger lines + const int NUM_LEDGER_LINES = 3; + for (int i = -NUM_LEDGER_LINES; i <= NUM_LINES + NUM_LEDGER_LINES; i++) { + if (i % 2 == 0) continue; // skip every other line + auto line_start = ImVec2(canvas_p0.x, canvas_p0.y + i * LINE_SPACING / 2); + auto line_end = + ImVec2(ImGui::GetContentRegionAvail().x, canvas_p0.y + i * LINE_SPACING / 2); + draw_list->AddLine(line_start, line_end, IM_COL32(150, 150, 150, 255), + LINE_THICKNESS); + } +} + +void MusicEditor::DrawPianoRoll() { + // Render the piano roll + float key_width = ImGui::GetContentRegionAvail().x / NUM_KEYS; + float white_key_height = ImGui::GetContentRegionAvail().y * 0.8f; + float black_key_height = ImGui::GetContentRegionAvail().y * 0.5f; + ImGui::Text("Click on the keys to toggle notes"); + ImGui::Separator(); + // Get the draw list for the current window + // ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + // draw_list->AddRectFilled(ImVec2(0, 0), ImGui::GetContentRegionAvail(), + // IM_COL32(35, 35, 35, 255)); + // // Iterate through the keys and draw them + // for (int i = 0; i < NUM_KEYS; i++) { + // // Calculate the position and size of the key + // ImVec2 key_pos = ImVec2(i * key_width, 0.0f); + // ImVec2 key_size; + + // if (i % 12 == 1 || i % 12 == 3 || i % 12 == 6 || i % 12 == 8 || + // i % 12 == 10) { + // // This is a black key + // key_size = ImVec2(key_width * 0.6f, black_key_height); + // ImVec2 dest; + // dest.x = key_pos.x + key_size.x; + // dest.y = key_pos.y + key_size.y; + // draw_list->AddRectFilled(key_pos, dest, IM_COL32(0, 0, 0, 255)); + // } else { + // // This is a white key + // ImVec2 dest; + // dest.x = key_pos.x + key_size.x; + // dest.y = key_pos.y + key_size.y; + // key_size = ImVec2(key_width, white_key_height); + // draw_list->AddRectFilled(key_pos, dest, IM_COL32(255, 255, 255, + // 255)); + // } + // } + + for (int i = 0; i < NUM_KEYS; i++) { + // Calculate the position and size of the key + ImVec2 key_pos = ImVec2(i * key_width, 0.0f); + ImVec2 key_size; + if (i % 12 == 1 || i % 12 == 3 || i % 12 == 6 || i % 12 == 8 || + i % 12 == 10) { + // This is a black key + key_size = ImVec2(key_width * 0.6f, black_key_height); + } else { + // This is a white key + key_size = ImVec2(key_width, white_key_height); + } + + ImGui::PushID(i); + + if (ImGui::Button(kSongNotes[i].data(), key_size)) { + keys[i] ^= 1; + } + ImVec2 button_pos = ImGui::GetItemRectMin(); + ImVec2 button_size = ImGui::GetItemRectSize(); + if (keys[i]) { + ImVec2 dest; + dest.x = button_pos.x + button_size.x; + dest.y = button_pos.y + button_size.y; + ImGui::GetWindowDrawList()->AddRectFilled(button_pos, dest, + IM_COL32(200, 200, 255, 255)); + } + ImGui::PopID(); + ImGui::SameLine(); + } +} + +void MusicEditor::DrawSongList() const { + static int current_song = 0; + ImGui::BeginChild("Song List", ImVec2(250, 0)); + ImGui::Text("Select a song to edit:"); + ImGui::ListBox( + "##songs", ¤t_song, + [](void* data, int idx, const char** out_text) { + *out_text = kGameSongs[idx].data(); + return true; + }, + nullptr, 30, 30); + ImGui::EndChild(); +} + +void MusicEditor::DrawToolset() { + static bool is_playing = false; + if (ImGui::BeginTable("DWToolset", 3, toolset_table_flags_, ImVec2(0, 0))) { + ImGui::TableSetupColumn("#play"); + ImGui::TableSetupColumn("#rewind"); + ImGui::TableSetupColumn("#fastforward"); + + ImGui::TableNextColumn(); + if (ImGui::Button(is_playing ? ICON_MD_STOP : ICON_MD_PLAY_ARROW)) { + is_playing = !is_playing; + } + + ImGui::TableNextColumn(); + ImGui::Button(ICON_MD_FAST_REWIND); + + ImGui::TableNextColumn(); + ImGui::Button(ICON_MD_FAST_FORWARD); + + ImGui::EndTable(); + } +} + +} // namespace editor +} // namespace app +} // namespace yaze diff --git a/src/app/editor/music_editor.h b/src/app/editor/music_editor.h new file mode 100644 index 00000000..047d4c94 --- /dev/null +++ b/src/app/editor/music_editor.h @@ -0,0 +1,69 @@ +#ifndef YAZE_APP_EDITOR_MUSIC_EDITOR_H +#define YAZE_APP_EDITOR_MUSIC_EDITOR_H + +#include + +#include "absl/strings/str_format.h" +#include "app/editor/assembly_editor.h" +#include "gui/canvas.h" +#include "gui/icons.h" + +namespace yaze { +namespace app { +namespace editor { + +static constexpr absl::string_view kGameSongs[] = {"Title", + "Light World", + "Beginning", + "Rabbit", + "Forest", + "Intro", + "Town", + "Warp", + "Dark world", + "Master sword", + "File select", + "Soldier", + "Mountain", + "Shop", + "Fanfare", + "Castle", + "Palace (Pendant)", + "Cave (Same as Secret Way)", + "Clear (Dungeon end)", + "Church", + "Boss", + "Dungeon (Crystal)", + "Psychic", + "Secret Way (Same as Cave)", + "Rescue", + "Crystal", + "Fountain", + "Pyramid", + "Kill Agahnim", + "Ganon Room", + "Last Boss"}; + +static constexpr absl::string_view kSongNotes[] = { + "C", "D", "E", "F", "G", "A", "B", "C", "D", "E", "F", "G", "A", + "B", "C", "D", "E", "F", "G", "A", "B", "C", "D", "E", "F"}; +class MusicEditor { + public: + void Update(); + + private: + void DrawPianoStaff(); + void DrawPianoRoll(); + void DrawSongList() const; + void DrawToolset(); + + AssemblyEditor assembly_editor_; + ImGuiTableFlags toolset_table_flags_ = + ImGuiTableFlags_SizingFixedFit; +}; + +} // namespace editor +} // namespace app +} // namespace yaze + +#endif