MIDI2LR 6.3.0.1
MIDI2LR is an application that interfaces MIDI controllers with Lightroom 6+/CC Classic. It processes MIDI input into develop parameter updates and photo actions, and sends MIDI output when parameters are changed for motorized feedback (on controllers that have motorized faders). A listing of available LightRoom commands is in the Wiki. Assistance on the code and design is welcome.
Loading...
Searching...
No Matches
Profile Class Reference

#include <Profile.h>

Public Member Functions

 Profile (const CommandSet &command_set) noexcept
bool CommandHasAssociatedMessage (const std::string &command) const
void FromXml (const juce::XmlElement *root)
const std::string & GetCommandForMessage (rsj::MidiMessageId message) const
rsj::MidiMessageId GetMessageForNumber (size_t num) const
std::vector< rsj::MidiMessageIdGetMessagesForCommand (const std::string &command) const
int GetRowForMessage (rsj::MidiMessageId message) const
void InsertOrAssign (const std::string &command, rsj::MidiMessageId message)
void InsertOrAssign (size_t command, rsj::MidiMessageId message)
void InsertUnassigned (rsj::MidiMessageId message)
bool MessageExistsInMap (rsj::MidiMessageId message) const
bool ProfileUnsaved () const
void RemoveAllRows ()
void RemoveMessage (rsj::MidiMessageId message)
void RemoveRow (size_t row)
void RemoveUnassignedMessages ()
void Resort (std::pair< int, bool > new_order)
size_t Size () const
void ToXmlFile (const juce::File &file)

Private Types

using mm_abbrv_lmnt_t = std::pair<rsj::MidiMessageId, std::string>

Private Member Functions

void InsertOrAssignI (const std::string &command, const rsj::MidiMessageId &message)
bool MessageExistsInMapI (rsj::MidiMessageId message) const
void SortI ()

Private Attributes

const CommandSetcommand_set_
std::pair< int, bool > current_sort_ {2, true}
std::vector< mm_abbrv_lmnt_tmm_abbrv_table_ {}
std::shared_mutex mutex_
bool profile_unsaved_ {false}
std::vector< mm_abbrv_lmnt_tsaved_mm_abbrv_table_ {}
std::mutex saved_table_mtx_

Member Typedef Documentation

◆ mm_abbrv_lmnt_t

using Profile::mm_abbrv_lmnt_t = std::pair<rsj::MidiMessageId, std::string>
private

Constructor & Destructor Documentation

◆ Profile()

Profile::Profile ( const CommandSet & command_set)
inlineexplicitnoexcept
38: command_set_ {command_set} {}
const CommandSet & command_set_
Definition Profile.h:67

References command_set_.

Member Function Documentation

◆ CommandHasAssociatedMessage()

bool Profile::CommandHasAssociatedMessage ( const std::string & command) const
inlinenodiscard
79{
80 auto guard {std::shared_lock {mutex_}};
81#ifdef __cpp_lib_ranges_contains
82 return std::ranges::contains(mm_abbrv_table_, command, &mm_abbrv_lmnt_t::second);
83#else
84 return std::ranges::any_of(mm_abbrv_table_,
85 [&command](const auto& p) { return p.second == command; });
86#endif
87}
std::vector< mm_abbrv_lmnt_t > mm_abbrv_table_
Definition Profile.h:74
std::shared_mutex mutex_
Definition Profile.h:72

Referenced by CommandMenu::AddSubMenuItems().

◆ FromXml()

