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
LrIpcOut Class Reference

#include <LR_IPC_Out.h>

Public Member Functions

 LrIpcOut (const CommandSet &command_set, ControlsModel &c_model, const Profile &profile, const MidiSender &midi_sender, MidiReceiver &midi_receiver, asio::io_context &io_context)
 
 LrIpcOut (const LrIpcOut &other)=delete
 
 LrIpcOut (LrIpcOut &&other)=delete
 
 ~LrIpcOut ()=default
 
template<class T >
void AddCallback (_In_ T *const object, _In_ void(T::*const mf)(bool, bool))
 
LrIpcOutoperator= (const LrIpcOut &other)=delete
 
LrIpcOutoperator= (LrIpcOut &&other)=delete
 
void SendCommand (const std::string &command) const
 
void SendCommand (std::string &&command) const
 
void SendingRestart ()
 
void SendingStop ()
 
void Start ()
 
void Stop ()
 

Private Types

using RepeatCmdIterator = const std::unordered_map< std::string, std::pair< std::string, std::string > >::const_iterator
 

Private Member Functions

void Connect (std::shared_ptr< LrIpcOutShared > lr_ipc_out_shared)
 
void ConnectionMade ()
 
void MidiCmdCallback (rsj::MidiMessage mm)
 
void ProcessChange (const RepeatCmdIterator &repeats, const rsj::MidiMessage &mm) const
 
void ProcessMessage (const rsj::MidiMessageId &message, const rsj::MidiMessage &mm)
 
void ProcessNonRepeatedCommand (const std::string &command_to_send, const rsj::MidiMessage &mm) const
 
void ProcessRepeatedCommand (const RepeatCmdIterator &repeats, const rsj::MidiMessage &mm, const rsj::MidiMessageId &message)
 
void SetRecenter (rsj::MidiMessageId mm)
 
bool ShouldSetRecenter (const rsj::MidiMessage &mm) const
 

Private Attributes

std::mutex callback_mtx_
 
std::vector< std::function< void(bool, bool)> > callbacks_ {}
 
bool connected_ {false}
 
ControlsModelcontrols_model_
 
std::shared_ptr< LrIpcOutSharedlr_ipc_out_shared_
 
const MidiSendermidi_sender_
 
const Profileprofile_
 
asio::steady_timer recenter_timer_
 
const std::unordered_map< std::string, std::pair< std::string, std::string > > & repeat_cmd_
 
bool sending_stopped_ {false}
 
std::atomic< bool > thread_should_exit_ {false}
 
const std::vector< std::string > & wrap_
 

Member Typedef Documentation

◆ RepeatCmdIterator

using LrIpcOut::RepeatCmdIterator = const std::unordered_map<std::string, std::pair<std::string, std::string> >::const_iterator
private

Constructor & Destructor Documentation

◆ LrIpcOut() [1/3]

LrIpcOut::LrIpcOut ( const CommandSet command_set,
ControlsModel c_model,
const Profile profile,
const MidiSender midi_sender,
MidiReceiver midi_receiver,
asio::io_context &  io_context 
)
60 : recenter_timer_ {asio::make_strand(io_context)}, midi_sender_ {midi_sender},
61 profile_ {profile}, repeat_cmd_ {command_set.GetRepeats()}, wrap_ {command_set.GetWraps()},
62 controls_model_ {c_model}, lr_ipc_out_shared_ {std::make_shared<LrIpcOutShared>(io_context)}
63{
64 midi_receiver.AddCallback(this, &LrIpcOut::MidiCmdCallback);
65}
const auto & GetRepeats() const noexcept
Definition CommandSet.h:60
const auto & GetWraps() const noexcept
Definition CommandSet.h:62
asio::steady_timer recenter_timer_
Definition LR_IPC_Out.h:82
std::shared_ptr< LrIpcOutShared > lr_ipc_out_shared_
Definition LR_IPC_Out.h:92
void MidiCmdCallback(rsj::MidiMessage mm)
Definition LR_IPC_Out.cpp:187
ControlsModel & controls_model_
Definition LR_IPC_Out.h:89
const MidiSender & midi_sender_
Definition LR_IPC_Out.h:85
const Profile & profile_
Definition LR_IPC_Out.h:86
const std::unordered_map< std::string, std::pair< std::string, std::string > > & repeat_cmd_
Definition LR_IPC_Out.h:87
const std::vector< std::string > & wrap_
Definition LR_IPC_Out.h:88
void AddCallback(_In_ T *const object, _In_ void(T::*const mf)(rsj::MidiMessage))
Definition MIDIReceiver.h:49

