1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-02 17:09:26 +02:00

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)

This commit is contained in:
rbdannenberg 2010-10-05 18:29:57 +00:00
parent 28aaa34889
commit 08b98dce53
7 changed files with 77 additions and 30 deletions

View File

@ -515,6 +515,7 @@ AudioIO::AudioIO()
mMidiStreamActive = false; mMidiStreamActive = false;
mSendMidiState = false; mSendMidiState = false;
mIterator = NULL; mIterator = NULL;
mMidiPlaySpeed = 1.0;
mNumFrames = 0; mNumFrames = 0;
mNumPauseFrames = 0; mNumPauseFrames = 0;
@ -1423,6 +1424,7 @@ void AudioIO::SetMeters(Meter *inputMeter, Meter *outputMeter)
void AudioIO::StopStream() void AudioIO::StopStream()
{ {
_RPT0(_CRT_WARN, "StopStream");
if( mPortStreamV19 == NULL if( mPortStreamV19 == NULL
#ifdef EXPERIMENTAL_MIDI_OUT #ifdef EXPERIMENTAL_MIDI_OUT
&& mMidiStream == NULL && mMidiStream == NULL
@ -1529,6 +1531,7 @@ void AudioIO::StopStream()
mIterator->end(); mIterator->end();
delete mIterator; delete mIterator;
mIterator = NULL; // just in case someone tries to reference it mIterator = NULL; // just in case someone tries to reference it
mMidiPlaySpeed = 1.0;
} }
#endif #endif
@ -1678,6 +1681,8 @@ bool AudioIO::IsStreamActive()
else isActive = false; else isActive = false;
#ifdef EXPERIMENTAL_MIDI_OUT #ifdef EXPERIMENTAL_MIDI_OUT
_RPT2(_CRT_WARN, "mMidiStreamActive %d, mMidiOutputComplete %d\n",
mMidiStreamActive, mMidiOutputComplete);
if( mMidiStreamActive && !mMidiOutputComplete ) if( mMidiStreamActive && !mMidiOutputComplete )
isActive = true; isActive = true;
#endif #endif
@ -2062,10 +2067,10 @@ MidiThread::ExitCode MidiThread::Entry()
gAudioIO->FillMidiBuffers(); gAudioIO->FillMidiBuffers();
// test for end // test for end
double real_time = gAudioIO->mT0 + gAudioIO->MidiTime() * 0.001 - double realTime = gAudioIO->mT0 + gAudioIO->MidiTime() * 0.001 -
gAudioIO->PauseTime(); gAudioIO->PauseTime();
if (gAudioIO->mNumPlaybackChannels != 0) { 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 // The TrackPanel::OnTimer() method updates the time position
// indicator every 200ms, so it tends to not advance the // 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 // 0.22s beyond mT1 (even though we stop playing at mT1. This
// gives OnTimer() time to wake up and draw the final time // gives OnTimer() time to wake up and draw the final time
// position at mT1 before shutting down the stream. // 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->mMidiOutputComplete =
(!gAudioIO->mPlayLooped && real_time >= gAudioIO->mT1 + 0.220); (!gAudioIO->mPlayLooped && timeAtSpeed >= gAudioIO->mT1 + 0.220);
// !gAudioIO->mNextEvent); // !gAudioIO->mNextEvent);
} }
} }
@ -2609,7 +2619,8 @@ void AudioIO::OutputEvent()
int data2 = -1; int data2 = -1;
// 0.0005 is for rounding // 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); ((mMidiLatency + mSynthLatency) * 0.001);
if (mNumPlaybackChannels > 0) { // is there audio playback? if (mNumPlaybackChannels > 0) { // is there audio playback?
time += 1; // MidiTime() has a 1s offset time += 1; // MidiTime() has a 1s offset
@ -2752,7 +2763,6 @@ bool AudioIO::SetHasSolo(bool hasSolo)
} }
// returns estimated current track time
void AudioIO::FillMidiBuffers() void AudioIO::FillMidiBuffers()
{ {
bool hasSolo = false; bool hasSolo = false;
@ -2777,22 +2787,23 @@ void AudioIO::FillMidiBuffers()
time = AudioTime() - PauseTime(); time = AudioTime() - PauseTime();
} else { } else {
time = mT0 + Pt_Time() * 0.001 - PauseTime(); time = mT0 + Pt_Time() * 0.001 - PauseTime();
double timeAtSpeed = (time - mT0) * mMidiPlaySpeed + mT0;
if (mNumCaptureChannels <= 0) { if (mNumCaptureChannels <= 0) {
// no audio callback, so move the time cursor here: // 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", //printf("mTime set. mT0 %g Pt_Time() %gs PauseTime %g\n",
// mT0, Pt_Time() * 0.001, PauseTime()); // mT0, Pt_Time() * 0.001, PauseTime());
// Since loop offset is incremented when we fill the // Since loop offset is incremented when we fill the
// buffer, the cursor tends to jump back to mT0 early. // buffer, the cursor tends to jump back to mT0 early.
// Therefore, if we are in loop mode, and if mTime < mT0, // Therefore, if we are in loop mode, and if mTime < mT0,
// we must not be at the end of the loop yet. // we must not be at the end of the loop yet.
if (mPlayLooped && track_time < mT0) { if (mPlayLooped && trackTime < mT0) {
track_time += (mT1 - mT0); trackTime += (mT1 - mT0);
} }
// mTime is shared with another thread so we stored // 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: // atomically now that we have the final value:
mTime = track_time; mTime = trackTime;
} }
// advance time so that midi messages are written a little early, // advance time so that midi messages are written a little early,
// timestamps will insure accurate output timing. This is an "extra" // timestamps will insure accurate output timing. This is an "extra"
@ -2802,7 +2813,7 @@ void AudioIO::FillMidiBuffers()
time += MIDI_SLEEP * 0.001; time += MIDI_SLEEP * 0.001;
} }
while (mNextEvent && while (mNextEvent &&
mNextEventTime < time + (mNextEventTime - mT0) / mMidiPlaySpeed + mT0 < time +
((MIDI_SLEEP + mSynthLatency) * 0.001)) { ((MIDI_SLEEP + mSynthLatency) * 0.001)) {
OutputEvent(); OutputEvent();
GetNextEvent(); GetNextEvent();

View File

@ -157,6 +157,7 @@ class AUDACITY_DLL_API AudioIO {
public: public:
bool SetHasSolo(bool hasSolo); bool SetHasSolo(bool hasSolo);
bool GetHasSolo() { return mHasSolo; } bool GetHasSolo() { return mHasSolo; }
void SetMidiPlaySpeed(double s) { mMidiPlaySpeed = s * 0.01; }
#endif #endif
/** \brief Returns true if the stream is active, or even if audio I/O is /** \brief Returns true if the stream is active, or even if audio I/O is
@ -404,6 +405,7 @@ private:
PmError mLastPmError; PmError mLastPmError;
long mMidiLatency; // latency value for PortMidi long mMidiLatency; // latency value for PortMidi
long mSynthLatency; // latency of MIDI synthesizer long mSynthLatency; // latency of MIDI synthesizer
double mMidiPlaySpeed; // a copy of TranscriptionToolBar::mPlaySpeed
// These fields are used to synchronize MIDI with audio // These fields are used to synchronize MIDI with audio
volatile double mAudioCallbackOutputTime; // PortAudio's outTime volatile double mAudioCallbackOutputTime; // PortAudio's outTime

View File

@ -5101,6 +5101,7 @@ void AudacityProject::OnScoreAlign()
int numNoteTracksSelected = 0; int numNoteTracksSelected = 0;
int numOtherTracksSelected = 0; int numOtherTracksSelected = 0;
NoteTrack *nt; NoteTrack *nt;
NoteTrack *alignedNoteTrack;
double endTime = 0.0; double endTime = 0.0;
// Iterate through once to make sure that there is exactly // Iterate through once to make sure that there is exactly
@ -5136,16 +5137,28 @@ void AudacityProject::OnScoreAlign()
if (params.mStatus != wxID_OK) return; if (params.mStatus != wxID_OK) return;
// We're going to do it. // 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 // Remove offset from NoteTrack because audio is
// mixed starting at zero and incorporating clip offsets. // mixed starting at zero and incorporating clip offsets.
if (nt->GetOffset() < 0) { if (alignedNoteTrack->GetOffset() < 0) {
// remove the negative offset data before alignment // remove the negative offset data before alignment
nt->Clear(nt->GetOffset(), 0); nt->Clear(alignedNoteTrack->GetOffset(), 0);
} else if (nt->GetOffset() > 0) { } else if (alignedNoteTrack->GetOffset() > 0) {
nt->Shift(nt->GetOffset()); alignedNoteTrack->Shift(alignedNoteTrack->GetOffset());
} }
nt->SetOffset(0); alignedNoteTrack->SetOffset(0);
WaveTrack **waveTracks; WaveTrack **waveTracks;
mTracks->GetWaveTracks(true /* selectionOnly */, mTracks->GetWaveTracks(true /* selectionOnly */,
@ -5176,7 +5189,7 @@ void AudacityProject::OnScoreAlign()
#ifndef SKIP_ACTUAL_SCORE_ALIGNMENT #ifndef SKIP_ACTUAL_SCORE_ALIGNMENT
int result = scorealign((void *) mix, &mixer_process, int result = scorealign((void *) mix, &mixer_process,
2 /* channels */, 44100.0 /* srate */, endTime, 2 /* channels */, 44100.0 /* srate */, endTime,
nt->GetSequence(), progress, params); alignedNoteTrack->GetSequence(), progress, params);
#else #else
int result = SA_SUCCESS; int result = SA_SUCCESS;
#endif #endif
@ -5185,22 +5198,27 @@ void AudacityProject::OnScoreAlign()
delete mix; delete mix;
if (result == SA_SUCCESS) { if (result == SA_SUCCESS) {
mTracks->Replace(nt, alignedNoteTrack, true);
RedrawProject(); RedrawProject();
wxMessageBox(wxString::Format( wxMessageBox(wxString::Format(
_("Alignment completed: MIDI from %.2f to %.2f secs, Audio from %.2f to %.2f secs."), _("Alignment completed: MIDI from %.2f to %.2f secs, Audio from %.2f to %.2f secs."),
params.mMidiStart, params.mMidiEnd, params.mMidiStart, params.mMidiEnd,
params.mAudioStart, params.mAudioEnd)); params.mAudioStart, params.mAudioEnd));
PushState(_("Sync MIDI with Audio"), _("Sync MIDI with Audio"));
} else if (result == SA_TOOSHORT) { } else if (result == SA_TOOSHORT) {
delete alignedNoteTrack;
wxMessageBox(wxString::Format( wxMessageBox(wxString::Format(
_("Alignment error: input too short: MIDI from %.2f to %.2f secs, Audio from %.2f to %.2f secs."), _("Alignment error: input too short: MIDI from %.2f to %.2f secs, Audio from %.2f to %.2f secs."),
params.mMidiStart, params.mMidiEnd, params.mMidiStart, params.mMidiEnd,
params.mAudioStart, params.mAudioEnd)); params.mAudioStart, params.mAudioEnd));
} else if (result == SA_CANCEL) { } 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 return; // no message when user cancels alignment
} else { } 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.")); wxMessageBox(_("Internal error reported by alignment process."));
} }
} }