void Profile::FromXml ( const juce::XmlElement * root)
38{
39 /* external use only, but will either use external versions of Profile calls to lock individual
40 * accesses or manually lock any internal calls instead of using mutex for entire method */
41 try {
42 if (!root || root->getTagName().compare("settings") != 0) { return; }
44 for (const gsl::not_null<juce::XmlElement*> setting : root->getChildIterator()) {
45 auto command {setting->getStringAttribute("command_string").toStdString()};
46 if (const auto it = replace_me.find(command); it != replace_me.end()) {
47 command = it->second;
48 }
49 if (setting->hasAttribute("controller")) {
50 const rsj::MidiMessageId message {setting->getIntAttribute("channel"),
51 setting->getIntAttribute("controller"), rsj::MessageType::kCc};
52 InsertOrAssign(command, message);
53 }
54 else if (setting->hasAttribute("note")) {
55 const rsj::MidiMessageId note {setting->getIntAttribute("channel"),
56 setting->getIntAttribute("note"), rsj::MessageType::kNoteOn};
57 InsertOrAssign(command, note);
58 }
59 else if (setting->hasAttribute("pitchbend")) {
60 const rsj::MidiMessageId pb {
61 setting->getIntAttribute("channel"), 0, rsj::MessageType::kPw};
62 InsertOrAssign(command, pb);
63 }
64 else { /* no action needed */
65 continue;
66 }
67 }
68 auto guard {std::unique_lock {mutex_}};
69 SortI();
71 profile_unsaved_ = false;
72 }
73 catch (const std::exception& e) {
74 rsj::ExceptionResponse(e, std::source_location::current());
75 throw;
76 }
77}
void InsertOrAssign(const std::string &command, rsj::MidiMessageId message)
Definition Profile.h:110
bool profile_unsaved_
Definition Profile.h:66
void RemoveAllRows()
Definition Profile.cpp:133
std::vector< mm_abbrv_lmnt_t > saved_mm_abbrv_table_
Definition Profile.h:75
void SortI()
Definition Profile.cpp:217
@ kCc
Definition MidiUtilities.h:45
@ kNoteOn
Definition MidiUtilities.h:43
@ kPw
Definition MidiUtilities.h:48
void ExceptionResponse(gsl::czstring id, gsl::czstring fu, const std::exception &e) noexcept

References profile_unsaved_, RemoveAllRows(), and SortI().

◆ GetCommandForMessage()

const std::string & Profile::GetCommandForMessage ( rsj::MidiMessageId message) const
inlinenodiscard
90{
91 auto guard {std::shared_lock {mutex_}};
92 const auto found {std::ranges::find(mm_abbrv_table_, message, &mm_abbrv_lmnt_t::first)};
93 if (found != mm_abbrv_table_.end()) { return found->second; }
95}
static const std::string kUnassigned
Definition CommandSet.h:70

Referenced by CommandTableModel::CreateNewCommandMenu(), LrIpcOut::ProcessMessage(), and CommandTableModel::UpdateCommandMenu().

◆ GetMessageForNumber()

rsj::MidiMessageId Profile::GetMessageForNumber ( size_t num) const
inlinenodiscard
98{
99 auto guard {std::shared_lock {mutex_}};
100 return mm_abbrv_table_.at(num).first;
101}

Referenced by CommandTableModel::CreateNewCommandMenu(), CommandTableModel::paintCell(), and CommandTableModel::UpdateCommandMenu().

◆ GetMessagesForCommand()

std::vector< rsj::MidiMessageId > Profile::GetMessagesForCommand ( const std::string & command) const
nodiscard
80{
81 try {
82 auto guard {std::shared_lock {mutex_}};
83 const auto filt {[&command](const auto& p) { return p.second == command; }};
84#ifdef __cpp_lib_ranges_to_container
85 auto mm {mm_abbrv_table_ | std::views::filter(filt)
86 | std::views::elements<0> | std::ranges::to<std::vector>()};
87#else
88 std::vector<rsj::MidiMessageId> mm;
89 std::ranges::copy(mm_abbrv_table_ | std::views::filter(filt) | std::views::elements<0>,
90 std::back_inserter(mm));
91#endif
92 return mm;
93 }
94 catch (const std::exception& e) {
95 rsj::ExceptionResponse(e, std::source_location::current());
96 throw;
97 }
98}

◆ GetRowForMessage()

int Profile::GetRowForMessage ( rsj::MidiMessageId message) const
inlinenodiscard
104{
105 auto guard {std::shared_lock {mutex_}};
106 return gsl::narrow_cast<int>(std::ranges::find(mm_abbrv_table_, message, &mm_abbrv_lmnt_t::first)
107 - mm_abbrv_table_.begin());
108}

Referenced by MainContentComponent::MidiCmdCallback().

◆ InsertOrAssign() [1/2]

