1 // Licensed GNU LGPL v3 or later: http://www.gnu.org/licenses/lgpl.html 2 3 #ifndef SPECTMORPH_SAMPLE_WIDGET_HH 4 #define SPECTMORPH_SAMPLE_WIDGET_HH 5 6 #include "smwidget.hh" 7 #include "sminstrument.hh" 8 9 #include <map> 10 11 namespace SpectMorph 12 { 13 14 class SampleWidget : public Widget 15 { 16 public: 17 struct DisplayTuning 18 { 19 bool enabled = false; 20 int partials = 3; 21 int range = 100; 22 }; 23 private: 24 void draw_grid(const DrawEvent & devent)25 draw_grid (const DrawEvent& devent) 26 { 27 cairo_t *cr = devent.cr; 28 DrawUtils du (cr); 29 30 du.set_color (Color (0.33, 0.33, 0.33)); 31 cairo_set_line_width (cr, 1); 32 33 const double pad = 8; 34 for (double y = pad; y < height() - 4; y += pad) 35 { 36 cairo_move_to (cr, 0, y); 37 cairo_line_to (cr, width(), y); 38 cairo_stroke (cr); 39 } 40 for (double x = pad; x < width() - 4; x += pad) 41 { 42 if (x >= devent.rect.x() && x <= devent.rect.x() + devent.rect.width()) 43 { 44 cairo_move_to (cr, x, 0); 45 cairo_line_to (cr, x, height()); 46 cairo_stroke (cr); 47 } 48 } 49 } 50 static double freq_ratio_to_cent(double freq_ratio)51 freq_ratio_to_cent (double freq_ratio) 52 { 53 return log (freq_ratio) / log (2) * 1200; 54 } 55 double vzoom = 1; 56 Sample *m_sample = nullptr; 57 MarkerType selected_marker = MARKER_NONE; 58 bool mouse_down = false; 59 DisplayTuning m_display_tuning; 60 61 std::map<MarkerType, Rect> marker_rect; 62 std::vector<float> m_play_pointers; 63 public: SampleWidget(Widget * parent)64 SampleWidget (Widget *parent) 65 : Widget (parent) 66 { 67 } 68 void draw(const DrawEvent & devent)69 draw (const DrawEvent& devent) override 70 { 71 cairo_t *cr = devent.cr; 72 DrawUtils du (cr); 73 74 du.round_box (0, 0, width(), height(), 1, 5, Color (0.4, 0.4, 0.4), Color (0.3, 0.3, 0.3)); 75 76 draw_grid (devent); 77 78 /* redraw border to overdraw line endings */ 79 du.round_box (0, 0, width(), height(), 1, 5, Color (0.4, 0.4, 0.4), Color::null()); 80 81 if (!m_sample) 82 return; 83 84 double azoom; 85 if (m_sample->audio) 86 azoom = db_to_factor (m_sample->audio->original_samples_norm_db); 87 else 88 azoom = 1; 89 90 const double length_ms = m_sample->wav_data().samples().size() / m_sample->wav_data().mix_freq() * 1000; 91 const double clip_start_x = m_sample->get_marker (MARKER_CLIP_START) / length_ms * width(); 92 const double clip_end_x = m_sample->get_marker (MARKER_CLIP_END) / length_ms * width(); 93 const double loop_start_x = m_sample->get_marker (MARKER_LOOP_START) / length_ms * width(); 94 const double loop_end_x = m_sample->get_marker (MARKER_LOOP_END) / length_ms * width(); 95 const std::vector<float>& samples = m_sample->wav_data().samples(); 96 97 //du.set_color (Color (0.4, 0.4, 1.0)); 98 du.set_color (Color (0.9, 0.1, 0.1)); 99 for (int pass = 0; pass < 2; pass++) 100 { 101 int last_x_pixel = -1; 102 float max_s = 0; 103 float min_s = 0; 104 cairo_move_to (cr, 0, height() / 2); 105 106 const int samples_per_pixel = sm_round_positive (std::max (samples.size() / width(), 1.0)); 107 const int pixels_per_sample = sm_round_positive (std::max (width() / samples.size(), 1.0)); 108 109 int draw_start = (devent.rect.x() - 2 * pixels_per_sample) / width() * samples.size(); 110 int draw_end = (devent.rect.x() + 2 * pixels_per_sample + devent.rect.width()) / width() * samples.size(); 111 112 draw_start = std::max<int> (draw_start - 2 * samples_per_pixel, 0); 113 draw_end = std::min<int> (draw_end + 2 * samples_per_pixel, samples.size()); 114 for (int i = draw_start; i < draw_end; i++) 115 { 116 double dx = double (i) * width() / samples.size(); 117 118 int x_pixel = dx; 119 max_s = std::max (samples[i], max_s); 120 min_s = std::min (samples[i], min_s); 121 if (x_pixel != last_x_pixel) 122 { 123 if (pass == 0) 124 cairo_line_to (cr, last_x_pixel, height() / 2 + min_s * height() / 2 * vzoom * azoom); 125 else 126 cairo_line_to (cr, last_x_pixel, height() / 2 + max_s * height() / 2 * vzoom * azoom); 127 128 last_x_pixel = x_pixel; 129 max_s = 0; 130 min_s = 0; 131 } 132 } 133 cairo_line_to (cr, last_x_pixel, height() / 2); 134 cairo_close_path (cr); 135 cairo_set_line_width (cr, 1); 136 cairo_stroke_preserve (cr); 137 cairo_fill (cr); 138 } 139 140 /* lighten loop region */ 141 if (m_sample->loop() == Sample::Loop::FORWARD || m_sample->loop() == Sample::Loop::PING_PONG) 142 { 143 cairo_rectangle (cr, loop_start_x, 0, loop_end_x - loop_start_x, height()); 144 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.25); 145 cairo_fill (cr); 146 } 147 148 /* darken widget before and after clip region */ 149 cairo_rectangle (cr, 0, 0, clip_start_x, height()); 150 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.25); 151 cairo_fill (cr); 152 153 double effective_end_x = clip_end_x; 154 if (m_sample->loop() == Sample::Loop::FORWARD || m_sample->loop() == Sample::Loop::PING_PONG) 155 effective_end_x = loop_end_x; 156 if (m_sample->loop() == Sample::Loop::SINGLE_FRAME) 157 effective_end_x = loop_start_x; 158 159 cairo_rectangle (cr, effective_end_x, 0, width() - effective_end_x, height()); 160 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.25); 161 cairo_fill (cr); 162 163 du.set_color (Color (1.0, 0.3, 0.3)); 164 cairo_move_to (cr, 0, height()/2); 165 cairo_line_to (cr, width(), height()/2); 166 cairo_stroke (cr); 167 168 if (m_display_tuning.enabled && m_sample->audio) 169 { 170 Audio *audio = m_sample->audio.get(); 171 172 cairo_move_to (cr, clip_start_x, height() / 2); 173 du.set_color (Color (0.8, 0.8, 0.8)); 174 for (size_t frame = 0; frame < audio->contents.size(); frame++) 175 { 176 double pos = clip_start_x + double (frame * audio->frame_step_ms) / length_ms * width(); 177 178 const AudioBlock& block = audio->contents[frame]; 179 const double cent = freq_ratio_to_cent (block.estimate_fundamental (m_display_tuning.partials)); 180 181 cairo_line_to (cr, pos, height() / 2 - cent / m_display_tuning.range * (height() / 2)); 182 } 183 cairo_stroke (cr); 184 } 185 /* markers */ 186 for (int m = MARKER_LOOP_START; m <= MARKER_CLIP_END; m++) 187 { 188 MarkerType marker = static_cast<MarkerType> (m); 189 double marker_x = m_sample->get_marker (marker) / length_ms * width(); 190 191 Rect rect; 192 Color color; 193 if (m == MARKER_LOOP_START) 194 { 195 double c = 0; 196 197 if (m_sample->loop() == Sample::Loop::NONE) 198 continue; 199 if (m_sample->loop() == Sample::Loop::SINGLE_FRAME) // center rect for single frame loop 200 c = 5; 201 202 rect = Rect (marker_x - c, 0, 10, 10); 203 color = Color (0.7, 0.7, 1); 204 205 } 206 else if (m == MARKER_LOOP_END) 207 { 208 if (m_sample->loop() == Sample::Loop::NONE || m_sample->loop() == Sample::Loop::SINGLE_FRAME) 209 continue; 210 211 rect = Rect (marker_x - 10, 0, 10, 10); 212 color = Color (0.7, 0.7, 1); 213 } 214 else if (m == MARKER_CLIP_START) 215 { 216 rect = Rect (marker_x, height() - 10, 10, 10); 217 color = Color (0.4, 0.4, 1); 218 } 219 else if (m == MARKER_CLIP_END) 220 { 221 if (m_sample->loop() != Sample::Loop::NONE) 222 continue; 223 224 rect = Rect (marker_x - 10, height() - 10, 10, 10); 225 color = Color (0.4, 0.4, 1); 226 } 227 marker_rect[marker] = rect; 228 229 if (marker == selected_marker) 230 color = color.lighter (175); 231 du.set_color (color); 232 233 cairo_rectangle (cr, rect.x(), rect.y(), rect.width(), rect.height()); 234 cairo_fill (cr); 235 cairo_move_to (cr, marker_x, 0); 236 cairo_line_to (cr, marker_x, height()); 237 cairo_stroke (cr); 238 } 239 for (auto p : m_play_pointers) 240 { 241 if (p > 0) 242 { 243 const double pos_x = play_pos_to_pixels (p); 244 245 du.set_color (Color (1.0, 0.5, 0.0)); 246 cairo_move_to (cr, pos_x, 0); 247 cairo_line_to (cr, pos_x, height()); 248 cairo_stroke (cr); 249 } 250 } 251 } 252 MarkerType find_marker_xy(double x,double y)253 find_marker_xy (double x, double y) 254 { 255 for (int m = MARKER_LOOP_START; m <= MARKER_CLIP_END; m++) 256 { 257 MarkerType marker = MarkerType (m); 258 259 if (marker_rect[marker].contains (x, y)) 260 return marker; 261 } 262 return MARKER_NONE; 263 } 264 void get_order(MarkerType marker,std::vector<MarkerType> & left,std::vector<MarkerType> & right)265 get_order (MarkerType marker, std::vector<MarkerType>& left, std::vector<MarkerType>& right) 266 { 267 std::vector<MarkerType> left_to_right { MARKER_CLIP_START, MARKER_LOOP_START, MARKER_LOOP_END, MARKER_CLIP_END }; 268 269 std::vector<MarkerType>::iterator it = find (left_to_right.begin(), left_to_right.end(), marker); 270 size_t pos = it - left_to_right.begin(); 271 272 left.clear(); 273 right.clear(); 274 275 for (size_t i = 0; i < left_to_right.size(); i++) 276 { 277 const MarkerType lr_marker = left_to_right[i]; 278 if (i < pos) 279 left.push_back (lr_marker); 280 if (i > pos) 281 right.push_back (lr_marker); 282 } 283 } 284 void update_marker(MarkerType marker)285 update_marker (MarkerType marker) 286 { 287 if (m_sample) 288 { 289 const double length_ms = m_sample->wav_data().samples().size() / m_sample->wav_data().mix_freq() * 1000; 290 const double marker_x = m_sample->get_marker (marker) / length_ms * width(); 291 292 update (marker_x - 11, 0, 22, height()); 293 } 294 } 295 void mouse_move(const MouseEvent & event)296 mouse_move (const MouseEvent& event) override 297 { 298 if (mouse_down) 299 { 300 if (selected_marker == MARKER_NONE) 301 return; 302 303 const double sample_len_ms = m_sample->wav_data().samples().size() / m_sample->wav_data().mix_freq() * 1000.0; 304 const double x_ms = sm_bound<double> (0, event.x / width() * sample_len_ms, sample_len_ms); 305 306 update_marker (selected_marker); 307 m_sample->set_marker (selected_marker, x_ms); 308 update_marker (selected_marker); 309 310 /* enforce ordering constraints */ 311 std::vector<MarkerType> left, right; 312 get_order (selected_marker, left, right); 313 314 for (auto l : left) 315 if (m_sample->get_marker (l) > x_ms) 316 m_sample->set_marker (l, x_ms); 317 318 for (auto r : right) 319 if (m_sample->get_marker (r) < x_ms) 320 m_sample->set_marker (r, x_ms); 321 } 322 else 323 { 324 MarkerType old_marker = selected_marker; 325 selected_marker = find_marker_xy (event.x, event.y); 326 327 if (selected_marker != old_marker) 328 update(); 329 } 330 } 331 void mouse_press(const MouseEvent & event)332 mouse_press (const MouseEvent& event) override 333 { 334 if (event.button == LEFT_BUTTON) 335 mouse_down = true; 336 } 337 void mouse_release(const MouseEvent & event)338 mouse_release (const MouseEvent& event) override 339 { 340 if (event.button == LEFT_BUTTON) 341 { 342 mouse_down = false; 343 selected_marker = find_marker_xy (event.x, event.y); 344 345 update(); 346 } 347 } 348 void leave_event()349 leave_event() override 350 { 351 selected_marker = MARKER_NONE; 352 update(); 353 } 354 void set_sample(Sample * sample)355 set_sample (Sample *sample) 356 { 357 m_sample = sample; 358 update(); 359 } 360 void set_vzoom(double factor)361 set_vzoom (double factor) 362 { 363 vzoom = factor; 364 update(); 365 } 366 void set_display_tuning(const DisplayTuning & tuning)367 set_display_tuning (const DisplayTuning& tuning) 368 { 369 m_display_tuning = tuning; 370 update(); 371 } 372 DisplayTuning display_tuning() const373 display_tuning() const 374 { 375 return m_display_tuning; 376 } 377 double play_pos_to_pixels(double pos_ms)378 play_pos_to_pixels (double pos_ms) 379 { 380 if (!m_sample) 381 return -1; 382 383 const double length_ms = m_sample->wav_data().samples().size() / m_sample->wav_data().mix_freq() * 1000; 384 const double pos_x = pos_ms / length_ms * width(); 385 return pos_x; 386 } 387 void set_play_pointers(const std::vector<float> & pointers)388 set_play_pointers (const std::vector<float>& pointers) 389 { 390 if (pointers == m_play_pointers) /* save CPU power */ 391 return; 392 393 std::vector<float> all_x; 394 for (auto p : m_play_pointers) 395 all_x.push_back (play_pos_to_pixels (p)); 396 397 m_play_pointers = pointers; 398 399 for (auto p : m_play_pointers) 400 all_x.push_back (play_pos_to_pixels (p)); 401 402 if (!all_x.empty()) 403 { 404 double min_x = *std::min_element (all_x.begin(), all_x.end()) - 1; 405 double max_x = *std::max_element (all_x.begin(), all_x.end()) + 1; 406 double update_width = max_x - min_x; 407 408 update (min_x, 0, update_width, height()); 409 } 410 } 411 void update_loop()412 update_loop() 413 { 414 update(); 415 } 416 }; 417 418 } 419 #endif 420