◆ ~LrIpcOut()

LrIpcOut::~LrIpcOut ( )
default

◆ LrIpcOut() [2/3]

LrIpcOut::LrIpcOut ( const LrIpcOut other)
delete

◆ LrIpcOut() [3/3]

LrIpcOut::LrIpcOut ( LrIpcOut &&  other)
delete

Member Function Documentation

◆ AddCallback()

template<class T >
void LrIpcOut::AddCallback ( _In_ T *const  object,
_In_ void(T::*)(bool, bool)  mf 
)
inline
52 {
53 if (object && mf) {
54 std::scoped_lock lk(callback_mtx_);
55 callbacks_.emplace_back(std::bind_front(mf, object));
56 }
57 }
std::mutex callback_mtx_
Definition LR_IPC_Out.h:90
std::vector< std::function< void(bool, bool)> > callbacks_
Definition LR_IPC_Out.h:96

◆ Connect()

void LrIpcOut::Connect ( std::shared_ptr< LrIpcOutShared lr_ipc_out_shared)
private
144{
145 try {
146 lr_ipc_out_shared->socket_.async_connect(
147 asio::ip::tcp::endpoint(asio::ip::address_v4::loopback(), kLrOutPort),
148 [this, lr_ipc_out_shared](const asio::error_code& error) mutable {
149 if (!error) {
151 LrIpcOutShared::SendOut(std::move(lr_ipc_out_shared));
152 }
153 else {
154 rsj::Log(fmt::format(FMT_STRING("LR_IPC_Out did not connect. {}."), error.message()),
155 std::source_location::current());
156 asio::error_code ec2;
157 std::ignore = lr_ipc_out_shared->socket_.close(ec2);
158 if (ec2) {
159 rsj::Log(fmt::format(FMT_STRING("LR_IPC_Out socket close error {}."), ec2.message()),
160 std::source_location::current());
161 }
162 }
163 });
164 }
165 catch (const std::exception& e) {
166 rsj::ExceptionResponse(e, std::source_location::current());
167 throw;
168 }
169}
void ConnectionMade()
Definition LR_IPC_Out.cpp:171
static void SendOut(std::shared_ptr< LrIpcOutShared > lr_ipc_out_shared)
Definition LR_IPC_Out.cpp:253
void ExceptionResponse(gsl::czstring id, gsl::czstring fu, const std::exception &e) noexcept
void Log(const juce::String &info, const std::source_location &location=std::source_location::current()) noexcept
Definition Misc.cpp:113

◆ ConnectionMade()

void LrIpcOut::ConnectionMade ( )
private
172{
173 try {
174 {
175 std::scoped_lock lk(callback_mtx_);
176 connected_ = true;
177 for (const auto& cb : callbacks_) { cb(true, sending_stopped_); }
178 }
179 rsj::Log("Socket connected in LR_IPC_Out.", std::source_location::current());
180 }
181 catch (const std::exception& e) {
182 rsj::ExceptionResponse(e, std::source_location::current());
183 throw;
184 }
185}
bool sending_stopped_
Definition LR_IPC_Out.h:84
bool connected_
Definition LR_IPC_Out.h:83

◆ MidiCmdCallback()

void LrIpcOut::MidiCmdCallback ( rsj::MidiMessage  mm)
private
188{
189 try {
190 const rsj::MidiMessageId message {mm};
191 if (profile_.MessageExistsInMap(message)) { ProcessMessage(message, mm); }
192 }
193 catch (const std::exception& e) {
194 rsj::ExceptionResponse(e, std::source_location::current());
195 throw;
196 }
197}
void ProcessMessage(const rsj::MidiMessageId &message, const rsj::MidiMessage &mm)
Definition LR_IPC_Out.cpp:199
bool MessageExistsInMap(rsj::MidiMessageId message) const
Definition Profile.h:124
Definition MidiUtilities.h:145

◆ operator=() [1/2]

LrIpcOut & LrIpcOut::operator= ( const LrIpcOut other)
delete

◆ operator=() [2/2]

LrIpcOut & LrIpcOut::operator= ( LrIpcOut &&  other)
delete

◆ ProcessChange()