void Profile::InsertOrAssign ( const std::string & command,
rsj::MidiMessageId message )
inline
111{
112 auto guard {std::unique_lock {mutex_}};
113 InsertOrAssignI(command, message);
114}
void InsertOrAssignI(const std::string &command, const rsj::MidiMessageId &message)
Definition Profile.cpp:100

References InsertOrAssignI().

◆ InsertOrAssign() [2/2]

void Profile::InsertOrAssign ( size_t command,
rsj::MidiMessageId message )
inline
117{
118 if (command < command_set_.CommandAbbrevSize()) {
119 auto guard {std::unique_lock {mutex_}};
120 InsertOrAssignI(command_set_.CommandAbbrevAt(command), message);
121 }
122}

References command_set_, CommandSet::CommandAbbrevSize(), and InsertOrAssignI().

Referenced by CommandMenu::ProcessUserSelection().

◆ InsertOrAssignI()

void Profile::InsertOrAssignI ( const std::string & command,
const rsj::MidiMessageId & message )
private
101{
102 try {
103 const auto found {std::ranges::find(mm_abbrv_table_, message, &mm_abbrv_lmnt_t::first)};
104 if (found != mm_abbrv_table_.end()) { found->second = command; }
105 else {
106 mm_abbrv_table_.emplace_back(message, command);
107 }
108 SortI();
109 profile_unsaved_ = true;
110 }
111 catch (const std::exception& e) {
112 rsj::ExceptionResponse(e, std::source_location::current());
113 throw;
114 }
115}

References profile_unsaved_, and SortI().

Referenced by InsertOrAssign(), and InsertOrAssign().

◆ InsertUnassigned()

void Profile::InsertUnassigned ( rsj::MidiMessageId message)
118{
119 try {
120 auto guard {std::unique_lock {mutex_}};
121 if (!MessageExistsInMapI(message)) {
122 mm_abbrv_table_.emplace_back(message, CommandSet::kUnassigned);
123 SortI();
124 profile_unsaved_ = true;
125 }
126 }
127 catch (const std::exception& e) {
128 rsj::ExceptionResponse(e, std::source_location::current());
129 throw;
130 }
131}
bool MessageExistsInMapI(rsj::MidiMessageId message) const
Definition Profile.h:130

References MessageExistsInMapI(), profile_unsaved_, and SortI().

Referenced by MainContentComponent::MidiCmdCallback().

◆ MessageExistsInMap()

bool Profile::MessageExistsInMap ( rsj::MidiMessageId message) const
inlinenodiscard
125{
126 auto guard {std::shared_lock {mutex_}};
127 return MessageExistsInMapI(message);
128}

References MessageExistsInMapI().

Referenced by LrIpcOut::MidiCmdCallback(), and ProfileManager::MidiCmdCallback().

◆ MessageExistsInMapI()

bool Profile::MessageExistsInMapI ( rsj::MidiMessageId message) const
inlinenodiscardprivate
131{
132#ifdef __cpp_lib_ranges_contains
133 return std::ranges::contains(mm_abbrv_table_, message, &mm_abbrv_lmnt_t::first);
134#else
135 return std::ranges::any_of(mm_abbrv_table_, [message](auto& p) { return p.first == message; });
136#endif
137}

Referenced by InsertUnassigned(), and MessageExistsInMap().

◆ ProfileUnsaved()

bool Profile::ProfileUnsaved ( ) const
inlinenodiscard
140{ /* technically, should check std::is_permutation, but much quicker to check if saved==current,
141 accept occasional false positive for much better program responsiveness */
142 auto abbrv_lock {std::scoped_lock {saved_table_mtx_}};
143 auto guard {std::shared_lock {mutex_}};
145}
std::mutex saved_table_mtx_
Definition Profile.h:71

Referenced by MainContentComponent::LoadClicked().

◆ RemoveAllRows()

void Profile::RemoveAllRows ( )
134{
135 try {
136 auto guard {std::unique_lock {mutex_}};
137 mm_abbrv_table_.clear();
138 /*avoid repeated allocations when building*/
139 mm_abbrv_table_.reserve(128);
140 /* no reason for profile_unsaved_ here. nothing to save */
141 profile_unsaved_ = false;
142 }
143 catch (const std::exception& e) {
144 rsj::ExceptionResponse(e, std::source_location::current());
145 throw;
146 }
147}

