annotate Source/TouchKeys/LogPlayback.cpp @ 16:61e3c9df4674

Fix bug where TouchKeys standalone mode turns off when mode is changed.
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Mon, 25 Nov 2013 21:36:02 +0000
parents 3580ffe87dc8
children ff5d65c69e73
rev   line source
andrewm@0 1 /*
andrewm@0 2 TouchKeys: multi-touch musical keyboard control software
andrewm@0 3 Copyright (c) 2013 Andrew McPherson
andrewm@0 4
andrewm@0 5 This program is free software: you can redistribute it and/or modify
andrewm@0 6 it under the terms of the GNU General Public License as published by
andrewm@0 7 the Free Software Foundation, either version 3 of the License, or
andrewm@0 8 (at your option) any later version.
andrewm@0 9
andrewm@0 10 This program is distributed in the hope that it will be useful,
andrewm@0 11 but WITHOUT ANY WARRANTY; without even the implied warranty of
andrewm@0 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
andrewm@0 13 GNU General Public License for more details.
andrewm@0 14
andrewm@0 15 You should have received a copy of the GNU General Public License
andrewm@0 16 along with this program. If not, see <http://www.gnu.org/licenses/>.
andrewm@0 17
andrewm@0 18 =====================================================================
andrewm@0 19
andrewm@0 20 LogPlayback.cpp: basic functions for playing back a recorded TouchKeys log.
andrewm@0 21 */
andrewm@0 22
andrewm@0 23 #include "LogPlayback.h"
andrewm@0 24
andrewm@0 25 LogPlayback::LogPlayback(PianoKeyboard& keyboard, MidiInputController& midi)
andrewm@0 26 : keyboard_(keyboard), midiInputController_(midi), open_(false), playing_(false), paused_(false),
andrewm@0 27 usingTouch_(false), usingMidi_(false), playbackRate_(1.0),
andrewm@0 28 nextTouchMidiNote_(0), nextTouchTimestamp_(0), nextMidiTimestamp_(0),
andrewm@0 29 lastMidiTimestamp_(0), timestampOffset_(0)
andrewm@0 30 {
andrewm@0 31 // Create a statically bound call to the performMapping() method that
andrewm@0 32 // we use each time we schedule a new mapping
andrewm@0 33 touchAction_ = boost::bind(&LogPlayback::nextTouchEvent, this);
andrewm@0 34 midiAction_ = boost::bind(&LogPlayback::nextMidiEvent, this);
andrewm@0 35 }
andrewm@0 36
andrewm@0 37 LogPlayback::~LogPlayback()
andrewm@0 38 {
andrewm@0 39 if(open_)
andrewm@0 40 closeLogFiles();
andrewm@0 41 }
andrewm@0 42
andrewm@0 43 // File management. Open a touch and/or MIDI file. Returns true on success.
andrewm@0 44 // Pass a blank string to either one of the paths to not use that form of data capture
andrewm@0 45 bool LogPlayback::openLogFiles(string const& touchPath, string const& midiPath) {
andrewm@0 46 touchLog_.open (touchPath.c_str(), ios::in | ios::binary);
andrewm@0 47 midiLog_.open (midiPath.c_str(), ios::in | ios::binary);
andrewm@0 48
andrewm@0 49 usingTouch_ = touchLog_.is_open();
andrewm@0 50 usingMidi_ = midiLog_.is_open();
andrewm@0 51
andrewm@0 52 // Check for bad file paths
andrewm@0 53 if(!usingTouch_ && touchPath != "")
andrewm@0 54 return false;
andrewm@0 55 if(!usingMidi_ && midiPath != "")
andrewm@0 56 return false;
andrewm@0 57 if(!usingTouch_ && !usingMidi_)
andrewm@0 58 return false;
andrewm@0 59
andrewm@0 60 // Set defaults
andrewm@0 61 open_ = true;
andrewm@0 62 playing_ = paused_ = false;
andrewm@0 63 playbackRate_ = 1.0;
andrewm@0 64 return true;
andrewm@0 65 }
andrewm@0 66
andrewm@0 67 // Close the current files
andrewm@0 68 void LogPlayback::closeLogFiles() {
andrewm@0 69 if(!open_)
andrewm@0 70 return;
andrewm@0 71 if(playing_)
andrewm@0 72 stopPlayback();
andrewm@0 73 touchLog_.close();
andrewm@0 74 midiLog_.close();
andrewm@0 75 open_ = playing_ = paused_ = false;
andrewm@0 76 }
andrewm@0 77
andrewm@0 78 // Start, stop, pause, resume
andrewm@0 79 void LogPlayback::startPlayback(timestamp_type startingTimestamp) {
andrewm@0 80 if(!open_)
andrewm@0 81 return;
andrewm@0 82
andrewm@0 83 // Start the playback scheduler thread
andrewm@0 84 playbackScheduler_.start(0);
andrewm@0 85
andrewm@0 86 timestamp_type firstTouchTimestamp, firstMidiTimestamp;
andrewm@0 87
andrewm@0 88 // Register actions on the scheduler thread
andrewm@0 89 if(usingTouch_) {
andrewm@0 90 readNextTouchFrame();
andrewm@0 91 firstTouchTimestamp = nextTouchTimestamp_;
andrewm@0 92 timestampOffset_ = playbackScheduler_.currentTimestamp() - firstTouchTimestamp;
andrewm@0 93 }
andrewm@0 94 if(usingMidi_) {
andrewm@0 95 readNextMidiFrame();
andrewm@0 96 firstMidiTimestamp = nextMidiTimestamp_;
andrewm@0 97 lastMidiTimestamp_ = nextMidiTimestamp_; // First timestamp difference is 0
andrewm@0 98
andrewm@0 99 // Timestamp offset is to first MIDI event, unless there's an earlier touch event
andrewm@0 100 if(!(usingTouch_ && (firstTouchTimestamp < firstMidiTimestamp)))
andrewm@0 101 timestampOffset_ = playbackScheduler_.currentTimestamp() - firstMidiTimestamp;
andrewm@0 102 }
andrewm@0 103
andrewm@0 104 playing_ = true;
andrewm@0 105 paused_ = false;
andrewm@0 106
andrewm@0 107 if(usingTouch_)
andrewm@0 108 playbackScheduler_.schedule(this, touchAction_, playbackScheduler_.currentTimestamp());
andrewm@0 109 if(usingMidi_)
andrewm@0 110 playbackScheduler_.schedule(this, midiAction_, playbackScheduler_.currentTimestamp());
andrewm@0 111 }
andrewm@0 112
andrewm@0 113 void LogPlayback::stopPlayback() {
andrewm@0 114 playing_ = paused_ = false;
andrewm@0 115
andrewm@0 116 // Stop the playback scheduler thread
andrewm@0 117 playbackScheduler_.stop();
andrewm@0 118 playbackScheduler_.unschedule(this);
andrewm@0 119 }
andrewm@0 120
andrewm@0 121 // Pause a currently playing file. Save the pause time so the offset
andrewm@0 122 // can be recalculated when it resumes
andrewm@0 123 void LogPlayback::pausePlayback() {
andrewm@0 124 if(open_ && playing_) {
andrewm@0 125 playbackScheduler_.unschedule(this);
andrewm@0 126
andrewm@0 127 // TODO: consider thread safety: what happens if this comes during one of the scheduled calls?
andrewm@0 128
andrewm@0 129 paused_ = true;
andrewm@0 130 pauseTimestamp_ = playbackScheduler_.currentTimestamp();
andrewm@0 131 }
andrewm@0 132 }
andrewm@0 133
andrewm@0 134 // Resume playback after a pause
andrewm@0 135 void LogPlayback::resumePlayback() {
andrewm@0 136 if(paused_) {
andrewm@0 137 paused_ = false;
andrewm@0 138 timestamp_type resumeTimestamp = playbackScheduler_.currentTimestamp();
andrewm@0 139
andrewm@0 140 // Update the timestamp offset
andrewm@0 141 timestampOffset_ += resumeTimestamp - pauseTimestamp_;
andrewm@0 142
andrewm@0 143 // Reschedule calls
andrewm@0 144 if(usingTouch_)
andrewm@0 145 playbackScheduler_.schedule(this, touchAction_, nextTouchTimestamp_ + timestampOffset_);
andrewm@0 146 if(usingMidi_)
andrewm@0 147 playbackScheduler_.schedule(this, touchAction_, nextMidiTimestamp_ + timestampOffset_);
andrewm@0 148 }
andrewm@0 149 }
andrewm@0 150
andrewm@0 151 // Seek to a timestamp in the file
andrewm@0 152 void LogPlayback::seekPlayback(timestamp_type newTimestamp) {
andrewm@0 153 // Advance through the file until we reach the indicated timestamp
andrewm@0 154
andrewm@0 155 if(!playing_ || !open_)
andrewm@0 156 return;
andrewm@0 157
andrewm@0 158 // Remove any future actions while we perform the seek
andrewm@0 159 playbackScheduler_.unschedule(this);
andrewm@0 160 //timestamp_diff_type offset = 0;
andrewm@0 161 timestamp_type firstUpcomingTimestamp = 0;
andrewm@0 162
andrewm@0 163 if(usingTouch_) {
andrewm@0 164 //timestamp_type lastTimestamp = nextTouchTimestamp_;
andrewm@0 165
andrewm@0 166 // TODO: this assumes the seek is moving forward
andrewm@0 167 while(nextTouchTimestamp_ <= newTimestamp) {
andrewm@0 168 if(!readNextTouchFrame()) { // EOF or error
andrewm@0 169 usingTouch_ = false;
andrewm@0 170 if(!usingMidi_)
andrewm@0 171 playing_ = paused_ = false;
andrewm@0 172 break;
andrewm@0 173 }
andrewm@0 174 }
andrewm@0 175
andrewm@0 176 // Now we have the first event scheduled after the seek location
andrewm@0 177 // Update timestamp offset to continue playback from here.
andrewm@0 178
andrewm@0 179 //offset = nextTouchTimestamp_ - lastTimestamp;
andrewm@0 180 firstUpcomingTimestamp = nextTouchTimestamp_;
andrewm@0 181 }
andrewm@0 182 if(usingMidi_) {
andrewm@0 183 //timestamp_type lastTimestamp = nextMidiTimestamp_;
andrewm@0 184
andrewm@0 185 // TODO: this assumes the seek is moving forward
andrewm@0 186 while(nextMidiTimestamp_ <= newTimestamp) {
andrewm@0 187 if(!readNextMidiFrame()) { // EOF or error
andrewm@0 188 usingMidi_ = false;
andrewm@0 189 if(!usingTouch_)
andrewm@0 190 playing_ = paused_ = false;
andrewm@0 191 break;
andrewm@0 192 }
andrewm@0 193 }
andrewm@0 194
andrewm@0 195 // Now we have the first event scheduled after the seek location
andrewm@0 196 // Update timestamp offset to continue playback from here.
andrewm@0 197 // Use whichever event came first
andrewm@0 198
andrewm@0 199 //if(!(usingTouch_ && (nextMidiTimestamp_ - lastTimestamp) > offset))
andrewm@0 200 // offset = (nextMidiTimestamp_ - lastTimestamp);
andrewm@0 201 if(!usingTouch_ || nextMidiTimestamp_ < nextTouchTimestamp_);
andrewm@0 202 firstUpcomingTimestamp = nextMidiTimestamp_;
andrewm@0 203 }
andrewm@0 204
andrewm@0 205 // Update the timestamp offset
andrewm@0 206 timestampOffset_ = playbackScheduler_.currentTimestamp() - firstUpcomingTimestamp;
andrewm@0 207
andrewm@0 208 if(usingTouch_)
andrewm@0 209 playbackScheduler_.schedule(this, touchAction_, nextTouchTimestamp_ + timestampOffset_);
andrewm@0 210 if(usingMidi_)
andrewm@0 211 playbackScheduler_.schedule(this, midiAction_, nextMidiTimestamp_ + timestampOffset_);
andrewm@0 212 }
andrewm@0 213
andrewm@0 214 // Change the playback rate (1.0 being the standard speed)
andrewm@0 215 void LogPlayback::changePlaybackRate(float rate) {
andrewm@0 216 playbackRate_ = rate;
andrewm@0 217 }
andrewm@0 218
andrewm@0 219 // Events the scheduler calls when the right time elapses. Find the
andrewm@0 220 // next touch or MIDI event and play it back
andrewm@0 221 timestamp_type LogPlayback::nextTouchEvent() {
andrewm@0 222 if(!playing_ || !open_ || paused_)
andrewm@0 223 return 0;
andrewm@0 224
andrewm@0 225 // TODO: handle playback rate
andrewm@0 226
andrewm@0 227 // Play the most recent stored touch frame
andrewm@0 228 if(nextTouchMidiNote_ >= 0 && nextTouchMidiNote_ < 128) {
andrewm@0 229 // Use PianoKeyboard timestamps for the messages we send since our scheduler
andrewm@0 230 // may have a different idea of time.
andrewm@0 231
andrewm@0 232 if(nextTouch_.count == 0) {
andrewm@0 233 if(keyboard_.key(nextTouchMidiNote_) != 0)
andrewm@0 234 keyboard_.key(nextTouchMidiNote_)->touchOff(keyboard_.schedulerCurrentTimestamp());
andrewm@0 235 /*
andrewm@0 236 // Send raw OSC message if enabled
andrewm@0 237 if(sendRawOscMessages_) {
andrewm@0 238 keyboard_.sendMessage("/touchkeys/raw-off", "iii",
andrewm@0 239 octave, key, frame,
andrewm@0 240 LO_ARGS_END );
andrewm@0 241 }
andrewm@0 242 */
andrewm@0 243 }
andrewm@0 244 else {
andrewm@0 245 if(keyboard_.key(nextTouchMidiNote_) != 0)
andrewm@0 246 keyboard_.key(nextTouchMidiNote_)->touchInsertFrame(nextTouch_,
andrewm@0 247 keyboard_.schedulerCurrentTimestamp());
andrewm@0 248 /*if(sendRawOscMessages_) {
andrewm@0 249 keyboard_.sendMessage("/touchkeys/raw", "iiifffffff",
andrewm@0 250 octave, key, frame,
andrewm@0 251 sliderPosition[0],
andrewm@0 252 sliderSize[0],
andrewm@0 253 sliderPosition[1],
andrewm@0 254 sliderSize[1],
andrewm@0 255 sliderPosition[2],
andrewm@0 256 sliderSize[2],
andrewm@0 257 sliderPositionH,
andrewm@0 258 LO_ARGS_END );
andrewm@0 259 }*/
andrewm@0 260 }
andrewm@0 261 }
andrewm@0 262
andrewm@0 263 bool newTouchFound = readNextTouchFrame();
andrewm@0 264
andrewm@0 265 // Go through next touch frames and send them as long as the timestamp is not in the future
andrewm@0 266 while(newTouchFound && (nextTouchTimestamp_ + timestampOffset_) <= playbackScheduler_.currentTimestamp()) {
andrewm@0 267 if(nextTouchMidiNote_ >= 0 && nextTouchMidiNote_ < 128) {
andrewm@0 268 // Use PianoKeyboard timestamps for the messages we send since our scheduler
andrewm@0 269 // may have a different idea of time.
andrewm@0 270
andrewm@0 271 if(keyboard_.key(nextTouchMidiNote_) != 0) {
andrewm@0 272 if(nextTouch_.count == 0)
andrewm@0 273 keyboard_.key(nextTouchMidiNote_)->touchOff(keyboard_.schedulerCurrentTimestamp());
andrewm@0 274 else
andrewm@0 275 keyboard_.key(nextTouchMidiNote_)->touchInsertFrame(nextTouch_,
andrewm@0 276 keyboard_.schedulerCurrentTimestamp());
andrewm@0 277 }
andrewm@0 278 }
andrewm@0 279
andrewm@0 280 readNextTouchFrame();
andrewm@0 281 }
andrewm@0 282
andrewm@0 283 if(!newTouchFound) { // EOF or error
andrewm@0 284 usingTouch_ = false;
andrewm@0 285 if(!usingMidi_)
andrewm@0 286 playing_ = paused_ = false;
andrewm@0 287 return 0;
andrewm@0 288 }
andrewm@0 289 else // Return the timestamp of the next call
andrewm@0 290 return (nextTouchTimestamp_ + timestampOffset_);
andrewm@0 291 }
andrewm@0 292
andrewm@0 293 timestamp_type LogPlayback::nextMidiEvent() {
andrewm@0 294 if(!playing_ || !open_ || paused_)
andrewm@0 295 return 0;
andrewm@0 296
andrewm@0 297 // TODO: handle playback rate
andrewm@0 298
andrewm@0 299 // Play the most recent stored touch frame
andrewm@0 300 if(nextMidi_.size() >= 3)
andrewm@0 301 midiInputController_.handleIncomingMidiMessage(0, MidiMessage(nextMidi_[0], nextMidi_[1], nextMidi_[2]));
andrewm@0 302 //midiInputController_.rtMidiCallback(nextMidiTimestamp_ - lastMidiTimestamp_, &nextMidi_, 0);
andrewm@0 303 lastMidiTimestamp_ = nextMidiTimestamp_;
andrewm@0 304
andrewm@0 305 bool newMidiEventFound = readNextMidiFrame();
andrewm@0 306
andrewm@0 307 // Go through next touch frames and send them as long as the timestamp is not in the future
andrewm@0 308 while(newMidiEventFound && (nextMidiTimestamp_ + timestampOffset_) <= playbackScheduler_.currentTimestamp()) {
andrewm@0 309 if(nextMidi_.size() >= 3)
andrewm@0 310 midiInputController_.handleIncomingMidiMessage(0, MidiMessage(nextMidi_[0], nextMidi_[1], nextMidi_[2]));
andrewm@0 311 //midiInputController_.rtMidiCallback(nextMidiTimestamp_ - lastMidiTimestamp_, &nextMidi_, 0);
andrewm@0 312 lastMidiTimestamp_ = nextMidiTimestamp_;
andrewm@0 313
andrewm@0 314 readNextMidiFrame();
andrewm@0 315 }
andrewm@0 316
andrewm@0 317 if(!newMidiEventFound) { // EOF or error
andrewm@0 318 usingMidi_ = false;
andrewm@0 319 if(!usingTouch_)
andrewm@0 320 playing_ = paused_ = false;
andrewm@0 321 return 0;
andrewm@0 322 }
andrewm@0 323 else // Return the timestamp of the next call
andrewm@0 324 return (nextMidiTimestamp_ + timestampOffset_);
andrewm@0 325 }
andrewm@0 326
andrewm@0 327 // Retrieve the next key touch frame from the log file
andrewm@0 328 // Return true if a touch was found, false if EOF or an error occurred
andrewm@0 329 bool LogPlayback::readNextTouchFrame() {
andrewm@0 330 int frameCounter;
andrewm@0 331
andrewm@0 332 try {
andrewm@0 333 touchLog_.read((char *)&nextTouchTimestamp_, sizeof(timestamp_type));
andrewm@0 334 touchLog_.read((char *)&frameCounter, sizeof(int));
andrewm@0 335 touchLog_.read((char *)&nextTouchMidiNote_, sizeof(int));
andrewm@0 336 touchLog_.read((char *)&nextTouch_, sizeof(KeyTouchFrame));
andrewm@0 337 }
andrewm@0 338 catch(...) {
andrewm@0 339 cout << "error reading touch\n";
andrewm@0 340 return false;
andrewm@0 341 }
andrewm@0 342 if(touchLog_.eof()) {
andrewm@0 343 cout << "Touch log playback finished\n";
andrewm@0 344 return false;
andrewm@0 345 }
andrewm@0 346
andrewm@0 347 //cout << "read touch on key " << nextTouchMidiNote_ << " timestamp " << nextTouchTimestamp_ << endl;
andrewm@0 348
andrewm@0 349 // TODO: what about frameCounter
andrewm@0 350
andrewm@0 351 return true;
andrewm@0 352 }
andrewm@0 353
andrewm@0 354 // Retrieve the next MIDI frame from the log file
andrewm@0 355 // Return true if an event was found, false if EOF or an error occurred
andrewm@0 356 bool LogPlayback::readNextMidiFrame() {
andrewm@0 357 int midi0, midi1, midi2;
andrewm@0 358
andrewm@0 359 try {
andrewm@0 360 midiLog_.read((char*)&nextMidiTimestamp_, sizeof (timestamp_type));
andrewm@0 361 midiLog_.read((char*)&midi0, sizeof (int));
andrewm@0 362 midiLog_.read((char*)&midi1, sizeof (int));
andrewm@0 363 midiLog_.read((char*)&midi2, sizeof (int));
andrewm@0 364 }
andrewm@0 365 catch(...) {
andrewm@0 366 cout << "error reading MIDI\n";
andrewm@0 367 return false;
andrewm@0 368 }
andrewm@0 369
andrewm@0 370 if(midiLog_.eof()) {
andrewm@0 371 cout << "MIDI log playback finished\n";
andrewm@0 372 return false;
andrewm@0 373 }
andrewm@0 374
andrewm@0 375 nextMidi_.clear();
andrewm@0 376 nextMidi_.push_back((unsigned char)midi0);
andrewm@0 377 nextMidi_.push_back((unsigned char)midi1);
andrewm@0 378 nextMidi_.push_back((unsigned char)midi2);
andrewm@0 379
andrewm@0 380 //cout << "read MIDI data " << (int)midi0 << " " << (int)midi1 << " " << (int)midi2 << endl;
andrewm@0 381
andrewm@0 382 return true;
andrewm@0 383 }