void LrIpcOut::ProcessChange ( const RepeatCmdIterator repeats,
const rsj::MidiMessage mm 
) const
private
232{
233 if (const auto change {controls_model_.MeasureChange(mm)}; change > 0) {
234 SendCommand(repeats->second.first); /* turned clockwise */
235 }
236 else if (change < 0) {
237 SendCommand(repeats->second.second); /* turned counterclockwise */
238 }
239}
int MeasureChange(rsj::MidiMessage mm)
Definition ControlsModel.h:231
void SendCommand(std::string &&command) const
Definition LR_IPC_Out.cpp:67

◆ ProcessMessage()

void LrIpcOut::ProcessMessage ( const rsj::MidiMessageId message,
const rsj::MidiMessage mm 
)
private
200{
201 const auto command_to_send {profile_.GetCommandForMessage(message)};
202 if (rollbear::none_of("PrevPro", "NextPro", CommandSet::kUnassigned) == command_to_send) {
203 if (const auto repeats {repeat_cmd_.find(command_to_send)}; repeats == repeat_cmd_.end()) {
204 ProcessNonRepeatedCommand(command_to_send, mm);
205 }
206 else {
207 ProcessRepeatedCommand(repeats, mm, message);
208 }
209 }
210}
static const std::string kUnassigned
Definition CommandSet.h:70
void ProcessNonRepeatedCommand(const std::string &command_to_send, const rsj::MidiMessage &mm) const
Definition LR_IPC_Out.cpp:241
void ProcessRepeatedCommand(const RepeatCmdIterator &repeats, const rsj::MidiMessage &mm, const rsj::MidiMessageId &message)
Definition LR_IPC_Out.cpp:212
const std::string & GetCommandForMessage(rsj::MidiMessageId message) const
Definition Profile.h:89

◆ ProcessNonRepeatedCommand()

void LrIpcOut::ProcessNonRepeatedCommand ( const std::string &  command_to_send,
const rsj::MidiMessage mm 
) const
private
243{
244#ifdef __cpp_lib_ranges_contains
245 const auto wrap {std::ranges::contains(wrap_, command_to_send)};
246#else
247 const auto wrap {std::ranges::find(wrap_, command_to_send) != wrap_.end()};
248#endif
249 const auto computed_value {controls_model_.ControllerToPlugin(mm, wrap)};
250 SendCommand(fmt::format(FMT_STRING("{} {}\n"), command_to_send, computed_value));
251}
double ControllerToPlugin(rsj::MidiMessage mm, bool wrap)
Definition ControlsModel.h:225

◆ ProcessRepeatedCommand()

void LrIpcOut::ProcessRepeatedCommand ( const RepeatCmdIterator repeats,
const rsj::MidiMessage mm,
const rsj::MidiMessageId message 
)
private
214{
215 constinit static TimePoint next_response {};
216 if (const auto now {Clock::now()}; next_response < now) {
217 next_response = now + kDelay;
218 if (ShouldSetRecenter(mm)) { SetRecenter(message); }
219 ProcessChange(repeats, mm);
220 }
221}
void ProcessChange(const RepeatCmdIterator &repeats, const rsj::MidiMessage &mm) const
Definition LR_IPC_Out.cpp:231
bool ShouldSetRecenter(const rsj::MidiMessage &mm) const
Definition LR_IPC_Out.cpp:223
void SetRecenter(rsj::MidiMessageId mm)
Definition LR_IPC_Out.cpp:276

◆ SendCommand() [1/2]

void LrIpcOut::SendCommand ( const std::string &  command) const
73{
74 if (!sending_stopped_) { lr_ipc_out_shared_->command_.push(command); }
75}

◆ SendCommand() [2/2]

void LrIpcOut::SendCommand ( std::string &&  command) const
68{
69 if (!sending_stopped_) { lr_ipc_out_shared_->command_.push(std::move(command)); }
70}

◆ SendingRestart()

void LrIpcOut::SendingRestart ( )
78{
79 try {
80 {
81 std::scoped_lock lk(callback_mtx_);
82 sending_stopped_ = false;
83 for (const auto& cb : callbacks_) { cb(connected_, false); }
84 }
85 SendCommand("FullRefresh 1\n"); /* synchronize controls */
86 }
87 catch (const std::exception& e) {
88 rsj::ExceptionResponse(e, std::source_location::current());
89 throw;
90 }
91}

◆ SendingStop()

void LrIpcOut::SendingStop ( )
94{
95 try {
96 std::scoped_lock lk(callback_mtx_);
97 sending_stopped_ = true;
98 for (const auto& cb : callbacks_) { cb(connected_, true); }
99 }
100 catch (const std::exception& e) {
101 rsj::ExceptionResponse(e, std::source_location::current());
102 throw;
103 }
104}

