2010-01-23 19:44:49 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
|
|
|
|
NoteTrack.h
|
|
|
|
|
|
|
|
Dominic Mazzoni
|
|
|
|
|
|
|
|
**********************************************************************/
|
|
|
|
|
|
|
|
#ifndef __AUDACITY_NOTETRACK__
|
|
|
|
#define __AUDACITY_NOTETRACK__
|
|
|
|
|
2017-05-11 17:41:26 +00:00
|
|
|
#include <utility>
|
2010-01-23 19:44:49 +00:00
|
|
|
#include <wx/string.h>
|
|
|
|
#include "Audacity.h"
|
|
|
|
#include "Experimental.h"
|
|
|
|
#include "Track.h"
|
2010-10-28 21:22:14 +00:00
|
|
|
#include "effects/TimeWarper.h"
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
#if defined(USE_MIDI)
|
|
|
|
|
2010-09-19 17:32:03 +00:00
|
|
|
#include "allegro.h"
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
// define this switch to play MIDI during redisplay to sonify run times
|
|
|
|
// Note that if SONIFY is defined, the default MIDI device will be opened
|
|
|
|
// and may block normal MIDI playback.
|
|
|
|
//#define SONIFY 1
|
|
|
|
|
|
|
|
#ifdef SONIFY
|
|
|
|
|
|
|
|
#define SONFNS(name) \
|
|
|
|
void Begin ## name(); \
|
|
|
|
void End ## name();
|
|
|
|
|
|
|
|
SONFNS(NoteBackground)
|
|
|
|
SONFNS(NoteForeground)
|
|
|
|
SONFNS(Measures)
|
|
|
|
SONFNS(Serialize)
|
|
|
|
SONFNS(Unserialize)
|
|
|
|
SONFNS(ModifyState)
|
|
|
|
SONFNS(AutoSave)
|
|
|
|
|
|
|
|
#undef SONFNS
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
class wxDC;
|
|
|
|
class wxRect;
|
|
|
|
|
|
|
|
class DirManager;
|
|
|
|
class Alg_seq; // from "allegro.h"
|
|
|
|
|
2017-01-08 08:36:12 +00:00
|
|
|
using NoteTrackBase =
|
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
|
|
|
PlayableTrack
|
|
|
|
#else
|
|
|
|
AudioTrack
|
|
|
|
#endif
|
|
|
|
;
|
|
|
|
|
2017-05-11 17:41:26 +00:00
|
|
|
using QuantizedTimeAndBeat = std::pair< double, double >;
|
|
|
|
|
Changed lifetime management of UIHandle objects, no singletons...
... Rather, construct them during hit tests (also capturing more state sooner
rather than at Click time, and adding some accessors for later use)
This also fixes bug 1677 by other means and avoids similar problems.
A cell may be implemented to re-use a previously hit handle object, not yet
clicked, in a later hit test, by remembering a weak pointer, but TrackPanel
holds the strong pointers that determine when the object is destroyed.
And the objects will surely be destroyed after drag-release, or ESC key.
For now they are also destroyed whenever not dragging, and hit-testing is
re-invoked; that will be changed later, so that the re-use mentioned above
becomes effective, but still they will be destroyed when the pointer moves
from one cell to another.
2017-07-05 20:45:55 +00:00
|
|
|
class StretchHandle;
|
|
|
|
|
2017-01-08 08:36:12 +00:00
|
|
|
class AUDACITY_DLL_API NoteTrack final
|
|
|
|
: public NoteTrackBase
|
|
|
|
{
|
2010-01-23 19:44:49 +00:00
|
|
|
public:
|
2016-08-13 04:38:31 +00:00
|
|
|
NoteTrack(const std::shared_ptr<DirManager> &projDirManager);
|
2010-01-23 19:44:49 +00:00
|
|
|
virtual ~NoteTrack();
|
|
|
|
|
2017-06-29 14:34:57 +00:00
|
|
|
std::vector<UIHandlePtr> DetailedHitTest
|
2017-06-29 03:21:20 +00:00
|
|
|
(const TrackPanelMouseState &state,
|
2017-06-19 19:02:45 +00:00
|
|
|
const AudacityProject *pProject, int currentTool, bool bMultiTool)
|
|
|
|
override;
|
2015-07-07 03:12:16 +00:00
|
|
|
|
2016-03-02 19:37:47 +00:00
|
|
|
using Holder = std::unique_ptr<NoteTrack>;
|
|
|
|
Track::Holder Duplicate() const override;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2016-02-24 06:06:47 +00:00
|
|
|
int GetKind() const override { return Note; }
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-02-24 06:06:47 +00:00
|
|
|
double GetOffset() const override;
|
|
|
|
double GetStartTime() const override;
|
|
|
|
double GetEndTime() const override;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2017-05-25 00:28:44 +00:00
|
|
|
Alg_seq &GetSeq() const;
|
|
|
|
|
2010-10-28 17:57:14 +00:00
|
|
|
void WarpAndTransposeNotes(double t0, double t1,
|
|
|
|
const TimeWarper &warper, double semitones);
|
|
|
|
|
2017-06-12 19:51:06 +00:00
|
|
|
static void DrawLabelControls
|
2017-06-22 04:23:17 +00:00
|
|
|
( const NoteTrack *pTrack, wxDC & dc, const wxRect &rect,
|
|
|
|
int highlightedChannel = -1 );
|
Changed lifetime management of UIHandle objects, no singletons...
... Rather, construct them during hit tests (also capturing more state sooner
rather than at Click time, and adding some accessors for later use)
This also fixes bug 1677 by other means and avoids similar problems.
A cell may be implemented to re-use a previously hit handle object, not yet
clicked, in a later hit test, by remembering a weak pointer, but TrackPanel
holds the strong pointers that determine when the object is destroyed.
And the objects will surely be destroyed after drag-release, or ESC key.
For now they are also destroyed whenever not dragging, and hit-testing is
re-invoked; that will be changed later, so that the re-use mentioned above
becomes effective, but still they will be destroyed when the pointer moves
from one cell to another.
2017-07-05 20:45:55 +00:00
|
|
|
int FindChannel(const wxRect &rect, int mx, int my);
|
2017-02-22 02:05:35 +00:00
|
|
|
bool LabelClick(const wxRect &rect, int x, int y, bool right);
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2016-04-08 05:56:06 +00:00
|
|
|
void SetSequence(std::unique_ptr<Alg_seq> &&seq);
|
2010-01-23 19:44:49 +00:00
|
|
|
void PrintSequence();
|
|
|
|
|
2017-02-22 19:23:35 +00:00
|
|
|
Alg_seq *MakeExportableSeq(std::unique_ptr<Alg_seq> &cleanup) const;
|
|
|
|
bool ExportMIDI(const wxString &f) const;
|
|
|
|
bool ExportAllegro(const wxString &f) const;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
// High-level editing
|
2016-03-02 20:36:44 +00:00
|
|
|
Track::Holder Cut (double t0, double t1) override;
|
2016-11-26 13:48:08 +00:00
|
|
|
Track::Holder Copy (double t0, double t1, bool forClipboard = true) const override;
|
2016-02-24 06:06:47 +00:00
|
|
|
bool Trim (double t0, double t1) /* not override */;
|
2017-03-23 15:10:14 +00:00
|
|
|
void Clear(double t0, double t1) override;
|
|
|
|
void Paste(double t, const Track *src) override;
|
|
|
|
void Silence(double t0, double t1) override;
|
|
|
|
void InsertSilence(double t, double len) override;
|
2016-02-24 06:06:47 +00:00
|
|
|
bool Shift(double t) /* not override */;
|
2010-09-18 21:02:36 +00:00
|
|
|
|
2011-10-19 23:06:53 +00:00
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
2017-03-28 19:50:07 +00:00
|
|
|
float GetVelocity() const { return mVelocity; }
|
|
|
|
void SetVelocity(float velocity) { mVelocity = velocity; }
|
2011-10-19 23:06:53 +00:00
|
|
|
#endif
|
2010-09-18 21:02:36 +00:00
|
|
|
|
2017-05-11 17:41:26 +00:00
|
|
|
QuantizedTimeAndBeat NearestBeatTime( double time ) const;
|
|
|
|
bool StretchRegion
|
|
|
|
( QuantizedTimeAndBeat t0, QuantizedTimeAndBeat t1, double newDur );
|
2010-01-23 19:44:49 +00:00
|
|
|
|
|
|
|
int GetBottomNote() const { return mBottomNote; }
|
2010-09-18 21:02:36 +00:00
|
|
|
int GetPitchHeight() const { return mPitchHeight; }
|
|
|
|
void SetPitchHeight(int h) { mPitchHeight = h; }
|
2017-07-04 08:02:43 +00:00
|
|
|
/// Zooms out by one unit
|
|
|
|
void ZoomOut(const wxRect &rect, int y) { Zoom(rect, y, -1, true); }
|
|
|
|
/// Zooms in by one unit
|
|
|
|
void ZoomIn(const wxRect &rect, int y) { Zoom(rect, y, 1, true); }
|
|
|
|
/// Zoom the note track around y.
|
|
|
|
/// Positive amounts zoom in; negative amounts zoom out.
|
|
|
|
/// If center is true, the result will be centered at y.
|
|
|
|
void Zoom(const wxRect &rect, int y, int amount, bool center);
|
2017-06-04 03:20:37 +00:00
|
|
|
void ZoomTo(const wxRect &rect, int start, int end);
|
2017-07-19 15:28:14 +00:00
|
|
|
int GetNoteMargin(int height) const
|
2017-07-19 15:28:14 +00:00
|
|
|
{ return std::min(height / 4, (mPitchHeight + 1) / 2); }
|
2010-09-18 21:02:36 +00:00
|
|
|
int GetOctaveHeight() const { return mPitchHeight * 12 + 2; }
|
|
|
|
// call this once before a series of calls to IPitchToY(). It
|
|
|
|
// sets mBottom to offset of octave 0 so that mBottomNote
|
|
|
|
// is located at r.y + r.height - (GetNoteMargin() + 1 + mPitchHeight)
|
2016-02-27 17:12:40 +00:00
|
|
|
void PrepareIPitchToY(const wxRect &r) const {
|
2017-07-19 15:28:14 +00:00
|
|
|
mBottom = r.y + r.height - GetNoteMargin(r.height) - 1 - mPitchHeight +
|
2014-06-03 20:30:19 +00:00
|
|
|
(mBottomNote / 12) * GetOctaveHeight() +
|
2010-09-18 21:02:36 +00:00
|
|
|
GetNotePos(mBottomNote % 12);
|
|
|
|
}
|
|
|
|
// IPitchToY returns Y coordinate of top of pitch p
|
|
|
|
int IPitchToY(int p) const {
|
|
|
|
return mBottom - (p / 12) * GetOctaveHeight() - GetNotePos(p % 12);
|
|
|
|
}
|
|
|
|
// compute the window coordinate of the bottom of an octave: This is
|
|
|
|
// the bottom of the line separating B and C.
|
|
|
|
int GetOctaveBottom(int oct) const {
|
|
|
|
return IPitchToY(oct * 12) + mPitchHeight + 1;
|
|
|
|
}
|
|
|
|
// Y coordinate for given floating point pitch (rounded to int)
|
|
|
|
int PitchToY(double p) const {
|
|
|
|
return IPitchToY((int) (p + 0.5));
|
|
|
|
}
|
|
|
|
// Integer pitch corresponding to a Y coordinate
|
|
|
|
int YToIPitch(int y);
|
|
|
|
// map pitch class number (0-11) to pixel offset from bottom of octave
|
|
|
|
// (the bottom of the black line between B and C) to the top of the
|
2014-06-03 20:30:19 +00:00
|
|
|
// note. Note extra pixel separates B(11)/C(0) and E(4)/F(5).
|
2010-09-18 21:02:36 +00:00
|
|
|
int GetNotePos(int p) const { return 1 + mPitchHeight * (p + 1) + (p > 4); }
|
|
|
|
// get pixel offset to top of ith black key note
|
|
|
|
int GetBlackPos(int i) const { return GetNotePos(i * 2 + 1 + (i > 1)); }
|
|
|
|
// GetWhitePos tells where to draw lines between keys as an offset from
|
|
|
|
// GetOctaveBottom. GetWhitePos(0) returns 1, which matches the location
|
|
|
|
// of the line separating B and C
|
|
|
|
int GetWhitePos(int i) const { return 1 + (i * GetOctaveHeight()) / 7; }
|
2014-06-03 20:30:19 +00:00
|
|
|
void SetBottomNote(int note)
|
|
|
|
{
|
2010-01-23 19:44:49 +00:00
|
|
|
if (note < 0)
|
|
|
|
note = 0;
|
|
|
|
else if (note > 96)
|
|
|
|
note = 96;
|
|
|
|
|
2014-06-03 20:30:19 +00:00
|
|
|
mBottomNote = note;
|
2010-01-23 19:44:49 +00:00
|
|
|
}
|
2014-06-03 20:30:19 +00:00
|
|
|
// Vertical scrolling is performed by dragging the keyboard at
|
2010-09-18 21:02:36 +00:00
|
|
|
// left of track. Protocol is call StartVScroll, then update by
|
|
|
|
// calling VScroll with original and final mouse position.
|
|
|
|
// These functions are not used -- instead, zooming/dragging works like
|
|
|
|
// audio track zooming/dragging. The vertical scrolling is nice however,
|
|
|
|
// so I left these functions here for possible use in the future.
|
|
|
|
void StartVScroll();
|
|
|
|
void VScroll(int start, int end);
|
|
|
|
|
2016-02-24 06:06:47 +00:00
|
|
|
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override;
|
|
|
|
XMLTagHandler *HandleXMLChild(const wxChar *tag) override;
|
2017-02-22 19:23:35 +00:00
|
|
|
void WriteXML(XMLWriter &xmlFile) const override;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-10-28 17:57:14 +00:00
|
|
|
// channels are numbered as integers 0-15, visible channels
|
|
|
|
// (mVisibleChannels) is a bit set. Channels are displayed as
|
|
|
|
// integers 1-16.
|
2017-03-28 18:29:40 +00:00
|
|
|
|
|
|
|
// Allegro's 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.
|
2017-05-30 15:21:28 +00:00
|
|
|
#define NUM_CHANNELS 16
|
|
|
|
// Bitmask with all NUM_CHANNELS bits set
|
|
|
|
#define ALL_CHANNELS (1 << NUM_CHANNELS) - 1
|
|
|
|
#define CHANNEL_BIT(c) (1 << (c % NUM_CHANNELS))
|
2017-03-28 18:29:40 +00:00
|
|
|
bool IsVisibleChan(int c) const {
|
2010-10-28 17:57:14 +00:00
|
|
|
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); }
|
2017-03-28 18:29:40 +00:00
|
|
|
// Solos the given channel. If it's the only channel visible, all channels
|
|
|
|
// are enabled; otherwise, it is set to the only visible channel.
|
|
|
|
void SoloVisibleChan(int c) {
|
|
|
|
if (mVisibleChannels == CHANNEL_BIT(c))
|
|
|
|
mVisibleChannels = ALL_CHANNELS;
|
|
|
|
else
|
|
|
|
mVisibleChannels = CHANNEL_BIT(c);
|
|
|
|
}
|
2017-05-25 00:28:44 +00:00
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
private:
|
2017-05-25 00:28:44 +00:00
|
|
|
void AddToDuration( double delta );
|
|
|
|
|
|
|
|
// These are mutable to allow NoteTrack to switch details of representation
|
|
|
|
// in logically const methods
|
|
|
|
// At most one of the two pointers is not null at any time.
|
|
|
|
// Both are null in a newly constructed NoteTrack.
|
|
|
|
mutable std::unique_ptr<Alg_seq> mSeq;
|
|
|
|
mutable std::unique_ptr<char[]> mSerializationBuffer;
|
|
|
|
mutable long mSerializationLength;
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2011-10-19 23:06:53 +00:00
|
|
|
#ifdef EXPERIMENTAL_MIDI_OUT
|
2017-03-28 19:50:07 +00:00
|
|
|
float mVelocity; // velocity offset
|
2011-10-19 23:06:53 +00:00
|
|
|
#endif
|
2010-01-23 19:44:49 +00:00
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
// mBottom is the Y offset of pitch 0 (normally off screen)
|
2016-02-27 17:12:40 +00:00
|
|
|
mutable int mBottom;
|
2010-09-18 21:02:36 +00:00
|
|
|
int mBottomNote;
|
|
|
|
int mStartBottomNote;
|
|
|
|
int mPitchHeight;
|
2010-10-28 17:57:14 +00:00
|
|
|
int mVisibleChannels; // bit set of visible channels
|
2015-07-07 03:12:16 +00:00
|
|
|
|
Changed lifetime management of UIHandle objects, no singletons...
... Rather, construct them during hit tests (also capturing more state sooner
rather than at Click time, and adding some accessors for later use)
This also fixes bug 1677 by other means and avoids similar problems.
A cell may be implemented to re-use a previously hit handle object, not yet
clicked, in a later hit test, by remembering a weak pointer, but TrackPanel
holds the strong pointers that determine when the object is destroyed.
And the objects will surely be destroyed after drag-release, or ESC key.
For now they are also destroyed whenever not dragging, and hit-testing is
re-invoked; that will be changed later, so that the re-use mentioned above
becomes effective, but still they will be destroyed when the pointer moves
from one cell to another.
2017-07-05 20:45:55 +00:00
|
|
|
std::weak_ptr<StretchHandle> mStretchHandle;
|
|
|
|
|
2015-07-07 03:12:16 +00:00
|
|
|
protected:
|
2017-06-23 21:33:45 +00:00
|
|
|
std::shared_ptr<TrackControls> GetControls() override;
|
|
|
|
std::shared_ptr<TrackVRulerControls> GetVRulerControls() override;
|
2010-01-23 19:44:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#endif // USE_MIDI
|
|
|
|
|
2010-09-18 21:02:36 +00:00
|
|
|
#ifndef SONIFY
|
|
|
|
// no-ops:
|
|
|
|
#define SonifyBeginSonification()
|
|
|
|
#define SonifyEndSonification()
|
|
|
|
#define SonifyBeginNoteBackground()
|
|
|
|
#define SonifyEndNoteBackground()
|
|
|
|
#define SonifyBeginNoteForeground()
|
|
|
|
#define SonifyEndNoteForeground()
|
|
|
|
#define SonifyBeginMeasures()
|
|
|
|
#define SonifyEndMeasures()
|
|
|
|
#define SonifyBeginSerialize()
|
|
|
|
#define SonifyEndSerialize()
|
|
|
|
#define SonifyBeginUnserialize()
|
|
|
|
#define SonifyEndUnserialize()
|
|
|
|
#define SonifyBeginAutoSave()
|
|
|
|
#define SonifyEndAutoSave()
|
|
|
|
#define SonifyBeginModifyState()
|
|
|
|
#define SonifyEndModifyState()
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
#endif
|