yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
instrument_editor_view.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cmath>
5
6#include "absl/strings/str_format.h"
9#include "imgui/imgui.h"
10#include "implot.h"
11
12namespace yaze {
13namespace editor {
14namespace music {
15
16using namespace yaze::zelda3::music;
17
18static void HelpMarker(const char* desc) {
19 ImGui::TextDisabled("(?)");
20 if (ImGui::IsItemHovered()) {
21 ImGui::BeginTooltip();
22 ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
23 ImGui::TextUnformatted(desc);
24 ImGui::PopTextWrapPos();
25 ImGui::EndTooltip();
26 }
27}
28
30 // Layout: List on left (25%), Properties on right (75%)
31 float list_width = ImGui::GetContentRegionAvail().x * 0.25f;
32 if (list_width < 150.0f) list_width = 150.0f;
33
34 ImGui::BeginChild("InstrumentList", ImVec2(list_width, 0), true);
36 ImGui::EndChild();
37
38 ImGui::SameLine();
39
40 ImGui::BeginChild("InstrumentProps", ImVec2(0, 0), true);
42 selected_instrument_index_ < static_cast<int>(bank.GetInstrumentCount())) {
44 } else {
45 ImGui::TextDisabled("Select an instrument to edit");
46 }
47 ImGui::EndChild();
48}
49
51 if (ImGui::Button("Add Instrument")) {
52 bank.CreateNewInstrument("New Instrument");
53 if (on_edit_) on_edit_();
54 }
55
56 ImGui::Separator();
57
58 for (size_t i = 0; i < bank.GetInstrumentCount(); ++i) {
59 const auto* inst = bank.GetInstrument(i);
60 std::string label = absl::StrFormat("%02X: %s", i, inst->name);
61 if (ImGui::Selectable(label.c_str(), selected_instrument_index_ == static_cast<int>(i))) {
62 selected_instrument_index_ = static_cast<int>(i);
63 }
64 }
65}
66
68 bool changed = false;
69
70 // Name
71 char name_buf[64];
72 strncpy(name_buf, instrument.name.c_str(), sizeof(name_buf));
73 if (ImGui::InputText("Name", name_buf, sizeof(name_buf))) {
74 instrument.name = name_buf;
75 changed = true;
76 }
77
78 ImGui::SameLine();
79 if (on_preview_) {
80 if (ImGui::Button(ICON_MD_PLAY_ARROW " Preview")) {
82 }
83 if (ImGui::IsItemHovered()) {
84 ImGui::SetTooltip("Play a C4 note with this instrument (requires ROM loaded)");
85 }
86 } else {
87 ImGui::BeginDisabled();
88 ImGui::Button(ICON_MD_PLAY_ARROW " Preview");
89 ImGui::EndDisabled();
90 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
91 ImGui::SetTooltip("Preview not available - load a ROM first");
92 }
93 }
94
95 // Sample Selection
96 if (ImGui::BeginCombo("Sample", absl::StrFormat("%02X", instrument.sample_index).c_str())) {
97 for (size_t i = 0; i < bank.GetSampleCount(); ++i) {
98 bool is_selected = (instrument.sample_index == i);
99 const auto* sample = bank.GetSample(i);
100 std::string label = absl::StrFormat("%02X: %s", i, sample ? sample->name.c_str() : "Unknown");
101 if (ImGui::Selectable(label.c_str(), is_selected)) {
102 instrument.sample_index = static_cast<uint8_t>(i);
103 changed = true;
104 }
105 if (is_selected) ImGui::SetItemDefaultFocus();
106 }
107 ImGui::EndCombo();
108 }
109 ImGui::SameLine();
110 HelpMarker("The BRR sample used by this instrument.");
111
112 // Pitch Multiplier (Tuning)
113 int pitch = instrument.pitch_mult;
114 if (ImGui::InputInt("Pitch Multiplier", &pitch, 1, 16, ImGuiInputTextFlags_CharsHexadecimal)) {
115 instrument.pitch_mult = static_cast<uint16_t>(std::clamp(pitch, 0, 0xFFFF));
116 changed = true;
117 }
118 ImGui::SameLine();
119 HelpMarker("Base pitch adjustment. $1000 = 1.0x (Standard C). Lower values lower the pitch.");
120
121 ImGui::Separator();
122 ImGui::Text("Envelope (ADSR)");
123 ImGui::SameLine();
124 HelpMarker("Attack, Decay, Sustain, Release envelope controls.");
125
126 // ADSR Controls
127 // Attack: 0-15
128 int attack = instrument.attack;
129 if (ImGui::SliderInt("Attack Rate", &attack, 0, 15)) {
130 instrument.attack = static_cast<uint8_t>(attack);
131 changed = true;
132 }
133 if (ImGui::IsItemHovered()) ImGui::SetTooltip("How fast the volume reaches peak. 15 = Fastest (Instant), 0 = Slowest.");
134
135 // Decay: 0-7
136 int decay = instrument.decay;
137 if (ImGui::SliderInt("Decay Rate", &decay, 0, 7)) {
138 instrument.decay = static_cast<uint8_t>(decay);
139 changed = true;
140 }
141 if (ImGui::IsItemHovered()) ImGui::SetTooltip("How fast volume drops from peak to Sustain Level. 7 = Fastest, 0 = Slowest.");
142
143 // Sustain Level: 0-7
144 int sustain_level = instrument.sustain_level;
145 if (ImGui::SliderInt("Sustain Level", &sustain_level, 0, 7)) {
146 instrument.sustain_level = static_cast<uint8_t>(sustain_level);
147 changed = true;
148 }
149 if (ImGui::IsItemHovered()) ImGui::SetTooltip("The volume level (1/8ths) to sustain at. 7 = Max Volume, 0 = Silence.");
150
151 // Sustain Rate: 0-31
152 int sustain_rate = instrument.sustain_rate;
153 if (ImGui::SliderInt("Sustain Rate", &sustain_rate, 0, 31)) {
154 instrument.sustain_rate = static_cast<uint8_t>(sustain_rate);
155 changed = true;
156 }
157 if (ImGui::IsItemHovered()) ImGui::SetTooltip("How fast volume decays WHILE holding the key (after reaching Sustain Level). 0 = Infinite sustain, 31 = Fast fade out.");
158
159 // Gain (if not using ADSR, but usually ADSR is preferred for instruments)
160 // TODO: Add Gain Mode toggle
161
162 if (changed && on_edit_) {
163 on_edit_();
164 }
165
166 ImGui::Separator();
167 DrawAdsrGraph(instrument);
168}
169
171 // Visualize ADSR
172 // Attack: Linear increase to max
173 // Decay: Exponential decrease to Sustain Level
174 // Sustain: Exponential decrease at Sustain Rate
175
176 // Ensure ImPlot context exists before plotting
178
179 // Helper to convert SNES rates to time/slope
180 // (Simplified for visualization)
181
182 plot_x_.clear();
183 plot_y_.clear();
184
185 float t = 0.0f;
186
187 // Attack Phase
188 // Rate N: (Rate * 2 + 1) ms to full volume? Roughly.
189 // Simplification: t += 1.0 / (attack + 1)
190 float attack_time = 1.0f / (instrument.attack + 1.0f);
191 if (instrument.attack == 15) attack_time = 0.0f; // Instant
192
193 plot_x_.push_back(0.0f);
194 plot_y_.push_back(0.0f);
195
196 // Attack is linear in SNES DSP (add 1/64 per tick)
197 plot_x_.push_back(attack_time);
198 plot_y_.push_back(1.0f); // Max volume
199 t = attack_time;
200
201 // Decay Phase
202 // Exponential decay to Sustain Level
203 // Sustain Level is instrument.sustain_level/8.0f + 1
204 float s_level = (instrument.sustain_level + 1) / 8.0f;
205
206 // Simulate exponential decay
207 // k = decay rate factor
208 for (int i = 0; i < 20; ++i) {
209 t += 0.02f;
210 // Fake exponential: vol = s_level + (1 - s_level) * exp(-k * dt)
211 // Or simple lerp for now
212 float alpha = (float)i / 20.0f;
213 float curve = alpha * alpha; // Quadratic approximation for exponential
214 float vol = 1.0f - (1.0f - s_level) * curve;
215
216 plot_x_.push_back(t);
217 plot_y_.push_back(vol);
218 }
219
220 // Sustain Phase (Decrease)
221 // Decreases at sustain_rate until key off
222 float sustain_time = 1.0f; // Show 1 second of sustain
223 float sustain_drop_per_sec = instrument.sustain_rate / 31.0f;
224
225 plot_x_.push_back(t + sustain_time);
226 plot_y_.push_back(std::max(0.0f, s_level - sustain_drop_per_sec));
227
228 if (ImPlot::BeginPlot("ADSR Envelope", ImVec2(-1, 200))) {
229 ImPlot::SetupAxes("Time", "Volume");
230 ImPlot::SetupAxesLimits(0, 2.0, 0, 1.1);
231 ImPlot::PlotLine("Volume", plot_x_.data(), plot_y_.data(), static_cast<int>(plot_x_.size()));
232
233 // Mark phases
234 ImPlot::TagX(attack_time, ImVec4(1,1,0,0.5), "Decay Start");
235
236 ImPlot::EndPlot();
237 }
238}
239
240} // namespace music
241} // namespace editor
242} // namespace yaze
void DrawAdsrGraph(const MusicInstrument &instrument)
void Draw(MusicBank &bank)
Draw the instrument editor.
void DrawProperties(MusicInstrument &instrument, MusicBank &bank)
Manages the collection of songs, instruments, and samples from a ROM.
Definition music_bank.h:27
MusicInstrument * GetInstrument(int index)
Get an instrument by index.
int CreateNewInstrument(const std::string &name)
Create a new instrument.
size_t GetSampleCount() const
Get the number of samples.
Definition music_bank.h:198
size_t GetInstrumentCount() const
Get the number of instruments.
Definition music_bank.h:176
MusicSample * GetSample(int index)
Get a sample by index.
#define ICON_MD_PLAY_ARROW
Definition icons.h:1479
Contains classes and functions for handling music data in Zelda 3.
An instrument definition with ADSR envelope.
Definition song_data.h:358