Mercurial > hg > touchkeys
view Source/TouchKeys/LogPlayback.cpp @ 20:dfff66c07936
Lots of minor changes to support building on Visual Studio. A few MSVC-specific #ifdefs to eliminate things Visual Studio doesn't like. This version now compiles on Windows (provided liblo, Juce and pthread are present) but the TouchKeys device support is not yet enabled. Also, the code now needs to be re-checked on Mac and Linux.
author | Andrew McPherson <andrewm@eecs.qmul.ac.uk> |
---|---|
date | Sun, 09 Feb 2014 18:40:51 +0000 |
parents | 3580ffe87dc8 |
children | ff5d65c69e73 |
line wrap: on
line source
/* TouchKeys: multi-touch musical keyboard control software Copyright (c) 2013 Andrew McPherson This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. ===================================================================== LogPlayback.cpp: basic functions for playing back a recorded TouchKeys log. */ #include "LogPlayback.h" LogPlayback::LogPlayback(PianoKeyboard& keyboard, MidiInputController& midi) : keyboard_(keyboard), midiInputController_(midi), open_(false), playing_(false), paused_(false), usingTouch_(false), usingMidi_(false), playbackRate_(1.0), nextTouchMidiNote_(0), nextTouchTimestamp_(0), nextMidiTimestamp_(0), lastMidiTimestamp_(0), timestampOffset_(0) { // Create a statically bound call to the performMapping() method that // we use each time we schedule a new mapping touchAction_ = boost::bind(&LogPlayback::nextTouchEvent, this); midiAction_ = boost::bind(&LogPlayback::nextMidiEvent, this); } LogPlayback::~LogPlayback() { if(open_) closeLogFiles(); } // File management. Open a touch and/or MIDI file. Returns true on success. // Pass a blank string to either one of the paths to not use that form of data capture bool LogPlayback::openLogFiles(string const& touchPath, string const& midiPath) { touchLog_.open (touchPath.c_str(), ios::in | ios::binary); midiLog_.open (midiPath.c_str(), ios::in | ios::binary); usingTouch_ = touchLog_.is_open(); usingMidi_ = midiLog_.is_open(); // Check for bad file paths if(!usingTouch_ && touchPath != "") return false; if(!usingMidi_ && midiPath != "") return false; if(!usingTouch_ && !usingMidi_) return false; // Set defaults open_ = true; playing_ = paused_ = false; playbackRate_ = 1.0; return true; } // Close the current files void LogPlayback::closeLogFiles() { if(!open_) return; if(playing_) stopPlayback(); touchLog_.close(); midiLog_.close(); open_ = playing_ = paused_ = false; } // Start, stop, pause, resume void LogPlayback::startPlayback(timestamp_type startingTimestamp) { if(!open_) return; // Start the playback scheduler thread playbackScheduler_.start(0); timestamp_type firstTouchTimestamp, firstMidiTimestamp; // Register actions on the scheduler thread if(usingTouch_) { readNextTouchFrame(); firstTouchTimestamp = nextTouchTimestamp_; timestampOffset_ = playbackScheduler_.currentTimestamp() - firstTouchTimestamp; } if(usingMidi_) { readNextMidiFrame(); firstMidiTimestamp = nextMidiTimestamp_; lastMidiTimestamp_ = nextMidiTimestamp_; // First timestamp difference is 0 // Timestamp offset is to first MIDI event, unless there's an earlier touch event if(!(usingTouch_ && (firstTouchTimestamp < firstMidiTimestamp))) timestampOffset_ = playbackScheduler_.currentTimestamp() - firstMidiTimestamp; } playing_ = true; paused_ = false; if(usingTouch_) playbackScheduler_.schedule(this, touchAction_, playbackScheduler_.currentTimestamp()); if(usingMidi_) playbackScheduler_.schedule(this, midiAction_, playbackScheduler_.currentTimestamp()); } void LogPlayback::stopPlayback() { playing_ = paused_ = false; // Stop the playback scheduler thread playbackScheduler_.stop(); playbackScheduler_.unschedule(this); } // Pause a currently playing file. Save the pause time so the offset // can be recalculated when it resumes void LogPlayback::pausePlayback() { if(open_ && playing_) { playbackScheduler_.unschedule(this); // TODO: consider thread safety: what happens if this comes during one of the scheduled calls? paused_ = true; pauseTimestamp_ = playbackScheduler_.currentTimestamp(); } } // Resume playback after a pause void LogPlayback::resumePlayback() { if(paused_) { paused_ = false; timestamp_type resumeTimestamp = playbackScheduler_.currentTimestamp(); // Update the timestamp offset timestampOffset_ += resumeTimestamp - pauseTimestamp_; // Reschedule calls if(usingTouch_) playbackScheduler_.schedule(this, touchAction_, nextTouchTimestamp_ + timestampOffset_); if(usingMidi_) playbackScheduler_.schedule(this, touchAction_, nextMidiTimestamp_ + timestampOffset_); } } // Seek to a timestamp in the file void LogPlayback::seekPlayback(timestamp_type newTimestamp) { // Advance through the file until we reach the indicated timestamp if(!playing_ || !open_) return; // Remove any future actions while we perform the seek playbackScheduler_.unschedule(this); //timestamp_diff_type offset = 0; timestamp_type firstUpcomingTimestamp = 0; if(usingTouch_) { //timestamp_type lastTimestamp = nextTouchTimestamp_; // TODO: this assumes the seek is moving forward while(nextTouchTimestamp_ <= newTimestamp) { if(!readNextTouchFrame()) { // EOF or error usingTouch_ = false; if(!usingMidi_) playing_ = paused_ = false; break; } } // Now we have the first event scheduled after the seek location // Update timestamp offset to continue playback from here. //offset = nextTouchTimestamp_ - lastTimestamp; firstUpcomingTimestamp = nextTouchTimestamp_; } if(usingMidi_) { //timestamp_type lastTimestamp = nextMidiTimestamp_; // TODO: this assumes the seek is moving forward while(nextMidiTimestamp_ <= newTimestamp) { if(!readNextMidiFrame()) { // EOF or error usingMidi_ = false; if(!usingTouch_) playing_ = paused_ = false; break; } } // Now we have the first event scheduled after the seek location // Update timestamp offset to continue playback from here. // Use whichever event came first //if(!(usingTouch_ && (nextMidiTimestamp_ - lastTimestamp) > offset)) // offset = (nextMidiTimestamp_ - lastTimestamp); if(!usingTouch_ || nextMidiTimestamp_ < nextTouchTimestamp_); firstUpcomingTimestamp = nextMidiTimestamp_; } // Update the timestamp offset timestampOffset_ = playbackScheduler_.currentTimestamp() - firstUpcomingTimestamp; if(usingTouch_) playbackScheduler_.schedule(this, touchAction_, nextTouchTimestamp_ + timestampOffset_); if(usingMidi_) playbackScheduler_.schedule(this, midiAction_, nextMidiTimestamp_ + timestampOffset_); } // Change the playback rate (1.0 being the standard speed) void LogPlayback::changePlaybackRate(float rate) { playbackRate_ = rate; } // Events the scheduler calls when the right time elapses. Find the // next touch or MIDI event and play it back timestamp_type LogPlayback::nextTouchEvent() { if(!playing_ || !open_ || paused_) return 0; // TODO: handle playback rate // Play the most recent stored touch frame if(nextTouchMidiNote_ >= 0 && nextTouchMidiNote_ < 128) { // Use PianoKeyboard timestamps for the messages we send since our scheduler // may have a different idea of time. if(nextTouch_.count == 0) { if(keyboard_.key(nextTouchMidiNote_) != 0) keyboard_.key(nextTouchMidiNote_)->touchOff(keyboard_.schedulerCurrentTimestamp()); /* // Send raw OSC message if enabled if(sendRawOscMessages_) { keyboard_.sendMessage("/touchkeys/raw-off", "iii", octave, key, frame, LO_ARGS_END ); } */ } else { if(keyboard_.key(nextTouchMidiNote_) != 0) keyboard_.key(nextTouchMidiNote_)->touchInsertFrame(nextTouch_, keyboard_.schedulerCurrentTimestamp()); /*if(sendRawOscMessages_) { keyboard_.sendMessage("/touchkeys/raw", "iiifffffff", octave, key, frame, sliderPosition[0], sliderSize[0], sliderPosition[1], sliderSize[1], sliderPosition[2], sliderSize[2], sliderPositionH, LO_ARGS_END ); }*/ } } bool newTouchFound = readNextTouchFrame(); // Go through next touch frames and send them as long as the timestamp is not in the future while(newTouchFound && (nextTouchTimestamp_ + timestampOffset_) <= playbackScheduler_.currentTimestamp()) { if(nextTouchMidiNote_ >= 0 && nextTouchMidiNote_ < 128) { // Use PianoKeyboard timestamps for the messages we send since our scheduler // may have a different idea of time. if(keyboard_.key(nextTouchMidiNote_) != 0) { if(nextTouch_.count == 0) keyboard_.key(nextTouchMidiNote_)->touchOff(keyboard_.schedulerCurrentTimestamp()); else keyboard_.key(nextTouchMidiNote_)->touchInsertFrame(nextTouch_, keyboard_.schedulerCurrentTimestamp()); } } readNextTouchFrame(); } if(!newTouchFound) { // EOF or error usingTouch_ = false; if(!usingMidi_) playing_ = paused_ = false; return 0; } else // Return the timestamp of the next call return (nextTouchTimestamp_ + timestampOffset_); } timestamp_type LogPlayback::nextMidiEvent() { if(!playing_ || !open_ || paused_) return 0; // TODO: handle playback rate // Play the most recent stored touch frame if(nextMidi_.size() >= 3) midiInputController_.handleIncomingMidiMessage(0, MidiMessage(nextMidi_[0], nextMidi_[1], nextMidi_[2])); //midiInputController_.rtMidiCallback(nextMidiTimestamp_ - lastMidiTimestamp_, &nextMidi_, 0); lastMidiTimestamp_ = nextMidiTimestamp_; bool newMidiEventFound = readNextMidiFrame(); // Go through next touch frames and send them as long as the timestamp is not in the future while(newMidiEventFound && (nextMidiTimestamp_ + timestampOffset_) <= playbackScheduler_.currentTimestamp()) { if(nextMidi_.size() >= 3) midiInputController_.handleIncomingMidiMessage(0, MidiMessage(nextMidi_[0], nextMidi_[1], nextMidi_[2])); //midiInputController_.rtMidiCallback(nextMidiTimestamp_ - lastMidiTimestamp_, &nextMidi_, 0); lastMidiTimestamp_ = nextMidiTimestamp_; readNextMidiFrame(); } if(!newMidiEventFound) { // EOF or error usingMidi_ = false; if(!usingTouch_) playing_ = paused_ = false; return 0; } else // Return the timestamp of the next call return (nextMidiTimestamp_ + timestampOffset_); } // Retrieve the next key touch frame from the log file // Return true if a touch was found, false if EOF or an error occurred bool LogPlayback::readNextTouchFrame() { int frameCounter; try { touchLog_.read((char *)&nextTouchTimestamp_, sizeof(timestamp_type)); touchLog_.read((char *)&frameCounter, sizeof(int)); touchLog_.read((char *)&nextTouchMidiNote_, sizeof(int)); touchLog_.read((char *)&nextTouch_, sizeof(KeyTouchFrame)); } catch(...) { cout << "error reading touch\n"; return false; } if(touchLog_.eof()) { cout << "Touch log playback finished\n"; return false; } //cout << "read touch on key " << nextTouchMidiNote_ << " timestamp " << nextTouchTimestamp_ << endl; // TODO: what about frameCounter return true; } // Retrieve the next MIDI frame from the log file // Return true if an event was found, false if EOF or an error occurred bool LogPlayback::readNextMidiFrame() { int midi0, midi1, midi2; try { midiLog_.read((char*)&nextMidiTimestamp_, sizeof (timestamp_type)); midiLog_.read((char*)&midi0, sizeof (int)); midiLog_.read((char*)&midi1, sizeof (int)); midiLog_.read((char*)&midi2, sizeof (int)); } catch(...) { cout << "error reading MIDI\n"; return false; } if(midiLog_.eof()) { cout << "MIDI log playback finished\n"; return false; } nextMidi_.clear(); nextMidi_.push_back((unsigned char)midi0); nextMidi_.push_back((unsigned char)midi1); nextMidi_.push_back((unsigned char)midi2); //cout << "read MIDI data " << (int)midi0 << " " << (int)midi1 << " " << (int)midi2 << endl; return true; }