View File

@ -871,6 +871,8 @@ void TrackPanel::OnTimer()
wxCommandEvent dummyEvent; wxCommandEvent dummyEvent;
AudacityProject *p = GetProject(); AudacityProject *p = GetProject();
static int rptcnt = 0;
_RPT1(_CRT_WARN, "OnTimer %d\n", rptcnt++);
if ((p->GetAudioIOToken() > 0) && if ((p->GetAudioIOToken() > 0) &&
gAudioIO->IsStreamActive(p->GetAudioIOToken())) gAudioIO->IsStreamActive(p->GetAudioIOToken()))
{ {

View File

@ -71,7 +71,9 @@ void MidiIOPrefs::Populate()
// Get current setting for devices // Get current setting for devices
mPlayDevice = gPrefs->Read(wxT("/MidiIO/PlaybackDevice"), wxT("")); mPlayDevice = gPrefs->Read(wxT("/MidiIO/PlaybackDevice"), wxT(""));
#ifdef EXPERIMENTAL_MIDI_IN
mRecordDevice = gPrefs->Read(wxT("/MidiIO/RecordingDevice"), wxT("")); mRecordDevice = gPrefs->Read(wxT("/MidiIO/RecordingDevice"), wxT(""));
#endif
// mRecordChannels = gPrefs->Read(wxT("/MidiIO/RecordChannels"), 2L); // mRecordChannels = gPrefs->Read(wxT("/MidiIO/RecordChannels"), 2L);
//------------------------- Main section -------------------- //------------------------- Main section --------------------
@ -147,7 +149,7 @@ void MidiIOPrefs::PopulateOrExchange( ShuttleGui & S ) {
S.EndMultiColumn(); S.EndMultiColumn();
} }
S.EndStatic(); S.EndStatic();
#ifdef EXPERIMENTAL_MIDI_IN
S.StartStatic(_("Recording")); S.StartStatic(_("Recording"));
{ {
S.StartMultiColumn(2); S.StartMultiColumn(2);
@ -167,6 +169,7 @@ void MidiIOPrefs::PopulateOrExchange( ShuttleGui & S ) {
S.EndMultiColumn(); S.EndMultiColumn();
} }
S.EndStatic(); S.EndStatic();
#endif
} }
void MidiIOPrefs::OnHost(wxCommandEvent & e) void MidiIOPrefs::OnHost(wxCommandEvent & e)
@ -182,7 +185,9 @@ void MidiIOPrefs::OnHost(wxCommandEvent & e)
} }
mPlay->Clear(); mPlay->Clear();
#ifdef EXPERIMENTAL_MIDI_IN
mRecord->Clear(); mRecord->Clear();
#endif
wxArrayString playnames; wxArrayString playnames;
wxArrayString recordnames; wxArrayString recordnames;
@ -204,7 +209,7 @@ void MidiIOPrefs::OnHost(wxCommandEvent & e)
mPlay->SetSelection(index); mPlay->SetSelection(index);
} }
} }
#ifdef EXPERIMENTAL_MIDI_IN
if (info->input) { if (info->input) {
recordnames.Add(name); recordnames.Add(name);
index = mRecord->Append(name, (void *) info); index = mRecord->Append(name, (void *) info);
@ -212,6 +217,7 @@ void MidiIOPrefs::OnHost(wxCommandEvent & e)
mRecord->SetSelection(index); mRecord->SetSelection(index);
} }
} }
#endif
} }
} }
@ -219,23 +225,25 @@ void MidiIOPrefs::OnHost(wxCommandEvent & e)
playnames.Add(_("No devices found")); playnames.Add(_("No devices found"));
mPlay->Append(playnames[0], (void *) NULL); mPlay->Append(playnames[0], (void *) NULL);
} }
#ifdef EXPERIMENTAL_MIDI_IN
if (mRecord->GetCount() == 0) { if (mRecord->GetCount() == 0) {
recordnames.Add(_("No devices found")); recordnames.Add(_("No devices found"));
mRecord->Append(recordnames[0], (void *) NULL); mRecord->Append(recordnames[0], (void *) NULL);
} }
#endif
if (mPlay->GetCount() && mPlay->GetSelection() == wxNOT_FOUND) { if (mPlay->GetCount() && mPlay->GetSelection() == wxNOT_FOUND) {
mPlay->SetSelection(0); mPlay->SetSelection(0);
} }
#ifdef EXPERIMENTAL_MIDI_IN
if (mRecord->GetCount() && mRecord->GetSelection() == wxNOT_FOUND) { if (mRecord->GetCount() && mRecord->GetSelection() == wxNOT_FOUND) {
mRecord->SetSelection(0); mRecord->SetSelection(0);
} }
#endif
ShuttleGui S(this, eIsCreating); ShuttleGui S(this, eIsCreating);
S.SetSizeHints(mPlay, playnames); S.SetSizeHints(mPlay, playnames);
#ifdef EXPERIMENTAL_MIDI_IN
S.SetSizeHints(mRecord, recordnames); S.SetSizeHints(mRecord, recordnames);
#endif
// OnDevice(e); // OnDevice(e);
} }
@ -253,7 +261,7 @@ bool MidiIOPrefs::Apply()
wxString(info->interf, wxConvLocal).c_str(), wxString(info->interf, wxConvLocal).c_str(),
wxString(info->name, wxConvLocal).c_str())); wxString(info->name, wxConvLocal).c_str()));
} }
#ifdef EXPERIMENTAL_MIDI_IN
info = (const PmDeviceInfo *) mRecord->GetClientData(mRecord->GetSelection()); info = (const PmDeviceInfo *) mRecord->GetClientData(mRecord->GetSelection());
if (info) { if (info) {
gPrefs->Write(wxT("/MidiIO/RecordingDevice"), gPrefs->Write(wxT("/MidiIO/RecordingDevice"),
@ -261,7 +269,7 @@ bool MidiIOPrefs::Apply()
wxString(info->interf, wxConvLocal).c_str(), wxString(info->interf, wxConvLocal).c_str(),
wxString(info->name, wxConvLocal).c_str())); wxString(info->name, wxConvLocal).c_str()));
} }
#endif
return true; return true;
} }

