2010-01-23 19:44:49 +00:00
|
|
|
/**********************************************************************
|
|
|
|
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
|
|
|
|
NoteTrack.h
|
|
|
|
|
|
|
|
Dominic Mazzoni
|
|
|
|
|
|
|
|
**********************************************************************/
|
|
|
|
|
|
|
|
#ifndef __AUDACITY_NOTETRACK__
|
|
|
|
#define __AUDACITY_NOTETRACK__
|
|
|
|
|
2021-05-09 15:16:56 +00:00
|
|
|
|
2018-11-11 02:40:37 +00:00
|
|
|
|
2020-06-19 19:43:09 +00:00
|
|
|
|
2018-11-11 17:27:44 +00:00
|
|
|
|
2017-05-11 17:41:26 +00:00
|
|
|
#include <utility>
|
2010-01-23 19:44:49 +00:00
|
|
|
#include "Track.h"
|
|
|
|
|
|
|
|
#if defined(USE_MIDI)
|
|
|
|
|
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 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;
|
2020-06-13 15:20:45 +00:00
|
|
|
class TimeWarper;
|
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
|
|
|
|
2017-01-08 08:36:12 +00:00
|
|
|
class AUDACITY_DLL_API NoteTrack final
|
|
|
|
: public NoteTrackBase
|
|
|
|
{
|
2018-11-19 01:15:26 +00:00
|
|
|
public:
|
2020-07-02 16:42:25 +00:00
|
|
|
NoteTrack();
|
2010-01-23 19:44:49 +00:00
|
|
|
virtual ~NoteTrack();
|
|
|
|
|
2018-11-19 04:07:05 +00:00
|
|
|
using Holder = std::shared_ptr<NoteTrack>;
|
2018-11-19 01:15:26 +00:00
|
|
|
|
|
|
|
private:
|
|
|
|
Track::Holder Clone() const override;
|
2014-06-03 20:30:19 +00:00
|
|
|
|
2018-11-19 01:15:26 +00:00
|
|
|
public:
|
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; }
|
2018-02-05 22:56:46 +00:00
|
|
|
void SetVelocity(float 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
|
|
|
|
2018-01-14 20:08:20 +00:00
|
|
|
/// Gets the current bottom note (a pitch)
|
2010-01-23 19:44:49 +00:00
|
|
|
int GetBottomNote() const { return mBottomNote; }
|
2018-01-14 20:08:20 +00:00
|
|
|
/// Gets the current top note (a pitch)
|
|
|
|
int GetTopNote() const { return mTopNote; }
|
|
|
|
/// Sets the bottom note (a pitch), making sure that it is never greater than the top note.
|
|
|
|
void SetBottomNote(int note);
|
|
|
|
/// Sets the top note (a pitch), making sure that it is never less than the bottom note.
|
|
|
|
void SetTopNote(int note);
|
|
|
|
/// Sets the top and bottom note (both pitches) automatically, swapping them if needed.
|
|
|
|
void SetNoteRange(int note1, int note2);
|
|
|
|
|
2018-01-14 20:08:54 +00:00
|
|
|
/// Zooms so that all notes are visible
|
|
|
|
void ZoomAllNotes();
|
2018-01-14 20:08:20 +00:00
|
|
|
/// Zooms so that the entire track is visible
|
|
|
|
void ZoomMaxExtent() { SetNoteRange(MinPitch, MaxPitch); }
|
|
|
|
/// Shifts all notes vertically by the given pitch
|
|
|
|
void ShiftNoteRange(int offset);
|
|
|
|
|
2017-07-19 18:00:27 +00:00
|
|
|
/// Zooms out a constant factor (subject to zoom limits)
|
|
|
|
void ZoomOut(const wxRect &rect, int y) { Zoom(rect, y, 1.0f / ZoomStep, true); }
|
2020-04-11 07:08:33 +00:00
|
|
|
/// Zooms in a constant factor (subject to zoom limits)
|
2017-07-19 18:00:27 +00:00
|
|
|
void ZoomIn(const wxRect &rect, int y) { Zoom(rect, y, ZoomStep, true); }
|
2017-07-04 08:02:43 +00:00
|
|
|
/// Zoom the note track around y.
|
|
|
|
/// If center is true, the result will be centered at y.
|
2017-07-19 18:00:27 +00:00
|
|
|
void Zoom(const wxRect &rect, int y, float multiplier, bool center);
|
2017-06-04 03:20:37 +00:00
|
|
|
void ZoomTo(const wxRect &rect, int start, int end);
|
2017-07-19 18:00:27 +00:00
|
|
|
|
|
|
|
#if 0
|
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);
|
2017-07-19 18:00:27 +00:00
|
|
|
#endif
|
2010-09-18 21:02:36 +00:00
|
|
|
|
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
|
|
|
|
2021-01-29 05:25:33 +00:00
|
|
|
Track::Holder PasteInto( AudacityProject & ) const override;
|
|
|
|
|
2020-09-09 11:35:58 +00:00
|
|
|
ConstIntervals GetIntervals() const override;
|
|
|
|
Intervals GetIntervals() override;
|
|
|
|
|
2010-01-23 19:44:49 +00:00
|
|
|
private:
|
2017-01-06 18:55:57 +00:00
|
|
|
|
2017-01-06 06:59:17 +00:00
|
|
|
TrackKind GetKind() const override { return TrackKind::Note; }
|
|
|
|
|
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
|
|
|
|
2018-01-14 20:08:20 +00:00
|
|
|
int mBottomNote, mTopNote;
|
2018-01-14 20:08:13 +00:00
|
|
|
#if 0
|
|
|
|
// Also unused from vertical scrolling
|
2010-09-18 21:02:36 +00:00
|
|
|
int mStartBottomNote;
|
2018-01-14 20:08:13 +00:00
|
|
|
#endif
|
2017-07-19 18:00:27 +00:00
|
|
|
|
|
|
|
// Remember continuous variation for zooming,
|
|
|
|
// but it is rounded off whenever drawing:
|
|
|
|
float mPitchHeight;
|
|
|
|
|
2018-01-14 20:08:20 +00:00
|
|
|
enum { MinPitch = 0, MaxPitch = 127 };
|
2017-07-19 18:00:27 +00:00
|
|
|
static const float ZoomStep;
|
|
|
|
|
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;
|
2010-01-23 19:44:49 +00:00
|
|
|
};
|
|
|
|
|
2018-01-14 20:08:20 +00:00
|
|
|
/// Data used to display a note track
|
2018-01-14 20:08:13 +00:00
|
|
|
class NoteTrackDisplayData {
|
|
|
|
private:
|
|
|
|
float mPitchHeight;
|
|
|
|
// mBottom is the Y offset of pitch 0 (normally off screen)
|
|
|
|
// Used so that mBottomNote is located at
|
|
|
|
// mY + mHeight - (GetNoteMargin() + 1 + GetPitchHeight())
|
|
|
|
int mBottom;
|
|
|
|
int mMargin;
|
2018-01-14 20:08:20 +00:00
|
|
|
|
|
|
|
enum { MinPitchHeight = 1, MaxPitchHeight = 25 };
|
2018-01-14 20:08:13 +00:00
|
|
|
public:
|
|
|
|
NoteTrackDisplayData(const NoteTrack* track, const wxRect &r);
|
|
|
|
|
|
|
|
int GetPitchHeight(int factor) const
|
|
|
|
{ return std::max(1, (int)(factor * mPitchHeight)); }
|
|
|
|
int GetNoteMargin() const { return mMargin; };
|
|
|
|
int GetOctaveHeight() const { return GetPitchHeight(12) + 2; }
|
|
|
|
// IPitchToY returns Y coordinate of top of pitch p
|
|
|
|
int IPitchToY(int p) const;
|
|
|
|
// 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) + GetPitchHeight(1) + 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) const;
|
|
|
|
// 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
|
|
|
|
// note. Note extra pixel separates B(11)/C(0) and E(4)/F(5).
|
|
|
|
int GetNotePos(int p) const
|
|
|
|
{ return 1 + GetPitchHeight(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; }
|
|
|
|
};
|
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
|