MIDI2LR 6.2.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

Member Function Documentation

◆ CommandHasAssociatedMessage()

bool Profile::CommandHasAssociatedMessage ( const std::string &  command) const
inline
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

◆ FromXml()

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

◆ GetCommandForMessage()

const std::string & Profile::GetCommandForMessage ( rsj::MidiMessageId  message) const
inline
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

◆ GetMessageForNumber()

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

◆ GetMessagesForCommand()

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

◆ GetRowForMessage()

int Profile::GetRowForMessage ( rsj::MidiMessageId  message) const
inline
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}

◆ 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:99

◆ 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_}};
121 }
122}
auto CommandAbbrevSize() const noexcept
Definition CommandSet.h:50
const auto & CommandAbbrevAt(size_t index) const
Definition CommandSet.h:45

◆ InsertOrAssignI()

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

◆ InsertUnassigned()

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

◆ MessageExistsInMap()

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

◆ MessageExistsInMapI()

bool Profile::MessageExistsInMapI ( rsj::MidiMessageId  message) const
inlineprivate
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}

◆ ProfileUnsaved()

bool Profile::ProfileUnsaved ( ) const
inline
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

◆ RemoveAllRows()

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

◆ RemoveMessage()

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

◆ RemoveRow()

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

◆ RemoveUnassignedMessages()

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

◆ Resort()

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

◆ Size()

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

◆ SortI()

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

◆ ToXmlFile()

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

Member Data Documentation

◆ command_set_

const CommandSet& Profile::command_set_
private

◆ 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
66{false};

◆ 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: