You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

418 lines
12KB

  1. #include <assert.h>
  2. #include "MidiEditor.h"
  3. #include "MidiEditorContext.h"
  4. #include "MidiLock.h"
  5. #include "MidiSelectionModel.h"
  6. #include "MidiSequencer.h"
  7. #include "MidiSong.h"
  8. #include "MidiTrack.h"
  9. #include "ReplaceDataCommand.h"
  10. #include "TimeUtils.h"
  11. extern int _mdb;
  12. MidiEditor::MidiEditor(std::shared_ptr<MidiSequencer> seq) :
  13. m_seq(seq)
  14. {
  15. _mdb++;
  16. }
  17. MidiEditor::~MidiEditor()
  18. {
  19. _mdb--;
  20. }
  21. MidiTrackPtr MidiEditor::getTrack()
  22. {
  23. return seq()->song->getTrack(seq()->context->getTrackNumber());
  24. }
  25. /**
  26. * If iterator already points to a note, return it.
  27. * Otherwise search for next one
  28. */
  29. static MidiTrack::const_iterator findNextNoteOrCurrent(
  30. MidiTrackPtr track,
  31. MidiTrack::const_iterator it)
  32. {
  33. if (it == track->end()) {
  34. return it; // if we are at the end, give up
  35. }
  36. for (bool done = false; !done; ) {
  37. if (it == track->end()) {
  38. done = true;
  39. }
  40. MidiEventPtr evt = it->second;
  41. if (evt->type == MidiEvent::Type::Note) {
  42. done = true;
  43. } else if (evt->type == MidiEvent::Type::End) {
  44. done = true;
  45. } else {
  46. assert(false);
  47. ++it;
  48. }
  49. }
  50. return it;
  51. }
  52. /**
  53. * returns track.end if can't find a note
  54. */
  55. static MidiTrack::const_iterator findPrevNoteOrCurrent(
  56. MidiTrackPtr track,
  57. MidiTrack::const_iterator it)
  58. {
  59. for (bool done = false; !done; ) {
  60. MidiEventPtr evt = it->second;
  61. switch (evt->type) {
  62. case MidiEvent::Type::Note:
  63. done = true; // if we are on a note, then we can accept that
  64. break;
  65. case MidiEvent::Type::End:
  66. if (it == track->begin()) {
  67. return track->end(); // Empty track, can't dec end ptr, so return "fail"
  68. } else {
  69. --it; // try prev
  70. }
  71. break;
  72. default:
  73. assert(false);
  74. if (it == track->begin()) {
  75. return track->end(); // Empty track, can't dec end ptr, so return "fail"
  76. } else {
  77. --it; // try prev
  78. }
  79. }
  80. }
  81. return it;
  82. }
  83. static void selectNextNoteOrCurrent(
  84. MidiTrackPtr track,
  85. MidiTrack::const_iterator it,
  86. MidiSelectionModelPtr selection)
  87. {
  88. it = findNextNoteOrCurrent(track, it);
  89. if (it == track->end()) {
  90. selection->clear();
  91. } else {
  92. MidiEventPtr evt = it->second;
  93. if (evt->type == MidiEvent::Type::End) {
  94. selection->clear();
  95. } else {
  96. selection->select(evt);
  97. }
  98. }
  99. }
  100. static void selectPrevNoteOrCurrent(
  101. MidiTrackPtr track,
  102. MidiTrack::const_iterator it,
  103. MidiSelectionModelPtr selection)
  104. {
  105. it = findPrevNoteOrCurrent(track, it);
  106. if (it == track->end()) {
  107. // If we can't find a good one, give up
  108. selection->clear();
  109. } else {
  110. MidiEventPtr evt = it->second;
  111. if (evt->type == MidiEvent::Type::End) {
  112. selection->clear();
  113. } else {
  114. selection->select(evt);
  115. }
  116. }
  117. }
  118. void MidiEditor::selectNextNote()
  119. {
  120. seq()->assertValid();
  121. MidiTrackPtr track = getTrack();
  122. assert(track);
  123. if (seq()->selection->empty()) {
  124. selectNextNoteOrCurrent(track, track->begin(), seq()->selection);
  125. } else {
  126. assert(seq()->selection->size() == 1); // can't handle multi select yet
  127. MidiEventPtr evt = *seq()->selection->begin();
  128. assert(evt->type == MidiEvent::Type::Note);
  129. // find the event in the track
  130. auto it = track->findEventDeep(*evt);
  131. if (it == track->end()) {
  132. assert(false);
  133. }
  134. ++it;
  135. selectNextNoteOrCurrent(track, it, seq()->selection);
  136. }
  137. updateCursor();
  138. seq()->context->adjustViewportForCursor();
  139. }
  140. // Move to edit context?
  141. void MidiEditor::updateCursor()
  142. {
  143. if (seq()->selection->empty()) {
  144. return;
  145. }
  146. MidiNoteEventPtr firstNote;
  147. // If cursor is already in selection, leave it there.
  148. for (auto it : *seq()->selection) {
  149. MidiEventPtr ev = it;
  150. MidiNoteEventPtr note = safe_cast<MidiNoteEvent>(ev);
  151. if (note) {
  152. if (!firstNote) {
  153. firstNote = note;
  154. }
  155. if ((note->startTime == seq()->context->cursorTime()) &&
  156. (note->pitchCV == seq()->context->cursorPitch())) {
  157. return;
  158. }
  159. }
  160. }
  161. seq()->context->setCursorTime(firstNote->startTime);
  162. seq()->context->setCursorPitch(firstNote->pitchCV);
  163. }
  164. void MidiEditor::selectPrevNote()
  165. {
  166. //assert(song);
  167. //assert(selection);
  168. seq()->assertValid();
  169. MidiTrackPtr track = getTrack();
  170. assert(track);
  171. if (seq()->selection->empty()) {
  172. // for prev, let's do same as next - if nothing selected, select first
  173. selectPrevNoteOrCurrent(track, --track->end(), seq()->selection);
  174. } else {
  175. // taken from next..
  176. assert(seq()->selection->size() == 1); // can't handle multi select yet
  177. MidiEventPtr evt = *seq()->selection->begin();
  178. assert(evt->type == MidiEvent::Type::Note);
  179. // find the event in the track
  180. auto it = track->findEventDeep(*evt);
  181. if (it == track->begin()) {
  182. seq()->selection->clear(); // if we are at start, can't dec.unselect
  183. return;
  184. }
  185. --it;
  186. selectPrevNoteOrCurrent(track, it, seq()->selection);
  187. }
  188. updateCursor();
  189. seq()->context->adjustViewportForCursor();
  190. }
  191. void MidiEditor::changePitch(int semitones)
  192. {
  193. #if 1
  194. ReplaceDataCommandPtr cmd = ReplaceDataCommand::makeChangePitchCommand(seq(), semitones);
  195. seq()->undo->execute(cmd);
  196. seq()->assertValid();
  197. float deltaCV = PitchUtils::semitone * semitones;
  198. #else
  199. float deltaCV = PitchUtils::semitone * semitones;
  200. for (auto ev : *seq()->selection) {
  201. MidiNoteEventPtr note = safe_cast<MidiNoteEvent>(ev); // for now selection is all notes
  202. note->pitchCV += deltaCV;
  203. }
  204. #endif
  205. // Now fixup selection and viewport
  206. seq()->context->setCursorPitch(seq()->context->cursorPitch() + deltaCV);
  207. seq()->context->adjustViewportForCursor();
  208. seq()->context->assertCursorInViewport();
  209. }
  210. void MidiEditor::changeStartTime(bool ticks, int amount)
  211. {
  212. MidiLocker l(seq()->song->lock);
  213. assert(!ticks); // not implemented yet
  214. assert(amount != 0);
  215. float advanceAmount = amount * 1.f / 4.f; // hard code units to 1/16th notes
  216. #if 1
  217. ReplaceDataCommandPtr cmd = ReplaceDataCommand::makeChangeStartTimeCommand(seq(), advanceAmount);
  218. seq()->undo->execute(cmd);
  219. seq()->assertValid();
  220. // after we change start times, we need to put the cursor on the moved notes
  221. seq()->context->setCursorToSelection(seq()->selection);
  222. #else
  223. MidiNoteEventPtr lastNote = safe_cast<MidiNoteEvent>(seq()->selection->getLast());
  224. float lastTime = lastNote->startTime + lastNote->duration;
  225. lastTime += advanceAmount;
  226. extendTrackToMinDuration(lastTime);
  227. bool setCursor = false;
  228. MidiTrackPtr track = seq()->context->getTrack();
  229. for (auto ev : *seq()->selection) {
  230. MidiNoteEventPtr note = safe_cast<MidiNoteEvent>(ev); // for now selection is all notes
  231. track->deleteEvent(*note);
  232. note->startTime += advanceAmount;
  233. note->startTime = std::max(0.f, note->startTime);
  234. track->insertEvent(note);
  235. if (!setCursor) {
  236. seq()->context->setCursorTime(note->startTime);
  237. setCursor = true;
  238. }
  239. }
  240. #endif
  241. seq()->context->adjustViewportForCursor();
  242. seq()->context->assertCursorInViewport();
  243. }
  244. void MidiEditor::changeDuration(bool ticks, int amount)
  245. {
  246. assert(!ticks); // not implemented yet
  247. assert(amount != 0);
  248. float advanceAmount = amount * 1.f / 4.f; // hard code units to 1/16th notes
  249. #if 1
  250. ReplaceDataCommandPtr cmd = ReplaceDataCommand::makeChangeDurationCommand(seq(), advanceAmount);
  251. seq()->undo->execute(cmd);
  252. seq()->assertValid();
  253. #else
  254. for (auto ev : *seq()->selection) {
  255. MidiNoteEventPtr note = safe_cast<MidiNoteEvent>(ev); // for now selection is all notes
  256. note->duration += advanceAmount;
  257. // arbitrary min limit.
  258. note->duration = std::max(.001f, note->duration);
  259. }
  260. #endif
  261. }
  262. void MidiEditor::assertCursorInSelection()
  263. {
  264. bool foundIt = false;
  265. assert(!seq()->selection->empty());
  266. for (auto it : *seq()->selection) {
  267. if (seq()->context->cursorTime() == it->startTime) {
  268. foundIt = true;
  269. }
  270. }
  271. assert(foundIt);
  272. }
  273. void MidiEditor::advanceCursor(bool ticks, int amount)
  274. {
  275. assert(!ticks); // not implemented yet
  276. assert(amount != 0);
  277. seq()->context->assertCursorInViewport();
  278. float advanceAmount = amount * 1.f / 4.f; // hard code units to 1/16th notes
  279. seq()->context->setCursorTime(seq()->context->cursorTime() + advanceAmount);
  280. seq()->context->setCursorTime(std::max(0.f, seq()->context->cursorTime()));
  281. updateSelectionForCursor();
  282. seq()->context->adjustViewportForCursor();
  283. seq()->context->assertCursorInViewport();
  284. seq()->assertValid();
  285. }
  286. void MidiEditor::extendTrackToMinDuration(float neededLength)
  287. {
  288. auto track = seq()->context->getTrack();
  289. float curLength = track->getLength();
  290. if (neededLength > curLength) {
  291. float need = neededLength;
  292. float needBars = need / 4.f;
  293. float roundedBars = std::round(needBars + 1.f);
  294. float duration = roundedBars * 4;
  295. std::shared_ptr<MidiEndEvent> end = track->getEndEvent();
  296. track->deleteEvent(*end);
  297. track->insertEnd(duration);
  298. }
  299. }
  300. void MidiEditor::insertNote()
  301. {
  302. MidiLocker l(seq()->song->lock);
  303. #if 1
  304. MidiNoteEventPtr note = std::make_shared<MidiNoteEvent>();
  305. note->startTime = seq()->context->cursorTime();
  306. note->pitchCV = seq()->context->cursorPitch();
  307. note->duration = 1; // for now, fixed to quarter
  308. auto cmd = ReplaceDataCommand::makeInsertNoteCommand(seq(), note);
  309. seq()->undo->execute(cmd);
  310. #else
  311. // for now, fixed to quarter
  312. extendTrackToMinDuration(seq()->context->cursorTime() + 1.f);
  313. auto track = seq()->context->getTrack();
  314. // for now, assume no note there
  315. MidiNoteEventPtr note = std::make_shared<MidiNoteEvent>();
  316. note->pitchCV = seq()->context->cursorPitch();
  317. note->startTime = seq()->context->cursorTime();
  318. note->duration = 1.0f; // for now, fixed to quarter
  319. track->insertEvent(note);
  320. #endif
  321. updateSelectionForCursor();
  322. seq()->assertValid();
  323. }
  324. void MidiEditor::deleteNote()
  325. {
  326. if (seq()->selection->empty()) {
  327. return;
  328. }
  329. auto cmd = ReplaceDataCommand::makeDeleteCommand(seq());
  330. seq()->undo->execute(cmd);
  331. // TODO: move selection into undo
  332. seq()->selection->clear();
  333. }
  334. void MidiEditor::updateSelectionForCursor()
  335. {
  336. seq()->selection->clear();
  337. const int cursorSemi = PitchUtils::cvToSemitone(seq()->context->cursorPitch());
  338. // iterator over all the notes that are in the edit context
  339. auto start = seq()->context->startTime();
  340. auto end = seq()->context->endTime();
  341. MidiTrack::note_iterator_pair notes = getTrack()->timeRangeNotes(start, end);
  342. for (auto it = notes.first; it != notes.second; ++it) {
  343. MidiNoteEventPtr note = safe_cast<MidiNoteEvent>(it->second);
  344. const auto startTime = note->startTime;
  345. const auto endTime = note->startTime + note->duration;
  346. if ((PitchUtils::cvToSemitone(note->pitchCV) == cursorSemi) &&
  347. (startTime <= seq()->context->cursorTime()) &&
  348. (endTime > seq()->context->cursorTime())) {
  349. seq()->selection->select(note);
  350. return;
  351. }
  352. }
  353. }
  354. void MidiEditor::changeCursorPitch(int semitones)
  355. {
  356. float pitch = seq()->context->cursorPitch() + (semitones * PitchUtils::semitone);
  357. pitch = std::max(pitch, -5.f);
  358. pitch = std::min(pitch, 5.f);
  359. seq()->context->setCursorPitch(pitch);
  360. seq()->context->scrollViewportToCursorPitch();
  361. updateSelectionForCursor();
  362. }
  363. void MidiEditor::setNoteEditorAttribute(MidiEditorContext::NoteAttribute attr)
  364. {
  365. seq()->context->noteAttribute = attr;
  366. }