1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   NoteTrack.h
6 
7   Dominic Mazzoni
8 
9 **********************************************************************/
10 
11 #ifndef __AUDACITY_NOTETRACK__
12 #define __AUDACITY_NOTETRACK__
13 
14 
15 
16 
17 
18 #include <utility>
19 #include "Prefs.h"
20 #include "Track.h"
21 
22 #if defined(USE_MIDI)
23 
24 // define this switch to play MIDI during redisplay to sonify run times
25 // Note that if SONIFY is defined, the default MIDI device will be opened
26 // and may block normal MIDI playback.
27 //#define SONIFY 1
28 
29 #ifdef SONIFY
30 
31 #define SONFNS(name) \
32    void Begin ## name(); \
33    void End ## name();
34 
35 SONFNS(NoteBackground)
36 SONFNS(NoteForeground)
37 SONFNS(Measures)
38 SONFNS(Serialize)
39 SONFNS(Unserialize)
40 SONFNS(ModifyState)
41 SONFNS(AutoSave)
42 
43 #undef SONFNS
44 
45 #endif
46 
47 class wxDC;
48 class wxRect;
49 
50 class Alg_seq;   // from "allegro.h"
51 
52 using NoteTrackBase =
53 #ifdef EXPERIMENTAL_MIDI_OUT
54    PlayableTrack
55 #else
56    AudioTrack
57 #endif
58    ;
59 
60 using QuantizedTimeAndBeat = std::pair< double, double >;
61 
62 class StretchHandle;
63 class TimeWarper;
64 
65 class AUDACITY_DLL_API NoteTrack final
66    : public NoteTrackBase
67 {
68 public:
69    NoteTrack();
70    virtual ~NoteTrack();
71 
72    using Holder = std::shared_ptr<NoteTrack>;
73 
74 private:
75    Track::Holder Clone() const override;
76 
77 public:
78    double GetOffset() const override;
79    double GetStartTime() const override;
80    double GetEndTime() const override;
81 
82    Alg_seq &GetSeq() const;
83 
84    void WarpAndTransposeNotes(double t0, double t1,
85                               const TimeWarper &warper, double semitones);
86 
87    static void DrawLabelControls
88       ( const NoteTrack *pTrack, wxDC & dc, const wxRect &rect,
89         int highlightedChannel = -1 );
90    int FindChannel(const wxRect &rect, int mx, int my);
91    bool LabelClick(const wxRect &rect, int x, int y, bool right);
92 
93    void SetSequence(std::unique_ptr<Alg_seq> &&seq);
94    void PrintSequence();
95 
96    Alg_seq *MakeExportableSeq(std::unique_ptr<Alg_seq> &cleanup) const;
97    bool ExportMIDI(const wxString &f) const;
98    bool ExportAllegro(const wxString &f) const;
99 
100    // High-level editing
101    Track::Holder Cut  (double t0, double t1) override;
102    Track::Holder Copy (double t0, double t1, bool forClipboard = true) const override;
103    bool Trim (double t0, double t1) /* not override */;
104    void Clear(double t0, double t1) override;
105    void Paste(double t, const Track *src) override;
106    void Silence(double t0, double t1) override;
107    void InsertSilence(double t, double len) override;
108    bool Shift(double t) /* not override */;
109 
110 #ifdef EXPERIMENTAL_MIDI_OUT
GetVelocity()111    float GetVelocity() const { return mVelocity; }
112    void SetVelocity(float velocity);
113 #endif
114 
115    QuantizedTimeAndBeat NearestBeatTime( double time ) const;
116    bool StretchRegion
117       ( QuantizedTimeAndBeat t0, QuantizedTimeAndBeat t1, double newDur );
118 
119    /// Gets the current bottom note (a pitch)
GetBottomNote()120    int GetBottomNote() const { return mBottomNote; }
121    /// Gets the current top note (a pitch)
GetTopNote()122    int GetTopNote() const { return mTopNote; }
123    /// Sets the bottom note (a pitch), making sure that it is never greater than the top note.
124    void SetBottomNote(int note);
125    /// Sets the top note (a pitch), making sure that it is never less than the bottom note.
126    void SetTopNote(int note);
127    /// Sets the top and bottom note (both pitches) automatically, swapping them if needed.
128    void SetNoteRange(int note1, int note2);
129 
130    /// Zooms so that all notes are visible
131    void ZoomAllNotes();
132    /// Zooms so that the entire track is visible
ZoomMaxExtent()133    void ZoomMaxExtent() { SetNoteRange(MinPitch, MaxPitch); }
134    /// Shifts all notes vertically by the given pitch
135    void ShiftNoteRange(int offset);
136 
137    /// Zooms out a constant factor (subject to zoom limits)
ZoomOut(const wxRect & rect,int y)138    void ZoomOut(const wxRect &rect, int y) { Zoom(rect, y, 1.0f / ZoomStep, true); }
139    /// Zooms in a constant factor (subject to zoom limits)
ZoomIn(const wxRect & rect,int y)140    void ZoomIn(const wxRect &rect, int y) { Zoom(rect, y, ZoomStep, true); }
141    /// Zoom the note track around y.
142    /// If center is true, the result will be centered at y.
143    void Zoom(const wxRect &rect, int y, float multiplier, bool center);
144    void ZoomTo(const wxRect &rect, int start, int end);
145 
146 #if 0
147    // Vertical scrolling is performed by dragging the keyboard at
148    // left of track. Protocol is call StartVScroll, then update by
149    // calling VScroll with original and final mouse position.
150    // These functions are not used -- instead, zooming/dragging works like
151    // audio track zooming/dragging. The vertical scrolling is nice however,
152    // so I left these functions here for possible use in the future.
153    void StartVScroll();
154    void VScroll(int start, int end);
155 #endif
156 
157    bool HandleXMLTag(const std::string_view& tag, const AttributesList& attrs) override;
158    XMLTagHandler *HandleXMLChild(const std::string_view& tag) override;
159    void WriteXML(XMLWriter &xmlFile) const override;
160 
161    // channels are numbered as integers 0-15, visible channels
162    // (mVisibleChannels) is a bit set. Channels are displayed as
163    // integers 1-16.
164 
165    // Allegro's data structure does not restrict channels to 16.
166    // Since there is not way to select more than 16 channels,
167    // map all channel numbers mod 16. This will have no effect
168    // on MIDI files, but it will allow users to at least select
169    // all channels on non-MIDI event sequence data.
170 #define NUM_CHANNELS 16
171    // Bitmask with all NUM_CHANNELS bits set
172 #define ALL_CHANNELS (1 << NUM_CHANNELS) - 1
173 #define CHANNEL_BIT(c) (1 << (c % NUM_CHANNELS))
IsVisibleChan(int c)174    bool IsVisibleChan(int c) const {
175       return (mVisibleChannels & CHANNEL_BIT(c)) != 0;
176    }
SetVisibleChan(int c)177    void SetVisibleChan(int c) { mVisibleChannels |= CHANNEL_BIT(c); }
ClearVisibleChan(int c)178    void ClearVisibleChan(int c) { mVisibleChannels &= ~CHANNEL_BIT(c); }
ToggleVisibleChan(int c)179    void ToggleVisibleChan(int c) { mVisibleChannels ^= CHANNEL_BIT(c); }
180    // Solos the given channel.  If it's the only channel visible, all channels
181    // are enabled; otherwise, it is set to the only visible channel.
SoloVisibleChan(int c)182    void SoloVisibleChan(int c) {
183       if (mVisibleChannels == CHANNEL_BIT(c))
184          mVisibleChannels = ALL_CHANNELS;
185       else
186          mVisibleChannels = CHANNEL_BIT(c);
187    }
188 
189    Track::Holder PasteInto( AudacityProject & ) const override;
190 
191    ConstIntervals GetIntervals() const override;
192    Intervals GetIntervals() override;
193 
194  private:
195 
GetKind()196    TrackKind GetKind() const override { return TrackKind::Note; }
197 
198    void AddToDuration( double delta );
199 
200    // These are mutable to allow NoteTrack to switch details of representation
201    // in logically const methods
202    // At most one of the two pointers is not null at any time.
203    // Both are null in a newly constructed NoteTrack.
204    mutable std::unique_ptr<Alg_seq> mSeq;
205    mutable std::unique_ptr<char[]> mSerializationBuffer;
206    mutable long mSerializationLength;
207 
208 #ifdef EXPERIMENTAL_MIDI_OUT
209    float mVelocity; // velocity offset
210 #endif
211 
212    int mBottomNote, mTopNote;
213 #if 0
214    // Also unused from vertical scrolling
215    int mStartBottomNote;
216 #endif
217 
218    // Remember continuous variation for zooming,
219    // but it is rounded off whenever drawing:
220    float mPitchHeight;
221 
222    enum { MinPitch = 0, MaxPitch = 127 };
223    static const float ZoomStep;
224 
225    int mVisibleChannels; // bit set of visible channels
226 
227    std::weak_ptr<StretchHandle> mStretchHandle;
228 };
229 
230 /// Data used to display a note track
231 class NoteTrackDisplayData {
232 private:
233    float mPitchHeight;
234    // mBottom is the Y offset of pitch 0 (normally off screen)
235    // Used so that mBottomNote is located at
236    // mY + mHeight - (GetNoteMargin() + 1 + GetPitchHeight())
237    int mBottom;
238    int mMargin;
239 
240    enum { MinPitchHeight = 1, MaxPitchHeight = 25 };
241 public:
242    NoteTrackDisplayData(const NoteTrack* track, const wxRect &r);
243 
GetPitchHeight(int factor)244    int GetPitchHeight(int factor) const
245    { return std::max(1, (int)(factor * mPitchHeight)); }
GetNoteMargin()246    int GetNoteMargin() const { return mMargin; };
GetOctaveHeight()247    int GetOctaveHeight() const { return GetPitchHeight(12) + 2; }
248    // IPitchToY returns Y coordinate of top of pitch p
249    int IPitchToY(int p) const;
250    // compute the window coordinate of the bottom of an octave: This is
251    // the bottom of the line separating B and C.
GetOctaveBottom(int oct)252    int GetOctaveBottom(int oct) const {
253       return IPitchToY(oct * 12) + GetPitchHeight(1) + 1;
254    }
255    // Y coordinate for given floating point pitch (rounded to int)
PitchToY(double p)256    int PitchToY(double p) const {
257       return IPitchToY((int) (p + 0.5));
258    }
259    // Integer pitch corresponding to a Y coordinate
260    int YToIPitch(int y) const;
261    // map pitch class number (0-11) to pixel offset from bottom of octave
262    // (the bottom of the black line between B and C) to the top of the
263    // note. Note extra pixel separates B(11)/C(0) and E(4)/F(5).
GetNotePos(int p)264    int GetNotePos(int p) const
265    { return 1 + GetPitchHeight(p + 1) + (p > 4); }
266    // get pixel offset to top of ith black key note
GetBlackPos(int i)267    int GetBlackPos(int i) const { return GetNotePos(i * 2 + 1 + (i > 1)); }
268    // GetWhitePos tells where to draw lines between keys as an offset from
269    // GetOctaveBottom. GetWhitePos(0) returns 1, which matches the location
270    // of the line separating B and C
GetWhitePos(int i)271    int GetWhitePos(int i) const { return 1 + (i * GetOctaveHeight()) / 7; }
272 };
273 
274 extern AUDACITY_DLL_API StringSetting MIDIPlaybackDevice;
275 extern AUDACITY_DLL_API StringSetting MIDIRecordingDevice;
276 extern AUDACITY_DLL_API IntSetting MIDISynthLatency_ms;
277 
278 #endif // USE_MIDI
279 
280 #ifndef SONIFY
281 // no-ops:
282 #define SonifyBeginSonification()
283 #define SonifyEndSonification()
284 #define SonifyBeginNoteBackground()
285 #define SonifyEndNoteBackground()
286 #define SonifyBeginNoteForeground()
287 #define SonifyEndNoteForeground()
288 #define SonifyBeginMeasures()
289 #define SonifyEndMeasures()
290 #define SonifyBeginSerialize()
291 #define SonifyEndSerialize()
292 #define SonifyBeginUnserialize()
293 #define SonifyEndUnserialize()
294 #define SonifyBeginAutoSave()
295 #define SonifyEndAutoSave()
296 #define SonifyBeginModifyState()
297 #define SonifyEndModifyState()
298 #endif
299 
300 
301 AUDACITY_DLL_API wxString GetMIDIDeviceInfo();
302 
303 #endif
304