1 /*
2 * Copyright (C) 2010-2011 Carl Hetherington <carl@carlh.net>
3 * Copyright (C) 2010-2018 Paul Davis <paul@linuxaudiosystems.com>
4 * Copyright (C) 2011-2015 David Robillard <d@drobilla.net>
5 * Copyright (C) 2013-2018 Robin Gareus <robin@gareus.org>
6 * Copyright (C) 2015-2017 Nick Mainsbridge <mainsbridge@gmail.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22
23 #include "ardour/midi_track.h"
24 #include "ardour/midi_region.h"
25 #include "ardour/tempo.h"
26 #include "ardour/types.h"
27
28 #include "gui_thread.h"
29 #include "midi_region_view.h"
30 #include "public_editor.h"
31 #include "step_editor.h"
32 #include "step_entry.h"
33
34 using namespace ARDOUR;
35 using namespace Gtk;
36 using namespace std;
37
StepEditor(PublicEditor & e,boost::shared_ptr<MidiTrack> t,MidiTimeAxisView & mtv)38 StepEditor::StepEditor (PublicEditor& e, boost::shared_ptr<MidiTrack> t, MidiTimeAxisView& mtv)
39 : _editor (e)
40 , _track (t)
41 , _mtv (mtv)
42 {
43 step_edit_insert_position = 0;
44 _step_edit_triplet_countdown = 0;
45 _step_edit_within_chord = 0;
46 _step_edit_chord_duration = Temporal::Beats();
47 step_edit_region_view = 0;
48
49 _track->PlaylistChanged.connect (*this, invalidator (*this),
50 boost::bind (&StepEditor::playlist_changed, this),
51 gui_context());
52 playlist_changed ();
53 }
54
~StepEditor()55 StepEditor::~StepEditor()
56 {
57 StepEntry::instance().set_step_editor (0);
58 }
59
60 void
start_step_editing()61 StepEditor::start_step_editing ()
62 {
63 _step_edit_triplet_countdown = 0;
64 _step_edit_within_chord = 0;
65 _step_edit_chord_duration = Temporal::Beats();
66 step_edit_region.reset ();
67 step_edit_region_view = 0;
68 last_added_pitch = -1;
69 last_added_end = Temporal::Beats();
70
71 resync_step_edit_position ();
72 prepare_step_edit_region ();
73 reset_step_edit_beat_pos ();
74
75 assert (step_edit_region);
76 assert (step_edit_region_view);
77
78 StepEntry::instance().set_step_editor (this);
79 delete_connection = StepEntry::instance().signal_delete_event().connect (sigc::mem_fun (*this, &StepEditor::step_entry_hidden));
80 hide_connection = StepEntry::instance(). signal_hide().connect (sigc::mem_fun (*this, &StepEditor::step_entry_done));
81
82 step_edit_region_view->show_step_edit_cursor (step_edit_beat_pos);
83 step_edit_region_view->set_step_edit_cursor_width (StepEntry::instance().note_length());
84
85 StepEntry::instance().present ();
86 }
87
88 void
resync_step_edit_position()89 StepEditor::resync_step_edit_position ()
90 {
91 step_edit_insert_position = _editor.get_preferred_edit_position (Editing::EDIT_IGNORE_NONE, false, true);
92 }
93
94 void
resync_step_edit_to_edit_point()95 StepEditor::resync_step_edit_to_edit_point ()
96 {
97 resync_step_edit_position ();
98 if (step_edit_region) {
99 reset_step_edit_beat_pos ();
100 }
101 }
102
103 void
prepare_step_edit_region()104 StepEditor::prepare_step_edit_region ()
105 {
106 boost::shared_ptr<Region> r = _track->playlist()->top_region_at (step_edit_insert_position);
107
108 if (r) {
109 step_edit_region = boost::dynamic_pointer_cast<MidiRegion>(r);
110 }
111
112 if (step_edit_region) {
113 RegionView* rv = _mtv.midi_view()->find_view (step_edit_region);
114 step_edit_region_view = dynamic_cast<MidiRegionView*> (rv);
115
116 } else {
117
118 const Meter& m = _mtv.session()->tempo_map().meter_at_sample (step_edit_insert_position);
119 double baf = max (0.0, _mtv.session()->tempo_map().beat_at_sample (step_edit_insert_position));
120 double next_bar_in_beats = baf + m.divisions_per_bar();
121 samplecnt_t next_bar_pos = _mtv.session()->tempo_map().sample_at_beat (next_bar_in_beats);
122 samplecnt_t len = next_bar_pos - step_edit_insert_position;
123
124 step_edit_region = _mtv.add_region (step_edit_insert_position, len, true);
125
126 RegionView* rv = _mtv.midi_view()->find_view (step_edit_region);
127 step_edit_region_view = dynamic_cast<MidiRegionView*>(rv);
128 }
129 }
130
131
132 void
reset_step_edit_beat_pos()133 StepEditor::reset_step_edit_beat_pos ()
134 {
135 assert (step_edit_region);
136 assert (step_edit_region_view);
137
138 samplecnt_t samples_from_start = _editor.get_preferred_edit_position() - step_edit_region->position();
139
140 if (samples_from_start < 0) {
141 /* this can happen with snap enabled, and the edit point == Playhead. we snap the
142 position of the new region, and it can end up after the edit point.
143 */
144 samples_from_start = 0;
145 }
146
147 step_edit_beat_pos = step_edit_region_view->region_samples_to_region_beats (samples_from_start);
148 step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos);
149 }
150
151 bool
step_entry_hidden(GdkEventAny *)152 StepEditor::step_entry_hidden (GdkEventAny*)
153 {
154 step_entry_done ();
155 return true;
156 }
157
158 void
step_entry_done()159 StepEditor::step_entry_done ()
160 {
161 hide_connection.disconnect ();
162 delete_connection.disconnect ();
163
164 /* everything else will follow the change in the model */
165 _track->set_step_editing (false);
166 }
167
168 void
stop_step_editing()169 StepEditor::stop_step_editing ()
170 {
171 StepEntry::instance().hide ();
172
173 if (step_edit_region_view) {
174 step_edit_region_view->hide_step_edit_cursor();
175 }
176
177 step_edit_region.reset ();
178 }
179
180 void
check_step_edit()181 StepEditor::check_step_edit ()
182 {
183 MidiRingBuffer<samplepos_t>& incoming (_track->step_edit_ring_buffer());
184 uint8_t* buf;
185 uint32_t bufsize = 32;
186
187 buf = new uint8_t[bufsize];
188
189 while (incoming.read_space()) {
190 samplepos_t time;
191 Evoral::EventType type;
192 uint32_t size;
193
194 if (!incoming.read_prefix (&time, &type, &size)) {
195 break;
196 }
197
198 if (size > bufsize) {
199 delete [] buf;
200 bufsize = size;
201 buf = new uint8_t[bufsize];
202 }
203
204 if (!incoming.read_contents (size, buf)) {
205 break;
206 }
207
208 if ((buf[0] & 0xf0) == MIDI_CMD_NOTE_ON && size == 3) {
209 step_add_note (buf[0] & 0xf, buf[1], buf[2], Temporal::Beats());
210 }
211 }
212 delete [] buf;
213 }
214
215 int
step_add_bank_change(uint8_t,uint8_t)216 StepEditor::step_add_bank_change (uint8_t /*channel*/, uint8_t /*bank*/)
217 {
218 return 0;
219 }
220
221 int
step_add_program_change(uint8_t,uint8_t)222 StepEditor::step_add_program_change (uint8_t /*channel*/, uint8_t /*program*/)
223 {
224 return 0;
225 }
226
227 void
step_edit_sustain(Temporal::Beats beats)228 StepEditor::step_edit_sustain (Temporal::Beats beats)
229 {
230 if (step_edit_region_view) {
231 step_edit_region_view->step_sustain (beats);
232 }
233 }
234
235 void
move_step_edit_beat_pos(Temporal::Beats beats)236 StepEditor::move_step_edit_beat_pos (Temporal::Beats beats)
237 {
238 if (!step_edit_region_view) {
239 return;
240 }
241 if (beats > 0.0) {
242 step_edit_beat_pos = min (step_edit_beat_pos + beats,
243 step_edit_region_view->region_samples_to_region_beats (step_edit_region->length()));
244 } else if (beats < 0.0) {
245 if (-beats < step_edit_beat_pos) {
246 step_edit_beat_pos += beats; // its negative, remember
247 } else {
248 step_edit_beat_pos = Temporal::Beats();
249 }
250 }
251 step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos);
252 }
253
254 int
step_add_note(uint8_t channel,uint8_t pitch,uint8_t velocity,Temporal::Beats beat_duration)255 StepEditor::step_add_note (uint8_t channel, uint8_t pitch, uint8_t velocity, Temporal::Beats beat_duration)
256 {
257 /* do these things in case undo removed the step edit region
258 */
259 if (!step_edit_region) {
260 resync_step_edit_position ();
261 prepare_step_edit_region ();
262 reset_step_edit_beat_pos ();
263 step_edit_region_view->show_step_edit_cursor (step_edit_beat_pos);
264 step_edit_region_view->set_step_edit_cursor_width (StepEntry::instance().note_length());
265 }
266
267 assert (step_edit_region);
268 assert (step_edit_region_view);
269
270 if (beat_duration == 0.0) {
271 beat_duration = StepEntry::instance().note_length();
272 } else if (beat_duration == 0.0) {
273 bool success;
274 beat_duration = _editor.get_grid_type_as_beats (success, step_edit_insert_position);
275
276 if (!success) {
277 return -1;
278 }
279 }
280
281 MidiStreamView* msv = _mtv.midi_view();
282
283 /* make sure its visible on the vertical axis */
284
285 if (pitch < msv->lowest_note() || pitch > msv->highest_note()) {
286 msv->update_note_range (pitch);
287 msv->set_note_range (MidiStreamView::ContentsRange);
288 }
289
290 /* make sure its visible on the horizontal axis */
291
292 samplepos_t fpos = step_edit_region_view->region_beats_to_absolute_samples (step_edit_beat_pos + beat_duration);
293
294 if (fpos >= (_editor.leftmost_sample() + _editor.current_page_samples())) {
295 _editor.reset_x_origin (fpos - (_editor.current_page_samples()/4));
296 }
297
298 Temporal::Beats at = step_edit_beat_pos;
299 Temporal::Beats len = beat_duration;
300
301 if ((last_added_pitch >= 0) && (pitch == last_added_pitch) && (last_added_end == step_edit_beat_pos)) {
302
303 /* avoid any apparent note overlap - move the start of this note
304 up by 1 tick from where the last note ended
305 */
306
307 at += Temporal::Beats::ticks(1);
308 len -= Temporal::Beats::ticks(1);
309 }
310
311 step_edit_region_view->step_add_note (channel, pitch, velocity, at, len);
312
313 last_added_pitch = pitch;
314 last_added_end = at+len;
315
316 if (_step_edit_triplet_countdown > 0) {
317 _step_edit_triplet_countdown--;
318
319 if (_step_edit_triplet_countdown == 0) {
320 _step_edit_triplet_countdown = 3;
321 }
322 }
323
324 if (!_step_edit_within_chord) {
325 step_edit_beat_pos += beat_duration;
326 step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos);
327 } else {
328 step_edit_beat_pos += Temporal::Beats::ticks(1); // tiny, but no longer overlapping
329 _step_edit_chord_duration = max (_step_edit_chord_duration, beat_duration);
330 }
331
332 step_edit_region_view->set_step_edit_cursor_width (StepEntry::instance().note_length());
333
334 return 0;
335 }
336
337 void
set_step_edit_cursor_width(Temporal::Beats beats)338 StepEditor::set_step_edit_cursor_width (Temporal::Beats beats)
339 {
340 if (step_edit_region_view) {
341 step_edit_region_view->set_step_edit_cursor_width (beats);
342 }
343 }
344
345 bool
step_edit_within_triplet() const346 StepEditor::step_edit_within_triplet() const
347 {
348 return _step_edit_triplet_countdown > 0;
349 }
350
351 bool
step_edit_within_chord() const352 StepEditor::step_edit_within_chord() const
353 {
354 return _step_edit_within_chord;
355 }
356
357 void
step_edit_toggle_triplet()358 StepEditor::step_edit_toggle_triplet ()
359 {
360 if (_step_edit_triplet_countdown == 0) {
361 _step_edit_within_chord = false;
362 _step_edit_triplet_countdown = 3;
363 } else {
364 _step_edit_triplet_countdown = 0;
365 }
366 }
367
368 void
step_edit_toggle_chord()369 StepEditor::step_edit_toggle_chord ()
370 {
371 if (_step_edit_within_chord) {
372 _step_edit_within_chord = false;
373 if (step_edit_region_view) {
374 step_edit_beat_pos += _step_edit_chord_duration;
375 step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos);
376 }
377 } else {
378 _step_edit_triplet_countdown = 0;
379 _step_edit_within_chord = true;
380 }
381 }
382
383 void
step_edit_rest(Temporal::Beats beats)384 StepEditor::step_edit_rest (Temporal::Beats beats)
385 {
386 bool success;
387
388 if (beats == 0.0) {
389 beats = _editor.get_grid_type_as_beats (success, step_edit_insert_position);
390 } else {
391 success = true;
392 }
393
394 if (success && step_edit_region_view) {
395 step_edit_beat_pos += beats;
396 step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos);
397 }
398 }
399
400 void
step_edit_beat_sync()401 StepEditor::step_edit_beat_sync ()
402 {
403 step_edit_beat_pos = step_edit_beat_pos.round_up_to_beat();
404 if (step_edit_region_view) {
405 step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos);
406 }
407 }
408
409 void
step_edit_bar_sync()410 StepEditor::step_edit_bar_sync ()
411 {
412 Session* _session = _mtv.session ();
413
414 if (!_session || !step_edit_region_view || !step_edit_region) {
415 return;
416 }
417
418 samplepos_t fpos = step_edit_region_view->region_beats_to_absolute_samples (step_edit_beat_pos);
419 fpos = _session->tempo_map().round_to_bar (fpos, RoundUpAlways).sample;
420 step_edit_beat_pos = step_edit_region_view->region_samples_to_region_beats (fpos - step_edit_region->position()).round_up_to_beat();
421 step_edit_region_view->move_step_edit_cursor (step_edit_beat_pos);
422 }
423
424 void
playlist_changed()425 StepEditor::playlist_changed ()
426 {
427 step_edit_region_connection.disconnect ();
428 _track->playlist()->RegionRemoved.connect (step_edit_region_connection, invalidator (*this),
429 boost::bind (&StepEditor::region_removed, this, _1),
430 gui_context());
431 }
432
433 void
region_removed(boost::weak_ptr<Region> wr)434 StepEditor::region_removed (boost::weak_ptr<Region> wr)
435 {
436 boost::shared_ptr<Region> r (wr.lock());
437
438 if (!r) {
439 return;
440 }
441
442 if (step_edit_region == r) {
443 step_edit_region.reset();
444 step_edit_region_view = 0;
445 // force a recompute of the insert position
446 step_edit_beat_pos = Temporal::Beats(-1);
447 }
448 }
449
450 string
name() const451 StepEditor::name() const
452 {
453 return _track->name();
454 }
455