View File

@ -45,13 +45,17 @@ class MidiIOPrefs:public PrefsPanel
wxArrayString mHostLabels; wxArrayString mHostLabels;
wxString mPlayDevice; wxString mPlayDevice;
#ifdef EXPERIMENTAL_MIDI_IN
wxString mRecordDevice; wxString mRecordDevice;
#endif
// long mRecordChannels; // long mRecordChannels;
wxChoice *mHost; wxChoice *mHost;
wxChoice *mPlay; wxChoice *mPlay;
wxTextCtrl *mLatency; wxTextCtrl *mLatency;
#ifdef EXPERIMENTAL_MIDI_IN
wxChoice *mRecord; wxChoice *mRecord;
#endif
// wxChoice *mChannels; // wxChoice *mChannels;
DECLARE_EVENT_TABLE(); DECLARE_EVENT_TABLE();

View File

@ -415,7 +415,9 @@ void TranscriptionToolBar::OnPlaySpeed(wxCommandEvent & event)
// Start playing // Start playing
if (playRegionStart >= 0) { if (playRegionStart >= 0) {
// playRegionEnd = playRegionStart + (playRegionEnd-playRegionStart)* 100.0/mPlaySpeed; // playRegionEnd = playRegionStart + (playRegionEnd-playRegionStart)* 100.0/mPlaySpeed;
#ifdef EXPERIMENTAL_MIDI_OUT
gAudioIO->SetMidiPlaySpeed(mPlaySpeed);
#endif
p->GetControlToolBar()->PlayPlayRegion(playRegionStart, p->GetControlToolBar()->PlayPlayRegion(playRegionStart,
playRegionEnd, playRegionEnd,
false, false,