From 08b98dce531e0bec67fd730724b56bd763ee3b68 Mon Sep 17 00:00:00 2001 From: rbdannenberg Date: Tue, 5 Oct 2010 18:29:57 +0000 Subject: [PATCH] PlayAtSpeed works for MIDI. Removed prefs for selecting MIDI input device (unless EXPERIMENTAL_MIDI_IN is on -- but there is no MIDI recording implemented at all) --- src/AudioIO.cpp | 33 +++++++++++++++-------- src/AudioIO.h | 2 ++ src/Menus.cpp | 38 ++++++++++++++++++++------- src/TrackPanel.cpp | 2 ++ src/prefs/MidiIOPrefs.cpp | 24 +++++++++++------ src/prefs/MidiIOPrefs.h | 4 +++ src/toolbars/TranscriptionToolBar.cpp | 4 ++- 7 files changed, 77 insertions(+), 30 deletions(-) diff --git a/src/AudioIO.cpp b/src/AudioIO.cpp index d71d6aaa7..db8d1e32e 100644 --- a/src/AudioIO.cpp +++ b/src/AudioIO.cpp @@ -515,6 +515,7 @@ AudioIO::AudioIO() mMidiStreamActive = false; mSendMidiState = false; mIterator = NULL; + mMidiPlaySpeed = 1.0; mNumFrames = 0; mNumPauseFrames = 0; @@ -1423,6 +1424,7 @@ void AudioIO::SetMeters(Meter *inputMeter, Meter *outputMeter) void AudioIO::StopStream() { + _RPT0(_CRT_WARN, "StopStream"); if( mPortStreamV19 == NULL #ifdef EXPERIMENTAL_MIDI_OUT && mMidiStream == NULL @@ -1529,6 +1531,7 @@ void AudioIO::StopStream() mIterator->end(); delete mIterator; mIterator = NULL; // just in case someone tries to reference it + mMidiPlaySpeed = 1.0; } #endif @@ -1678,6 +1681,8 @@ bool AudioIO::IsStreamActive() else isActive = false; #ifdef EXPERIMENTAL_MIDI_OUT + _RPT2(_CRT_WARN, "mMidiStreamActive %d, mMidiOutputComplete %d\n", + mMidiStreamActive, mMidiOutputComplete); if( mMidiStreamActive && !mMidiOutputComplete ) isActive = true; #endif @@ -2062,10 +2067,10 @@ MidiThread::ExitCode MidiThread::Entry() gAudioIO->FillMidiBuffers(); // test for end - double real_time = gAudioIO->mT0 + gAudioIO->MidiTime() * 0.001 - + double realTime = gAudioIO->mT0 + gAudioIO->MidiTime() * 0.001 - gAudioIO->PauseTime(); if (gAudioIO->mNumPlaybackChannels != 0) { - real_time -= 1; // with audio, MidiTime() runs ahead 1s + realTime -= 1; // with audio, MidiTime() runs ahead 1s } // The TrackPanel::OnTimer() method updates the time position // indicator every 200ms, so it tends to not advance the @@ -2077,8 +2082,13 @@ MidiThread::ExitCode MidiThread::Entry() // 0.22s beyond mT1 (even though we stop playing at mT1. This // gives OnTimer() time to wake up and draw the final time // position at mT1 before shutting down the stream. + double timeAtSpeed = (realTime - gAudioIO->mT0) * + gAudioIO->mMidiPlaySpeed + gAudioIO->mT0; + _RPT2(_CRT_WARN, "realTime %g, timeAtSpeed %g\n", + realTime, timeAtSpeed); + gAudioIO->mMidiOutputComplete = - (!gAudioIO->mPlayLooped && real_time >= gAudioIO->mT1 + 0.220); + (!gAudioIO->mPlayLooped && timeAtSpeed >= gAudioIO->mT1 + 0.220); // !gAudioIO->mNextEvent); } } @@ -2609,7 +2619,8 @@ void AudioIO::OutputEvent() int data2 = -1; // 0.0005 is for rounding - double time = mNextEventTime + PauseTime() + 0.0005 - + double eventTime = (mNextEventTime - mT0) / mMidiPlaySpeed + mT0; + double time = eventTime + PauseTime() + 0.0005 - ((mMidiLatency + mSynthLatency) * 0.001); if (mNumPlaybackChannels > 0) { // is there audio playback? time += 1; // MidiTime() has a 1s offset @@ -2752,7 +2763,6 @@ bool AudioIO::SetHasSolo(bool hasSolo) } -// returns estimated current track time void AudioIO::FillMidiBuffers() { bool hasSolo = false; @@ -2777,22 +2787,23 @@ void AudioIO::FillMidiBuffers() time = AudioTime() - PauseTime(); } else { time = mT0 + Pt_Time() * 0.001 - PauseTime(); + double timeAtSpeed = (time - mT0) * mMidiPlaySpeed + mT0; if (mNumCaptureChannels <= 0) { // no audio callback, so move the time cursor here: - double track_time = time - mMidiLoopOffset; + double trackTime = timeAtSpeed - mMidiLoopOffset; //printf("mTime set. mT0 %g Pt_Time() %gs PauseTime %g\n", // mT0, Pt_Time() * 0.001, PauseTime()); // Since loop offset is incremented when we fill the // buffer, the cursor tends to jump back to mT0 early. // Therefore, if we are in loop mode, and if mTime < mT0, // we must not be at the end of the loop yet. - if (mPlayLooped && track_time < mT0) { - track_time += (mT1 - mT0); + if (mPlayLooped && trackTime < mT0) { + trackTime += (mT1 - mT0); } // mTime is shared with another thread so we stored - // intermediate values in track_time. Do the update + // intermediate values in trackTime. Do the update // atomically now that we have the final value: - mTime = track_time; + mTime = trackTime; } // advance time so that midi messages are written a little early, // timestamps will insure accurate output timing. This is an "extra" @@ -2802,7 +2813,7 @@ void AudioIO::FillMidiBuffers() time += MIDI_SLEEP * 0.001; } while (mNextEvent && - mNextEventTime < time + + (mNextEventTime - mT0) / mMidiPlaySpeed + mT0 < time + ((MIDI_SLEEP + mSynthLatency) * 0.001)) { OutputEvent(); GetNextEvent(); diff --git a/src/AudioIO.h b/src/AudioIO.h index fb8e1e1bf..0633872aa 100644 --- a/src/AudioIO.h +++ b/src/AudioIO.h @@ -157,6 +157,7 @@ class AUDACITY_DLL_API AudioIO { public: bool SetHasSolo(bool hasSolo); bool GetHasSolo() { return mHasSolo; } + void SetMidiPlaySpeed(double s) { mMidiPlaySpeed = s * 0.01; } #endif /** \brief Returns true if the stream is active, or even if audio I/O is @@ -404,6 +405,7 @@ private: PmError mLastPmError; long mMidiLatency; // latency value for PortMidi long mSynthLatency; // latency of MIDI synthesizer + double mMidiPlaySpeed; // a copy of TranscriptionToolBar::mPlaySpeed // These fields are used to synchronize MIDI with audio volatile double mAudioCallbackOutputTime; // PortAudio's outTime diff --git a/src/Menus.cpp b/src/Menus.cpp index 525cdda88..5bed5972b 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -5101,6 +5101,7 @@ void AudacityProject::OnScoreAlign() int numNoteTracksSelected = 0; int numOtherTracksSelected = 0; NoteTrack *nt; + NoteTrack *alignedNoteTrack; double endTime = 0.0; // Iterate through once to make sure that there is exactly @@ -5136,16 +5137,28 @@ void AudacityProject::OnScoreAlign() if (params.mStatus != wxID_OK) return; // We're going to do it. - PushState(_("Sync MIDI with Audio"), _("Sync MIDI with Audio")); + //pushing the state before the change is wrong (I think) + //PushState(_("Sync MIDI with Audio"), _("Sync MIDI with Audio")); + // Make a copy of the note track in case alignment is canceled or fails + alignedNoteTrack = (NoteTrack *) nt->Duplicate(); + // Duplicate() on note tracks serializes seq to a buffer, but we need + // the seq, so Duplicate again and discard the track with buffer. The + // test is here in case Duplicate() is changed in the future. + if (alignedNoteTrack->GetSequence() == NULL) { + NoteTrack *temp = (NoteTrack *) alignedNoteTrack->Duplicate(); + delete alignedNoteTrack; + alignedNoteTrack = temp; + assert(alignedNoteTrack->GetSequence()); + } // Remove offset from NoteTrack because audio is // mixed starting at zero and incorporating clip offsets. - if (nt->GetOffset() < 0) { + if (alignedNoteTrack->GetOffset() < 0) { // remove the negative offset data before alignment - nt->Clear(nt->GetOffset(), 0); - } else if (nt->GetOffset() > 0) { - nt->Shift(nt->GetOffset()); + nt->Clear(alignedNoteTrack->GetOffset(), 0); + } else if (alignedNoteTrack->GetOffset() > 0) { + alignedNoteTrack->Shift(alignedNoteTrack->GetOffset()); } - nt->SetOffset(0); + alignedNoteTrack->SetOffset(0); WaveTrack **waveTracks; mTracks->GetWaveTracks(true /* selectionOnly */, @@ -5176,7 +5189,7 @@ void AudacityProject::OnScoreAlign() #ifndef SKIP_ACTUAL_SCORE_ALIGNMENT int result = scorealign((void *) mix, &mixer_process, 2 /* channels */, 44100.0 /* srate */, endTime, - nt->GetSequence(), progress, params); + alignedNoteTrack->GetSequence(), progress, params); #else int result = SA_SUCCESS; #endif @@ -5185,22 +5198,27 @@ void AudacityProject::OnScoreAlign() delete mix; if (result == SA_SUCCESS) { - + mTracks->Replace(nt, alignedNoteTrack, true); RedrawProject(); wxMessageBox(wxString::Format( _("Alignment completed: MIDI from %.2f to %.2f secs, Audio from %.2f to %.2f secs."), params.mMidiStart, params.mMidiEnd, params.mAudioStart, params.mAudioEnd)); + PushState(_("Sync MIDI with Audio"), _("Sync MIDI with Audio")); } else if (result == SA_TOOSHORT) { + delete alignedNoteTrack; wxMessageBox(wxString::Format( _("Alignment error: input too short: MIDI from %.2f to %.2f secs, Audio from %.2f to %.2f secs."), params.mMidiStart, params.mMidiEnd, params.mAudioStart, params.mAudioEnd)); } else if (result == SA_CANCEL) { - GetActiveProject()->OnUndo(); // recover any changes to note track + // wrong way to recover... + //GetActiveProject()->OnUndo(); // recover any changes to note track + delete alignedNoteTrack; return; // no message when user cancels alignment } else { - GetActiveProject()->OnUndo(); // recover any changes to note track + //GetActiveProject()->OnUndo(); // recover any changes to note track + delete alignedNoteTrack; wxMessageBox(_("Internal error reported by alignment process.")); } } diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index 4d6950ef9..add8ae6da 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -871,6 +871,8 @@ void TrackPanel::OnTimer() wxCommandEvent dummyEvent; AudacityProject *p = GetProject(); + static int rptcnt = 0; + _RPT1(_CRT_WARN, "OnTimer %d\n", rptcnt++); if ((p->GetAudioIOToken() > 0) && gAudioIO->IsStreamActive(p->GetAudioIOToken())) { diff --git a/src/prefs/MidiIOPrefs.cpp b/src/prefs/MidiIOPrefs.cpp index c172bb5cb..42d19e0f6 100644 --- a/src/prefs/MidiIOPrefs.cpp +++ b/src/prefs/MidiIOPrefs.cpp @@ -71,7 +71,9 @@ void MidiIOPrefs::Populate() // Get current setting for devices mPlayDevice = gPrefs->Read(wxT("/MidiIO/PlaybackDevice"), wxT("")); +#ifdef EXPERIMENTAL_MIDI_IN mRecordDevice = gPrefs->Read(wxT("/MidiIO/RecordingDevice"), wxT("")); +#endif // mRecordChannels = gPrefs->Read(wxT("/MidiIO/RecordChannels"), 2L); //------------------------- Main section -------------------- @@ -147,7 +149,7 @@ void MidiIOPrefs::PopulateOrExchange( ShuttleGui & S ) { S.EndMultiColumn(); } S.EndStatic(); - +#ifdef EXPERIMENTAL_MIDI_IN S.StartStatic(_("Recording")); { S.StartMultiColumn(2); @@ -167,6 +169,7 @@ void MidiIOPrefs::PopulateOrExchange( ShuttleGui & S ) { S.EndMultiColumn(); } S.EndStatic(); +#endif } void MidiIOPrefs::OnHost(wxCommandEvent & e) @@ -182,7 +185,9 @@ void MidiIOPrefs::OnHost(wxCommandEvent & e) } mPlay->Clear(); +#ifdef EXPERIMENTAL_MIDI_IN mRecord->Clear(); +#endif wxArrayString playnames; wxArrayString recordnames; @@ -204,7 +209,7 @@ void MidiIOPrefs::OnHost(wxCommandEvent & e) mPlay->SetSelection(index); } } - +#ifdef EXPERIMENTAL_MIDI_IN if (info->input) { recordnames.Add(name); index = mRecord->Append(name, (void *) info); @@ -212,6 +217,7 @@ void MidiIOPrefs::OnHost(wxCommandEvent & e) mRecord->SetSelection(index); } } +#endif } } @@ -219,23 +225,25 @@ void MidiIOPrefs::OnHost(wxCommandEvent & e) playnames.Add(_("No devices found")); mPlay->Append(playnames[0], (void *) NULL); } - +#ifdef EXPERIMENTAL_MIDI_IN if (mRecord->GetCount() == 0) { recordnames.Add(_("No devices found")); mRecord->Append(recordnames[0], (void *) NULL); } - +#endif if (mPlay->GetCount() && mPlay->GetSelection() == wxNOT_FOUND) { mPlay->SetSelection(0); } - +#ifdef EXPERIMENTAL_MIDI_IN if (mRecord->GetCount() && mRecord->GetSelection() == wxNOT_FOUND) { mRecord->SetSelection(0); } - +#endif ShuttleGui S(this, eIsCreating); S.SetSizeHints(mPlay, playnames); +#ifdef EXPERIMENTAL_MIDI_IN S.SetSizeHints(mRecord, recordnames); +#endif // OnDevice(e); } @@ -253,7 +261,7 @@ bool MidiIOPrefs::Apply() wxString(info->interf, wxConvLocal).c_str(), wxString(info->name, wxConvLocal).c_str())); } - +#ifdef EXPERIMENTAL_MIDI_IN info = (const PmDeviceInfo *) mRecord->GetClientData(mRecord->GetSelection()); if (info) { gPrefs->Write(wxT("/MidiIO/RecordingDevice"), @@ -261,7 +269,7 @@ bool MidiIOPrefs::Apply() wxString(info->interf, wxConvLocal).c_str(), wxString(info->name, wxConvLocal).c_str())); } - +#endif return true; } diff --git a/src/prefs/MidiIOPrefs.h b/src/prefs/MidiIOPrefs.h index 4bd27be74..57879070b 100644 --- a/src/prefs/MidiIOPrefs.h +++ b/src/prefs/MidiIOPrefs.h @@ -45,13 +45,17 @@ class MidiIOPrefs:public PrefsPanel wxArrayString mHostLabels; wxString mPlayDevice; +#ifdef EXPERIMENTAL_MIDI_IN wxString mRecordDevice; +#endif // long mRecordChannels; wxChoice *mHost; wxChoice *mPlay; wxTextCtrl *mLatency; +#ifdef EXPERIMENTAL_MIDI_IN wxChoice *mRecord; +#endif // wxChoice *mChannels; DECLARE_EVENT_TABLE(); diff --git a/src/toolbars/TranscriptionToolBar.cpp b/src/toolbars/TranscriptionToolBar.cpp index ec31ad123..0cbd9a7ca 100644 --- a/src/toolbars/TranscriptionToolBar.cpp +++ b/src/toolbars/TranscriptionToolBar.cpp @@ -415,7 +415,9 @@ void TranscriptionToolBar::OnPlaySpeed(wxCommandEvent & event) // Start playing if (playRegionStart >= 0) { // playRegionEnd = playRegionStart + (playRegionEnd-playRegionStart)* 100.0/mPlaySpeed; - +#ifdef EXPERIMENTAL_MIDI_OUT + gAudioIO->SetMidiPlaySpeed(mPlaySpeed); +#endif p->GetControlToolBar()->PlayPlayRegion(playRegionStart, playRegionEnd, false,