References profile_unsaved_.

Referenced by FromXml(), and MainContentComponent::RemoveAllRowsClicked().

◆ RemoveMessage()

void Profile::RemoveMessage ( rsj::MidiMessageId message)
150{
151 try {
152 auto guard {std::unique_lock {mutex_}};
153 const auto found {std::ranges::find(mm_abbrv_table_, message, &mm_abbrv_lmnt_t::first)};
154 if (found != mm_abbrv_table_.end()) [[likely]] {
155 mm_abbrv_table_.erase(found);
156 profile_unsaved_ = true;
157 }
158 else {
159 rsj::Log(fmt::format("Error in Profile::RemoveMessage. Message not found. Message is: "
160 "channel {} control number {} type {}.",
161 message.channel, message.control_number, message.msg_id_type),
162 std::source_location::current());
163 }
164 }
165 catch (const std::exception& e) {
166 rsj::ExceptionResponse(e, std::source_location::current());
167 throw;
168 }
169}
void Log(const juce::String &info, const std::source_location &location=std::source_location::current()) noexcept
Definition Misc.cpp:112
MessageType msg_id_type
Definition MidiUtilities.h:145
int channel
Definition MidiUtilities.h:143
int control_number
Definition MidiUtilities.h:144

References profile_unsaved_.

◆ RemoveRow()

void Profile::RemoveRow ( size_t row)
172{
173 try {
174 auto guard {std::unique_lock {mutex_}};
175 if (row >= mm_abbrv_table_.size()) [[unlikely]] {
176 rsj::Log(fmt::format("Error in Profile::RemoveRow. Row {} out of range.", row),
177 std::source_location::current());
178 return;
179 }
180 mm_abbrv_table_.erase(mm_abbrv_table_.begin() + gsl::narrow_cast<std::ptrdiff_t>(row));
181 profile_unsaved_ = true;
182 }
183 catch (const std::exception& e) {
184 rsj::ExceptionResponse(e, std::source_location::current());
185 throw;
186 }
187}

References profile_unsaved_.

Referenced by CommandTableModel::RemoveRow().

◆ RemoveUnassignedMessages()

void Profile::RemoveUnassignedMessages ( )
190{
191 try {
192 auto guard {std::unique_lock {mutex_}};
193 if (std::erase_if(mm_abbrv_table_,
194 [](const auto& p) { return p.second == CommandSet::kUnassigned; })) {
195 profile_unsaved_ = true;
196 }
197 }
198 catch (const std::exception& e) {
199 rsj::ExceptionResponse(e, std::source_location::current());
200 throw;
201 }
202}

References profile_unsaved_.

Referenced by MainContentComponent::RemoveUnassignedClicked().

◆ Resort()

void Profile::Resort ( std::pair< int, bool > new_order)
205{
206 try {
207 auto guard {std::unique_lock {mutex_}};
208 current_sort_ = new_order;
209 SortI();
210 }
211 catch (const std::exception& e) {
212 rsj::ExceptionResponse(e, std::source_location::current());
213 throw;
214 }
215}
std::pair< int, bool > current_sort_
Definition Profile.h:73

References SortI().

Referenced by CommandTableModel::sortOrderChanged().

◆ Size()

size_t Profile::Size ( ) const
inlinenodiscard
148{
149 auto guard {std::shared_lock {mutex_}};
150 return mm_abbrv_table_.size();
151}

Referenced by CommandTableModel::CreateNewCommandMenu(), CommandTableModel::getNumRows(), and CommandTableModel::paintCell().

◆ SortI()

void Profile::SortI ( )
private
218{
219 try {
220 const auto CommandNumber {
221 [this](const auto& a) { return command_set_.CommandTextIndex(a.second); }};
222 if (current_sort_.first == 1) {
223 if (current_sort_.second) { std::ranges::sort(mm_abbrv_table_); }
224 else {
225 std::ranges::sort(mm_abbrv_table_ | std::views::reverse);
226 }
227 }
228 else if (current_sort_.second) {
229 std::ranges::stable_sort(mm_abbrv_table_, {}, CommandNumber);
230 }
231 else {
232 std::ranges::stable_sort(mm_abbrv_table_ | std::views::reverse, {}, CommandNumber);
233 }
234 }
235 catch (const std::exception& e) {
236 rsj::ExceptionResponse(e, std::source_location::current());
237 throw;
238 }
239}

References command_set_, and CommandSet::CommandTextIndex().

Referenced by FromXml(), InsertOrAssignI(), InsertUnassigned(), and Resort().

◆ ToXmlFile()

void Profile::ToXmlFile ( const juce::File & file)
242{
243 try { /* except for saved_mm_abbrv_table_ and profile_unsaved_, doesn't alter anything, so to
244 allow for better responsiveness in slow serialization and file write, use shared_lock +
245 mtx solely guarding the two members that are overwritten */
246 auto abbrv_lock {std::scoped_lock {saved_table_mtx_}};
247 auto guard {std::shared_lock {mutex_}};
248 /* don't bother if map is empty */
249 if (!mm_abbrv_table_.empty()) {
250 /* save the contents of the command map to an xml file */
251 juce::XmlElement root {"settings"};
252 for (const auto& [msg_id, cmd_str] : mm_abbrv_table_) {
253 auto setting {std::make_unique<juce::XmlElement>("setting")};
254 setting->setAttribute("channel", msg_id.channel);
255 switch (msg_id.msg_id_type) {
257 setting->setAttribute("note", msg_id.control_number);
258 break;
260 setting->setAttribute("controller", msg_id.control_number);
261 break;
263 setting->setAttribute("pitchbend", 0);
264 break;
270 /* can't handle other types */
271 continue;
272 }
273 setting->setAttribute("command_string", cmd_str);
274 root.prependChildElement(setting.release());
275 }
276 if (!root.writeTo(file)) {
277 /* Give feedback if file-save doesn't work */
278 const auto& p {file.getFullPathName()};
279 rsj::LogAndAlertError(juce::translate("Unable to save file. Choose a different "
280 "location and try again.")
281 + ' ' + p,
282 "Unable to save file. Choose a different location and try again. " + p,
283 std::source_location::current());
284 }
285 else {
286 /*could use shared_mutex above if it were upgradable to unique here*/
288 profile_unsaved_ = false;
289 }
290 }
291 }
292 catch (const std::exception& e) {
293 rsj::ExceptionResponse(e, std::source_location::current());
294 throw;
295 }
296}
@ kChanPressure
Definition MidiUtilities.h:47
@ kSystem
Definition MidiUtilities.h:49
@ kKeyPressure
Definition MidiUtilities.h:44
@ kNoteOff
Definition MidiUtilities.h:42
@ kPgmChange
Definition MidiUtilities.h:46
void LogAndAlertError(const juce::String &error_text, const std::source_location &location=std::source_location::current()) noexcept
Definition Misc.cpp:141

References profile_unsaved_.

Member Data Documentation

◆ command_set_

const CommandSet& Profile::command_set_
private

Referenced by Profile(), InsertOrAssign(), and SortI().

◆ current_sort_

std::pair<int, bool> Profile::current_sort_ {2, true}
private
73{2, true};

◆ mm_abbrv_table_

std::vector<mm_abbrv_lmnt_t> Profile::mm_abbrv_table_ {}
private
74{};

◆ mutex_

std::shared_mutex Profile::mutex_
mutableprivate

◆ profile_unsaved_

bool Profile::profile_unsaved_ {false}
private

◆ saved_mm_abbrv_table_

std::vector<mm_abbrv_lmnt_t> Profile::saved_mm_abbrv_table_ {}
private
75{};

◆ saved_table_mtx_

std::mutex Profile::saved_table_mtx_
mutableprivate

The documentation for this class was generated from the following files:
  • C:/Users/rsjaf/source/repos/MIDI2LR/src/application/Profile.h
  • C:/Users/rsjaf/source/repos/MIDI2LR/src/application/Profile.cpp