1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-10-19 17:11:12 +02:00

Extended pitch, tempo, and speed effects to operate on NoteTracks. Fixed off-by-one error on channel buttons.

This commit is contained in:
rbdannenberg
2010-10-28 17:57:14 +00:00
parent f3b91514d2
commit f274c9fb2b
6 changed files with 125 additions and 14 deletions

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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);