◆ SetRecenter()

void LrIpcOut::SetRecenter ( rsj::MidiMessageId  mm)
private
277{
278 /* by capturing mm by copy, don't have to worry about later calls changing it--those will just
279 * cancel and reschedule new one */
280 try {
281 recenter_timer_.expires_after(kRecenterTimer);
282 recenter_timer_.async_wait([this, mm](const asio::error_code& error) {
283 if (!error && !thread_should_exit_.load(std::memory_order_acquire)) {
284 midi_sender_.Send(mm, controls_model_.SetToCenter(mm));
285 }
286 });
287 }
288 catch (const std::exception& e) {
289 rsj::ExceptionResponse(e, std::source_location::current());
290 throw;
291 }
292}
std::atomic< bool > thread_should_exit_
Definition LR_IPC_Out.h:91

◆ ShouldSetRecenter()

bool LrIpcOut::ShouldSetRecenter ( const rsj::MidiMessage mm) const
private
224{
229}
rsj::CCmethod GetCcMethod(int channel, int controlnumber) const
Definition ControlsModel.h:244
int control_number
Definition MidiUtilities.h:126
int channel
Definition MidiUtilities.h:125
MessageType message_type_byte
Definition MidiUtilities.h:124

◆ Start()

void LrIpcOut::Start ( )
inline
void Connect(std::shared_ptr< LrIpcOutShared > lr_ipc_out_shared)
Definition LR_IPC_Out.cpp:143

◆ Stop()

void LrIpcOut::Stop ( )
107{
108 thread_should_exit_.store(true, std::memory_order_release);
109 /* clear output queue before port closed */
110 if (const auto m {lr_ipc_out_shared_->command_.clear_count_emplace(kTerminate)}) {
111 rsj::Log(fmt::format(FMT_STRING("{} left in queue in LrIpcOut destructor."), m),
112 std::source_location::current());
113 }
114 {
115 std::scoped_lock lk(callback_mtx_);
116 callbacks_.clear(); /* no more connect/disconnect notifications */
117 }
118 recenter_timer_.cancel();
119 if (auto& sock {lr_ipc_out_shared_->socket_}; sock.is_open()) {
120 asio::error_code ec;
121 /* For portable behaviour with respect to graceful closure of a connected socket, call
122 * shutdown() before closing the socket. */
123 try { /* ignore exceptions from shutdown, always close */
124 std::ignore = sock.shutdown(asio::ip::tcp::socket::shutdown_both, ec);
125 if (ec) {
126 rsj::Log(fmt::format(FMT_STRING("LR_IPC_Out socket shutdown error {}."), ec.message()),
127 std::source_location::current());
128 ec.clear();
129 }
130 }
131 catch (const std::exception& e) {
132 rsj::Log(fmt::format(FMT_STRING("Exception during socket shutdown: {}"), e.what()),
133 std::source_location::current());
134 }
135 std::ignore = sock.close(ec);
136 if (ec) {
137 rsj::Log(fmt::format(FMT_STRING("LR_IPC_Out socket close error {}."), ec.message()),
138 std::source_location::current());
139 }
140 }
141}

Member Data Documentation

◆ callback_mtx_

std::mutex LrIpcOut::callback_mtx_
mutableprivate

◆ callbacks_

std::vector<std::function<void(bool, bool)> > LrIpcOut::callbacks_ {}
private
96{};

◆ connected_

bool LrIpcOut::connected_ {false}
private
83{false};

◆ controls_model_

ControlsModel& LrIpcOut::controls_model_
private

◆ lr_ipc_out_shared_

std::shared_ptr<LrIpcOutShared> LrIpcOut::lr_ipc_out_shared_
private

◆ midi_sender_

const MidiSender& LrIpcOut::midi_sender_
private

◆ profile_

const Profile& LrIpcOut::profile_
private

◆ recenter_timer_

asio::steady_timer LrIpcOut::recenter_timer_
private

◆ repeat_cmd_

const std::unordered_map<std::string, std::pair<std::string, std::string> >& LrIpcOut::repeat_cmd_
private

◆ sending_stopped_

bool LrIpcOut::sending_stopped_ {false}
private
84{false};

◆ thread_should_exit_

std::atomic<bool> LrIpcOut::thread_should_exit_ {false}
private
91{false};

◆ wrap_

const std::vector<std::string>& LrIpcOut::wrap_
private

The documentation for this class was generated from the following files: