#include "Sequencer.h" #include "PlaybackScreen.h" #include "colors.h" #include #include PlaybackScreen::PlaybackScreen(Sequencer& seq) : parent(seq), beatDisplayOffset(0), messageDisplayOffset(0) { } void PlaybackScreen::enable() { if(sequencer.beats.size() == 0) { beatDisplayOffset = 0; } else { beatDisplayOffset = std::min( beatDisplayOffset, (Player::BeatIndex)(sequencer.beats.size() - 1) / beatDisplayWidth * beatDisplayWidth ); } if(messageDisplayOffset >= sequencer.messages.size()) { messageDisplayOffset = sequencer.messages.size() - 1; } refreshAll(); parent::enable(); } void PlaybackScreen::beforeBeat(Player::BeatIndex beat) { // std::cout << "before beat #" << beat << std::endl; beatDisplayOffset = beat / beatDisplayWidth * beatDisplayWidth; refreshAll(); } void PlaybackScreen::afterBeat(Player::BeatIndex beat) { } void PlaybackScreen::keyPressed(unsigned char x, unsigned char y) { // std::cout << "clicked x=" << (int)x << ", y=" << (int)y << std::endl; if(x == 8) { // very right std::size_t messageIndex = y + messageDisplayOffset; if(messageIndex < sequencer.messages.size()) { std::shared_ptr msg_ptr = sequencer.messages[messageIndex]; msg_ptr->print(std::cout); sequencer.midiOut.sendMessage(*msg_ptr); } } else if(y == 8) { // very top bool playing = sequencer.player.isPlaying(); switch(x) { case 0: // down if(messageDisplayOffset > 0) { int offset = messageDisplayOffset; offset -= messageDisplayShift; messageDisplayOffset = std::max(0, offset); } break; case 1: // up if(messageDisplayOffset + beatsHeight < sequencer.messages.size()) { messageDisplayOffset = std::min( (MessageDisplayOffset)sequencer.messages.size() - 1, messageDisplayOffset + messageDisplayShift ); } break; case 2: // left if(!playing && beatDisplayOffset > 0) { beatDisplayOffset = std::max(0, (int)beatDisplayOffset - beatDisplayWidth); } break; case 3: // right if(!playing && (beatDisplayOffset + beatDisplayWidth) < sequencer.beats.size()) { beatDisplayOffset += beatDisplayWidth; } break; case 5: disable(); sequencer.configScreen.enable(); break; case 7: // start / stop if(playing) { sequencer.player.stop(); } else { sequencer.player.loop(); } break; } } else { std::size_t messageIndex = y + messageDisplayOffset; Player::BeatIndex beatIndex = x + beatDisplayOffset; if(beatIndex < sequencer.beats.size() && messageIndex < sequencer.messages.size()) { std::shared_ptr noteOnMessage_ptr = std::dynamic_pointer_cast(sequencer.messages[messageIndex]); if(noteOnMessage_ptr) { toggleOnMessage(noteOnMessage_ptr, beatIndex); } } } refreshAll(); } void PlaybackScreen::keyReleased(unsigned char x, unsigned char y) { if(x == 8) { // very right // this might not be the right message std::size_t messageIndex = y + messageDisplayOffset; if(messageIndex < sequencer.messages.size()) { std::shared_ptr msgOn_ptr = std::dynamic_pointer_cast(sequencer.messages[messageIndex]); if(msgOn_ptr) { sequencer.midiOut.sendMessage(msgOn_ptr->toOffMessage(0)); } } } refreshAll(); } void PlaybackScreen::refresh(unsigned char x, unsigned char y) { if(x == 8) { // very right std::size_t messageIndex = y + messageDisplayOffset; if(messageIndex < sequencer.messages.size()) { setColor(x, y, getKeyPressed(x, y) ? colors::activeOption : colors::inactiveOption); } else { setColor(x, y, colors::dark); } } else if(y == 8) { // very top bool playing = sequencer.player.isPlaying(); switch(x) { case 0: // down if(messageDisplayOffset > 0) { setColor(x, y, colors::inactiveOption); } else { setColor(x, y, colors::dark); } break; case 1: // up if(messageDisplayOffset + beatsHeight < sequencer.messages.size()) { setColor(x, y, colors::inactiveOption); } else { setColor(x, y, colors::dark); } break; case 2: // left if(!playing && beatDisplayOffset > 0) { setColor(x, y, colors::inactiveOption); } else { setColor(x, y, colors::dark); } break; case 3: // right if(!playing && (beatDisplayOffset + beatDisplayWidth) < sequencer.beats.size()) { setColor(x, y, colors::inactiveOption); } else { setColor(x, y, colors::dark); } break; case 5: setColor(x, y, colors::inactiveOption); break; case 7: // start / stop if(playing) { setColor(x, y, colors::activeOption); } else { setColor(x, y, colors::inactiveOption); } break; } } else { std::size_t messageIndex = y + messageDisplayOffset; Player::BeatIndex beatIndex = x + beatDisplayOffset; if(beatIndex < sequencer.beats.size() && messageIndex < sequencer.messages.size()) { std::shared_ptr noteOnMessage_ptr = std::dynamic_pointer_cast(sequencer.messages[messageIndex]); if(noteOnMessage_ptr) { setColor(x, y, getBeatColor(noteOnMessage_ptr, beatIndex)); } } else { setColor(x, y, colors::dark); } } } void PlaybackScreen::toggleOnMessage(std::shared_ptr onMsg_ptr, Player::BeatIndex beatIndex) { onMsg_ptr->print(std::cout); midi::NoteOffMessage offMsg = onMsg_ptr->toOffMessage(0); const BeatSequenceNoteInformation& noteInfo = sequencer.beats.getNoteInfo(onMsg_ptr->channel, onMsg_ptr->pitch); midi::MessageList& beat = sequencer.beats[beatIndex]; midi::MessageList::iterator beatOnMsg_it = beat.find(*onMsg_ptr); if(beatOnMsg_it == beat.end()) { // further steps depend on whether a off message has been inserted at the current position midi::MessageList::iterator beatOffMsg_it = beat.find(onMsg_ptr->toOffMessage(0)); BeatIndex nextBeatIndex = (beatIndex + 1) % sequencer.beats.size(); if(beatOffMsg_it == beat.end()) { // is there a ongoing msg, that has to be stopped first? BeatIndexMultiset::const_iterator precedingOnMsgBeatIndex_it = noteInfo.onBeatIndices.less_or_max(beatIndex); if(precedingOnMsgBeatIndex_it != noteInfo.onBeatIndices.end()) { // where is this preceding note being stopped? BeatIndexMultiset::const_iterator precedingOnFollowingOffMsgBeatIndex_it = noteInfo.offBeatIndices.greater_or_min(*precedingOnMsgBeatIndex_it); std::cout << "preceding at " << *precedingOnMsgBeatIndex_it << std::endl; std::cout << "\tstop of preceding at " << *precedingOnFollowingOffMsgBeatIndex_it << std::endl; std::cout << "beatsInSequence(" << *precedingOnMsgBeatIndex_it << ", " << beatIndex + 1 << ", " << *precedingOnFollowingOffMsgBeatIndex_it << ")" << std::endl; if(precedingOnFollowingOffMsgBeatIndex_it == noteInfo.offBeatIndices.end()) { // this preceding note was never stopped // so stop it before the new note sequencer.beats.pushFrontMessage(beatIndex, std::shared_ptr(new midi::NoteOffMessage(offMsg))); } else if (beatsInSequence(*precedingOnMsgBeatIndex_it, beatIndex, *precedingOnFollowingOffMsgBeatIndex_it)) { // this preceding note is currently being stopped after the beat // of the new note. so stop it before the new note. sequencer.beats.pushFrontMessage(beatIndex, std::shared_ptr(new midi::NoteOffMessage(offMsg))); // since the new note will be stopped on the next beat, // remove this extra off message, which no longer stops the preceding note. midi::MessageList& precedingOnFollowingOffBeat = sequencer.beats[*precedingOnFollowingOffMsgBeatIndex_it]; const midi::MessageList::iterator precedingOnFollowingOffMsg_it = precedingOnFollowingOffBeat.find(offMsg); assert(precedingOnFollowingOffMsg_it != precedingOnFollowingOffBeat.end()); sequencer.beats.eraseMessage( *precedingOnFollowingOffMsgBeatIndex_it, precedingOnFollowingOffMsg_it ); } } // on msg on current beat & off msg on next beat sequencer.beats.pushBackMessage(beatIndex, std::dynamic_pointer_cast(onMsg_ptr)); } else { // move off to next beat sequencer.beats.eraseMessage(beatIndex, beatOffMsg_it); } // stop at following beat sequencer.beats.pushFrontMessage(nextBeatIndex, std::shared_ptr(new midi::NoteOffMessage(offMsg))); } else { // remove following off message, if not relevant for other on message. BeatIndexMultiset::const_iterator followingOffBeatIndex_it = noteInfo.offBeatIndices.greater_or_min(beatIndex); if(followingOffBeatIndex_it != noteInfo.offBeatIndices.end()) { BeatIndex followingOffBeatIndex = *followingOffBeatIndex_it; BeatIndexMultiset::const_iterator offPrecedingOnBeatIndex_it = noteInfo.onBeatIndices.less_or_max(followingOffBeatIndex); assert(offPrecedingOnBeatIndex_it != noteInfo.onBeatIndices.end()); assert(*offPrecedingOnBeatIndex_it >= beatIndex); if(*offPrecedingOnBeatIndex_it == beatIndex) { // no relevant on message before off found. so off may be removed. midi::MessageList& followingOffBeat = sequencer.beats[followingOffBeatIndex]; midi::MessageList::iterator followingOffMsg_it = followingOffBeat.find(offMsg); assert(followingOffMsg_it != followingOffBeat.end()); sequencer.beats.eraseMessage(*followingOffBeatIndex_it, followingOffMsg_it); } } // previous on without off before to-be-removed on ? BeatIndexMultiset::const_iterator precedingOnBeatIndex_it = noteInfo.onBeatIndices.less_or_max(beatIndex); if(precedingOnBeatIndex_it != noteInfo.onBeatIndices.end()) { std::cout << "previous: " << *precedingOnBeatIndex_it << std::endl; } // remove on message from beat sequencer.beats.eraseMessage(beatIndex, beatOnMsg_it); } sequencer.beats.printRegistry(std::cout); } midi::LaunchpadColor PlaybackScreen::getBeatColor(std::shared_ptr onMsg_ptr, BeatIndex beatIndex) { const BeatSequenceNoteInformation& noteInfo = sequencer.beats.getNoteInfo(onMsg_ptr->channel, onMsg_ptr->pitch); BeatIndexMultiset::const_iterator onIndex_it = noteInfo.onBeatIndices.equal_or_less_or_max(beatIndex); if(onIndex_it == noteInfo.onBeatIndices.end()) { return colors::dark; } else { bool isNextBeat = (sequencer.player.getNextBeat() == beatIndex); if(*onIndex_it == beatIndex) { // on msg on current beat if(noteInfo.offBeatIndices.find(*onIndex_it) != noteInfo.offBeatIndices.end()) { return isNextBeat ? colors::activeOffOnBeat : colors::inactiveOffOnBeat; } else { return isNextBeat ? colors::activeOnBeat : colors::inactiveOnBeat; } } else { // continuing msg from previous beat ? BeatIndexMultiset::const_iterator offIndex_it = noteInfo.offBeatIndices.greater_or_min(*onIndex_it); if(offIndex_it == noteInfo.offBeatIndices.end() || beatsInSequence(*onIndex_it, beatIndex, (*offIndex_it - 1) % sequencer.beats.size())) { return isNextBeat ? colors::activeContinuingBeat : colors::inactiveContinuingBeat; } } } } bool PlaybackScreen::beatsInSequence(BeatIndex a, BeatIndex b, BeatIndex c) { if(a <= b) { return b <= c || c < a; } else { return b <= c && c < a; } }