From f274c9fb2b9ea25539ac0c47977647e584e9f6eb Mon Sep 17 00:00:00 2001 From: rbdannenberg Date: Thu, 28 Oct 2010 17:57:14 +0000 Subject: [PATCH] Extended pitch, tempo, and speed effects to operate on NoteTracks. Fixed off-by-one error on channel buttons. --- lib-src/libscorealign/curvefit.cpp | 1 + src/NoteTrack.cpp | 88 +++++++++++++++++++++++++----- src/NoteTrack.h | 16 +++++- src/effects/ChangePitch.cpp | 10 ++++ src/effects/SoundTouchEffect.cpp | 19 +++++++ src/effects/SoundTouchEffect.h | 5 ++ 6 files changed, 125 insertions(+), 14 deletions(-) diff --git a/lib-src/libscorealign/curvefit.cpp b/lib-src/libscorealign/curvefit.cpp index 48e5f8646..84d839f5d 100644 --- a/lib-src/libscorealign/curvefit.cpp +++ b/lib-src/libscorealign/curvefit.cpp @@ -196,6 +196,7 @@ double Curvefit::distance_xy(double x, double y) printf("FATAL INTERNAL ERROR IN distance_xy: neither x nor y is " "an integer\n"); assert(false); + return 100.0; // to make the compiler happy } } diff --git a/src/NoteTrack.cpp b/src/NoteTrack.cpp index 6e2bc9aa0..823acaeab 100644 --- a/src/NoteTrack.cpp +++ b/src/NoteTrack.cpp @@ -31,6 +31,7 @@ #include "DirManager.h" #include "Internat.h" #include "Prefs.h" +#include "effects/TimeWarper.h" #ifdef SONIFY #include "portmidi.h" @@ -117,7 +118,7 @@ Track(projDirManager) mBottomNote = 24; mPitchHeight = 5; - mVisibleChannels = 0xFFFF; + mVisibleChannels = ALL_CHANNELS; mLastMidiPosition = 0; } @@ -181,6 +182,65 @@ double NoteTrack::GetEndTime() } +void NoteTrack::WarpAndTransposeNotes(double t0, double t1, + const TimeWarper &warper, + double semitones) +{ + // Since this is a duplicate and duplicates convert mSeq to + // a text string for saving as XML, we probably have to + // duplicate again to get back an mSeq + NoteTrack *nt = this; + double offset = nt->GetOffset(); // track is shifted this amount + if (!mSeq) { // replace saveme with an (unserialized) duplicate + nt = (NoteTrack *) this->Duplicate(); + wxASSERT(!mSeq && nt->mSeq && !nt->mSerializationBuffer); + // swap mSeq and Buffer between this and nt + nt->mSerializationBuffer = mSerializationBuffer; + nt->mSerializationLength = mSerializationLength; + mSerializationBuffer = NULL; + mSerializationLength = 0; + mSeq = nt->mSeq; + nt->mSeq = NULL; + delete nt; // delete the duplicate + } + mSeq->convert_to_seconds(); // make sure time units are right + t1 -= offset; // adjust time range to compensate for track offset + t0 -= offset; + if (t1 > mSeq->get_dur()) { // make sure t0, t1 are within sequence + t1 = mSeq->get_dur(); + if (t0 >= t1) return; + } + Alg_iterator iter(mSeq, false); + iter.begin(); + Alg_event_ptr event; + while ((event = iter.next()) && event->time < t1) { + if (event->is_note() && event->time >= t0 && + // Allegro data structure does not restrict channels to 16. + // Since there is not way to select more than 16 channels, + // map all channel numbers mod 16. This will have no effect + // on MIDI files, but it will allow users to at least select + // all channels on non-MIDI event sequence data. + IsVisibleChan(event->chan % 16)) { + event->set_pitch(event->get_pitch() + semitones); + } + } + iter.end(); + // now, use warper to warp the tempo map + mSeq->convert_to_beats(); // beats remain the same + Alg_time_map_ptr map = mSeq->get_time_map(); + map->insert_beat(t0, map->time_to_beat(t0)); + map->insert_beat(t1, map->time_to_beat(t1)); + int i, len = map->length(); + for (i = 0; i < len; i++) { + Alg_beat &beat = map->beats[i]; + beat.time = warper.Warp(beat.time + offset) - offset; + } + // about to redisplay, so might as well convert back to time now + mSeq->convert_to_seconds(); +} + + + int NoteTrack::DrawLabelControls(wxDC & dc, wxRect & r) { int wid = 23; @@ -196,32 +256,34 @@ int NoteTrack::DrawLabelControls(wxDC & dc, wxRect & r) wxRect box; for (int row = 0; row < 4; row++) { for (int col = 0; col < 4; col++) { - int channel = row * 4 + col + 1; + // chanName is the "external" channel number (1-16) + // used by AColor and button labels + int chanName = row * 4 + col + 1; box.x = x + col * wid; box.y = y + row * ht; box.width = wid; box.height = ht; - if (mVisibleChannels & (1 << (channel - 1))) { - AColor::MIDIChannel(&dc, channel); + if (IsVisibleChan(chanName - 1)) { + AColor::MIDIChannel(&dc, chanName); dc.DrawRectangle(box); // two choices: channel is enabled (to see and play) when button is in // "up" position (original Audacity style) or in "down" position // #define CHANNEL_ON_IS_DOWN 1 #if CHANNEL_ON_IS_DOWN - AColor::DarkMIDIChannel(&dc, channel); + AColor::DarkMIDIChannel(&dc, chanName); #else - AColor::LightMIDIChannel(&dc, channel); + AColor::LightMIDIChannel(&dc, chanName); #endif AColor::Line(dc, box.x, box.y, box.x + box.width - 1, box.y); AColor::Line(dc, box.x, box.y, box.x, box.y + box.height - 1); #if CHANNEL_ON_IS_DOWN - AColor::LightMIDIChannel(&dc, channel); + AColor::LightMIDIChannel(&dc, chanName); #else - AColor::DarkMIDIChannel(&dc, channel); + AColor::DarkMIDIChannel(&dc, chanName); #endif AColor::Line(dc, box.x + box.width - 1, box.y, @@ -258,7 +320,7 @@ int NoteTrack::DrawLabelControls(wxDC & dc, wxRect & r) long w; long h; - t.Printf(wxT("%d"), channel); + t.Printf(wxT("%d"), chanName); dc.GetTextExtent(t, &w, &h); dc.DrawText(t, box.x + (box.width - w) / 2, box.y + (box.height - h) / 2); @@ -290,12 +352,12 @@ bool NoteTrack::LabelClick(wxRect & r, int mx, int my, bool right) int channel = row * 4 + col; if (right) { - if (mVisibleChannels == (1 << channel)) - mVisibleChannels = 0xFFFF; + if (mVisibleChannels == CHANNEL_BIT(channel)) + mVisibleChannels = ALL_CHANNELS; else - mVisibleChannels = (1 << channel); + mVisibleChannels = CHANNEL_BIT(channel); } else - mVisibleChannels ^= (1 << channel); + ToggleVisibleChan(channel); return true; } diff --git a/src/NoteTrack.h b/src/NoteTrack.h index cb6fbc18e..e157d960e 100644 --- a/src/NoteTrack.h +++ b/src/NoteTrack.h @@ -63,6 +63,9 @@ class AUDACITY_DLL_API NoteTrack:public Track { virtual double GetStartTime(); virtual double GetEndTime(); + void WarpAndTransposeNotes(double t0, double t1, + const TimeWarper &warper, double semitones); + int DrawLabelControls(wxDC & dc, wxRect & r); bool LabelClick(wxRect & r, int x, int y, bool right); @@ -164,6 +167,17 @@ class AUDACITY_DLL_API NoteTrack:public Track { virtual XMLTagHandler *HandleXMLChild(const wxChar *tag); virtual void WriteXML(XMLWriter &xmlFile); + // channels are numbered as integers 0-15, visible channels + // (mVisibleChannels) is a bit set. Channels are displayed as + // integers 1-16. +#define CHANNEL_BIT(c) (1 << (c)) +#define ALL_CHANNELS 0xFFFF + bool IsVisibleChan(int c) { + return (mVisibleChannels & CHANNEL_BIT(c)) != 0; + } + void SetVisibleChan(int c) { mVisibleChannels |= CHANNEL_BIT(c); } + void ClearVisibleChan(int c) { mVisibleChannels &= ~CHANNEL_BIT(c); } + void ToggleVisibleChan(int c) { mVisibleChannels ^= CHANNEL_BIT(c); } private: Alg_seq *mSeq; // NULL means no sequence // when Duplicate() is called, assume that it is to put a copy @@ -189,7 +203,7 @@ class AUDACITY_DLL_API NoteTrack:public Track { int mBottomNote; int mStartBottomNote; int mPitchHeight; - int mVisibleChannels; + int mVisibleChannels; // bit set of visible channels int mLastMidiPosition; wxRect mGainPlacementRect; }; diff --git a/src/effects/ChangePitch.cpp b/src/effects/ChangePitch.cpp index 98f8d73b8..e4e9c5ee0 100644 --- a/src/effects/ChangePitch.cpp +++ b/src/effects/ChangePitch.cpp @@ -156,6 +156,16 @@ bool EffectChangePitch::Process() mSoundTouch = new SoundTouch(); SetTimeWarper(new IdentityTimeWarper()); mSoundTouch->setPitchSemiTones((float)(m_SemitonesChange)); +#ifdef USE_MIDI + // Note: m_SemitonesChange is private to ChangePitch because it only + // needs to pass it along to mSoundTouch (above). I added mSemitones + // to SoundTouchEffect (the super class) to convey this value + // to process Note tracks. This approach minimizes changes to existing + // code, but it would be cleaner to change all m_SemitonesChange to + // mSemitones, make mSemitones exist with or without USE_MIDI, and + // eliminate the next line: + mSemitones = m_SemitonesChange; +#endif return this->EffectSoundTouch::Process(); } diff --git a/src/effects/SoundTouchEffect.cpp b/src/effects/SoundTouchEffect.cpp index 2bbfd8c32..c28b0835b 100644 --- a/src/effects/SoundTouchEffect.cpp +++ b/src/effects/SoundTouchEffect.cpp @@ -34,6 +34,14 @@ bool EffectSoundTouch::ProcessLabelTrack(Track *track) return true; } +bool EffectSoundTouch::ProcessNoteTrack(Track *track) +{ + NoteTrack *nt = (NoteTrack *) track; + if (nt == NULL) return false; + nt->WarpAndTransposeNotes(mCurT0, mCurT1, *GetTimeWarper(), mSemitones); + return true; +} + bool EffectSoundTouch::Process() { // Assumes that mSoundTouch has already been initialized @@ -68,6 +76,17 @@ bool EffectSoundTouch::Process() break; } } +#ifdef USE_MIDI + else if (t->GetKind() == Track::Note && + (t->GetSelected() || (mustSync && t->IsSyncLockSelected()))) + { + if (!ProcessNoteTrack(t)) + { + bGoodResult = false; + break; + } + } +#endif else if (t->GetKind() == Track::Wave && t->GetSelected()) { WaveTrack* leftTrack = (WaveTrack*)t; diff --git a/src/effects/SoundTouchEffect.h b/src/effects/SoundTouchEffect.h index 97aefb351..8b69d010a 100644 --- a/src/effects/SoundTouchEffect.h +++ b/src/effects/SoundTouchEffect.h @@ -36,6 +36,10 @@ class EffectSoundTouch:public Effect { public: virtual bool Process(); +#ifdef USE_MIDI + double mSemitones; // pitch change for NoteTracks + EffectSoundTouch() { mSemitones = 0; } +#endif protected: SoundTouch *mSoundTouch; @@ -44,6 +48,7 @@ class EffectSoundTouch:public Effect { private: bool ProcessLabelTrack(Track *track); + bool ProcessNoteTrack(Track *track); bool ProcessOne(WaveTrack * t, sampleCount start, sampleCount end); bool ProcessStereo(WaveTrack* leftTrack, WaveTrack* rightTrack, sampleCount start, sampleCount end);