Zrythm v2.0.0-alpha.1
a highly automated and intuitive digital audio workstation
Loading...
Searching...
No Matches
midi_note.h
1// SPDX-FileCopyrightText: © 2018-2025 Alexandros Theodotou <alex@zrythm.org>
2// SPDX-License-Identifier: LicenseRef-ZrythmLicense
3
4#pragma once
5
6#include <cstdint>
7
8#include "structure/arrangement/arranger_object.h"
9#include "utils/midi.h"
10
11#include <nlohmann/json_fwd.hpp>
12
13namespace zrythm::structure::arrangement
14{
15
16template <typename T>
18
22class MidiNote : public ArrangerObject
23{
24 Q_OBJECT
25 Q_PROPERTY (int pitch READ pitch WRITE setPitch NOTIFY pitchChanged)
26 Q_PROPERTY (
27 int velocity READ velocity WRITE setVelocity NOTIFY velocityChanged)
28 Q_PROPERTY (
29 int midiChannel READ midiChannel WRITE setMidiChannel NOTIFY
30 midiChannelChanged)
31 QML_ELEMENT
32 QML_UNCREATABLE ("")
33
34public:
35 MidiNote (
36 const dsp::TempoMapWrapper &tempo_map_wrapper,
37 QObject * parent = nullptr);
38 Q_DISABLE_COPY_MOVE (MidiNote)
39 ~MidiNote () override;
40
41 dsp::ContentPosition * position () const override
42 {
43 return qobject_cast<dsp::ContentPosition *> (ArrangerObject::position ());
44 }
45
46 static constexpr midi_byte_t DEFAULT_VELOCITY = 90;
47
48 enum class Notation : std::uint8_t
49 {
50 Musical,
51 Pitch,
52 };
53
54 // ========================================================================
55 // QML Interface
56 // ========================================================================
57
58 int pitch () const { return static_cast<int> (pitch_); }
59 void setPitch (int ipitch)
60 {
61 ipitch = std::clamp (ipitch, 0, 127);
62 const auto pitch = static_cast<midi_byte_t> (ipitch);
63 if (pitch_ != pitch)
64 {
65 pitch_ = pitch;
66 Q_EMIT pitchChanged (ipitch);
67 }
68 }
69 /**
70 * Shifts the pitch by @p delta amount.
71 */
72 Q_INVOKABLE void shift_pitch (int delta)
73 {
74 setPitch (static_cast<int> (pitch_) + delta);
75 }
76 Q_SIGNAL void pitchChanged (int ipitch);
77 Q_INVOKABLE QString pitchAsRichText () const
78 {
79 return pitch_to_string (pitch_, Notation::Musical, true).to_qstring ();
80 }
81
82 int velocity () const { return static_cast<int> (velocity_); }
83 void setVelocity (int ivelocity)
84 {
85 ivelocity = std::clamp (ivelocity, 0, 127);
86 const auto velocity = static_cast<midi_byte_t> (ivelocity);
87 if (velocity_ != velocity)
88 {
89 velocity_ = velocity;
90 Q_EMIT velocityChanged (ivelocity);
91 }
92 }
93 Q_SIGNAL void velocityChanged (int ivelocity);
94
95 int midiChannel () const { return static_cast<int> (midi_channel_); }
96 void setMidiChannel (int ichannel)
97 {
98 ichannel = std::clamp (ichannel, 0, 15);
99 const auto channel = static_cast<uint8_t> (ichannel);
100 if (midi_channel_ != channel)
101 {
102 midi_channel_ = channel;
103 Q_EMIT midiChannelChanged (ichannel);
104 }
105 }
106 Q_SIGNAL void midiChannelChanged (int ichannel);
107
108 // ========================================================================
109
115 static utils::Utf8String
116 pitch_to_string (uint8_t pitch, Notation notation, bool rich_text);
117
118private:
120 * Returns the note starting first, or nullptr.
121 */
122 template <RangeOfMidiNotePointers Range>
123 friend MidiNote * get_first_midi_note (const Range &range)
124 {
125 auto it = std::ranges::min_element (range, [] (const auto &a, const auto &b) {
126 return a->position ()->ticks () < b->position ()->ticks ();
127 });
128 return (it != range.end ()) ? *it : nullptr;
129 }
130
132 * Gets last midi note
133 */
134 template <RangeOfMidiNotePointers Range>
135 friend MidiNote * get_last_midi_note (const Range &range)
136 {
137 // take the reverse because max_element returns the first match, and we want
138 // to return the last match in the container in this case
139 const auto range_to_test = range | std::views::reverse;
140 auto it = std::ranges::max_element (
141 range_to_test, [] (const auto &a, const auto &b) {
142 return (a->position ()->ticks () + a->length ()->ticks ())
143 < (b->position ()->ticks () + b->length ()->ticks ());
144 });
145 return (it != range_to_test.end ()) ? *it : nullptr;
146 }
147
149 * Returns the minimum and maximum pitches in the given range.
150 */
151 template <RangeOfMidiNotePointers Range>
152 friend auto get_pitch_range (const Range &range)
153 -> std::optional<std::pair<midi_byte_t, midi_byte_t>>
154 {
155 auto [min_it, max_it] =
156 std::ranges::minmax_element (range, [] (const auto &a, const auto &b) {
157 return a->pitch () < b->pitch ();
158 });
159 if (min_it == range.end () || max_it == range.end ())
160 return std::nullopt;
161 return std::make_pair ((*min_it)->pitch (), (*max_it)->pitch ());
162 }
163
164 friend void init_from (
165 MidiNote &obj,
166 const MidiNote &other,
167 utils::ObjectCloneType clone_type);
168
169 static constexpr auto kVelocityKey = "velocity"sv;
170 static constexpr auto kPitchKey = "pitch"sv;
171 static constexpr auto kChannelKey = "midiChannel"sv;
172 friend void to_json (nlohmann::json &j, const MidiNote &note);
173 friend void from_json (const nlohmann::json &j, MidiNote &note);
174
175private:
177 std::uint8_t velocity_{ DEFAULT_VELOCITY };
178
180 std::uint8_t pitch_{ 60 };
181
183 std::uint8_t midi_channel_{ 0 };
184
185 BOOST_DESCRIBE_CLASS (
186 MidiNote,
188 (),
189 (),
190 (velocity_, pitch_, midi_channel_))
191};
192
193}
A Position whose ticks are content-space (clip-local) ticks.
Definition position.h:118
ArrangerObject(Type type, const dsp::TempoMapWrapper &tempo_map_wrapper, ArrangerObjectFeatures features, QObject *parent=nullptr) noexcept
Construct a new ArrangerObject.
A MIDI note inside a Clip shown in the piano roll.
Definition midi_note.h:23
friend auto get_pitch_range(const Range &range) -> std::optional< std::pair< midi_byte_t, midi_byte_t > >
Returns the minimum and maximum pitches in the given range.
Definition midi_note.h:149
Q_INVOKABLE void shift_pitch(int delta)
Shifts the pitch by delta amount.
Definition midi_note.h:69
static utils::Utf8String pitch_to_string(uint8_t pitch, Notation notation, bool rich_text)
Gets the MIDI note's value as a string (eg "C#4").
friend MidiNote * get_first_midi_note(const Range &range)
Returns the note starting first, or nullptr.
Definition midi_note.h:120
friend MidiNote * get_last_midi_note(const Range &range)
Gets last midi note.
Definition midi_note.h:132
Lightweight UTF-8 string wrapper with safe conversions.
Definition utf8_string.h:37
MIDI utils.
std::uint8_t midi_byte_t
MIDI byte.
Definition midi.h:43