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)
33 list_width = 150.0f;
34
35 ImGui::BeginChild("InstrumentList", ImVec2(list_width, 0), true);
37 ImGui::EndChild();
38
39 ImGui::SameLine();
40
41 ImGui::BeginChild("InstrumentProps", ImVec2(0, 0), true);
44 static_cast<int>(bank.GetInstrumentCount())) {
46 } else {
47 ImGui::TextDisabled("Select an instrument to edit");
48 }
49 ImGui::EndChild();
50}
51
53 if (ImGui::Button("Add Instrument")) {
54 bank.CreateNewInstrument("New Instrument");
55 if (on_edit_)
56 on_edit_();
57 }
58
59 ImGui::Separator();
60
61 for (size_t i = 0; i < bank.GetInstrumentCount(); ++i) {
62 const auto* inst = bank.GetInstrument(i);
63 std::string label = absl::StrFormat("%02X: %s", i, inst->name);
64 if (ImGui::Selectable(label.c_str(),
65 selected_instrument_index_ == static_cast<int>(i))) {
66 selected_instrument_index_ = static_cast<int>(i);
67 }
68 }
69}
70
72 MusicBank& bank) {
73 bool changed = false;
74
75 // Name
76 char name_buf[64];
77 strncpy(name_buf, instrument.name.c_str(), sizeof(name_buf));
78 if (ImGui::InputText("Name", name_buf, sizeof(name_buf))) {
79 instrument.name = name_buf;
80 changed = true;
81 }
82
83 ImGui::SameLine();
84 if (on_preview_) {
85 if (ImGui::Button(ICON_MD_PLAY_ARROW " Preview")) {
87 }
88 if (ImGui::IsItemHovered()) {
89 ImGui::SetTooltip(
90 "Play a C4 note with this instrument (requires ROM loaded)");
91 }
92 } else {
93 ImGui::BeginDisabled();
94 ImGui::Button(ICON_MD_PLAY_ARROW " Preview");
95 ImGui::EndDisabled();
96 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
97 ImGui::SetTooltip("Preview not available - load a ROM first");
98 }
99 }
100
101 // Sample Selection
102 if (ImGui::BeginCombo(
103 "Sample", absl::StrFormat("%02X", instrument.sample_index).c_str())) {
104 for (size_t i = 0; i < bank.GetSampleCount(); ++i) {
105 bool is_selected = (instrument.sample_index == i);
106 const auto* sample = bank.GetSample(i);
107 std::string label = absl::StrFormat(
108 "%02X: %s", i, sample ? sample->name.c_str() : "Unknown");
109 if (ImGui::Selectable(label.c_str(), is_selected)) {
110 instrument.sample_index = static_cast<uint8_t>(i);
111 changed = true;
112 }
113 if (is_selected)
114 ImGui::SetItemDefaultFocus();
115 }
116 ImGui::EndCombo();
117 }
118 ImGui::SameLine();
119 HelpMarker("The BRR sample used by this instrument.");
120
121 // Pitch Multiplier (Tuning)
122 int pitch = instrument.pitch_mult;
123 if (ImGui::InputInt("Pitch Multiplier", &pitch, 1, 16,
124 ImGuiInputTextFlags_CharsHexadecimal)) {
125 instrument.pitch_mult = static_cast<uint16_t>(std::clamp(pitch, 0, 0xFFFF));
126 changed = true;
127 }
128 ImGui::SameLine();
129 HelpMarker(
130 "Base pitch adjustment. $1000 = 1.0x (Standard C). Lower values lower "
131 "the pitch.");
132
133 ImGui::Separator();
134 ImGui::Text("Envelope (ADSR)");
135 ImGui::SameLine();
136 HelpMarker("Attack, Decay, Sustain, Release envelope controls.");
137
138 // ADSR Controls
139 // Attack: 0-15
140 int attack = instrument.attack;
141 if (ImGui::SliderInt("Attack Rate", &attack, 0, 15)) {
142 instrument.attack = static_cast<uint8_t>(attack);
143 changed = true;
144 }
145 if (ImGui::IsItemHovered())
146 ImGui::SetTooltip(
147 "How fast the volume reaches peak. 15 = Fastest (Instant), 0 = "
148 "Slowest.");
149
150 // Decay: 0-7
151 int decay = instrument.decay;
152 if (ImGui::SliderInt("Decay Rate", &decay, 0, 7)) {
153 instrument.decay = static_cast<uint8_t>(decay);
154 changed = true;
155 }
156 if (ImGui::IsItemHovered())
157 ImGui::SetTooltip(
158 "How fast volume drops from peak to Sustain Level. 7 = Fastest, 0 = "
159 "Slowest.");
160
161 // Sustain Level: 0-7
162 int sustain_level = instrument.sustain_level;
163 if (ImGui::SliderInt("Sustain Level", &sustain_level, 0, 7)) {
164 instrument.sustain_level = static_cast<uint8_t>(sustain_level);
165 changed = true;
166 }
167 if (ImGui::IsItemHovered())
168 ImGui::SetTooltip(
169 "The volume level (1/8ths) to sustain at. 7 = Max Volume, 0 = "
170 "Silence.");
171
172 // Sustain Rate: 0-31
173 int sustain_rate = instrument.sustain_rate;
174 if (ImGui::SliderInt("Sustain Rate", &sustain_rate, 0, 31)) {
175 instrument.sustain_rate = static_cast<uint8_t>(sustain_rate);
176 changed = true;
177 }
178 if (ImGui::IsItemHovered())
179 ImGui::SetTooltip(
180 "How fast volume decays WHILE holding the key (after reaching Sustain "
181 "Level). 0 = Infinite sustain, 31 = Fast fade out.");
182
183 // Gain (if not using ADSR, but usually ADSR is preferred for instruments)
184 // TODO: Add Gain Mode toggle
185
186 if (changed && on_edit_) {
187 on_edit_();
188 }
189
190 ImGui::Separator();
191 DrawAdsrGraph(instrument);
192}
193
195 // Visualize ADSR
196 // Attack: Linear increase to max
197 // Decay: Exponential decrease to Sustain Level
198 // Sustain: Exponential decrease at Sustain Rate
199
200 // Ensure ImPlot context exists before plotting
202
203 // Helper to convert SNES rates to time/slope
204 // (Simplified for visualization)
205
206 plot_x_.clear();
207 plot_y_.clear();
208
209 float t = 0.0f;
210
211 // Attack Phase
212 // Rate N: (Rate * 2 + 1) ms to full volume? Roughly.
213 // Simplification: t += 1.0 / (attack + 1)
214 float attack_time = 1.0f / (instrument.attack + 1.0f);
215 if (instrument.attack == 15)
216 attack_time = 0.0f; // Instant
217
218 plot_x_.push_back(0.0f);
219 plot_y_.push_back(0.0f);
220
221 // Attack is linear in SNES DSP (add 1/64 per tick)
222 plot_x_.push_back(attack_time);
223 plot_y_.push_back(1.0f); // Max volume
224 t = attack_time;
225
226 // Decay Phase
227 // Exponential decay to Sustain Level
228 // Sustain Level is instrument.sustain_level/8.0f + 1
229 float s_level = (instrument.sustain_level + 1) / 8.0f;
230
231 // Simulate exponential decay
232 // k = decay rate factor
233 for (int i = 0; i < 20; ++i) {
234 t += 0.02f;
235 // Fake exponential: vol = s_level + (1 - s_level) * exp(-k * dt)
236 // Or simple lerp for now
237 float alpha = (float)i / 20.0f;
238 float curve = alpha * alpha; // Quadratic approximation for exponential
239 float vol = 1.0f - (1.0f - s_level) * curve;
240
241 plot_x_.push_back(t);
242 plot_y_.push_back(vol);
243 }
244
245 // Sustain Phase (Decrease)
246 // Decreases at sustain_rate until key off
247 float sustain_time = 1.0f; // Show 1 second of sustain
248 float sustain_drop_per_sec = instrument.sustain_rate / 31.0f;
249
250 plot_x_.push_back(t + sustain_time);
251 plot_y_.push_back(std::max(0.0f, s_level - sustain_drop_per_sec));
252
253 if (ImPlot::BeginPlot("ADSR Envelope", ImVec2(-1, 200))) {
254 ImPlot::SetupAxes("Time", "Volume");
255 ImPlot::SetupAxesLimits(0, 2.0, 0, 1.1);
256 ImPlot::PlotLine("Volume", plot_x_.data(), plot_y_.data(),
257 static_cast<int>(plot_x_.size()));
258
259 // Mark phases
260 ImPlot::TagX(attack_time, ImVec4(1, 1, 0, 0.5), "Decay Start");
261
262 ImPlot::EndPlot();
263 }
264}
265
266} // namespace music
267} // namespace editor
268} // 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