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

#include <LR_IPC_Out.h>

Classes

struct  RecenterInfo

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 Clock = std::chrono::steady_clock
using RepeatCmdIterator
using TimePoint = Clock::time_point

Private Member Functions

void Connect (std::shared_ptr< LrIpcOutShared > lr_ipc_out_shared)
void ConnectionMade ()
void MidiCmdCallback (rsj::MidiMessage mm)
void ProcessMessage (const rsj::MidiMessageId &message, const rsj::MidiMessage &mm)
void ProcessRepeatedCommand (const RepeatCmdIterator &repeats, const rsj::MidiMessage &mm, const rsj::MidiMessageId &message)
void SendNonRepeatedCommand (const std::string &command_to_send, const rsj::MidiMessage &mm) const
void SendRepeatedCommand (const RepeatCmdIterator &repeats, const rsj::MidiMessage &mm) const
void SetRecenter (const rsj::MidiMessageId &, RecenterInfo &)
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_
std::map< rsj::MidiMessageId, RecenterInforecenter_timers_
std::mutex recenter_timers_mtx_ {}
const std::unordered_map< std::string, std::pair< std::string, std::string > > & repeat_cmd_
std::atomic< bool > sending_stopped_ {false}
asio::strand< asio::io_context::executor_type > timer_strand_
const std::vector< std::string > & wrap_

Member Typedef Documentation

◆ Clock

using LrIpcOut::Clock = std::chrono::steady_clock
private

◆ RepeatCmdIterator

Initial value:
const std::unordered_map<std::string, std::pair<std::string, std::string>>::const_iterator

◆ TimePoint

using LrIpcOut::TimePoint = Clock::time_point
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 )
62 : timer_strand_ {asio::make_strand(io_context)}, midi_sender_ {midi_sender}, profile_ {profile},
63 repeat_cmd_ {command_set.GetRepeats()}, wrap_ {command_set.GetWraps()},
64 controls_model_ {c_model}, lr_ipc_out_shared_ {std::make_shared<LrIpcOutShared>(io_context)}
65{
66 midi_receiver.AddCallback(this, &LrIpcOut::MidiCmdCallback);
67}
const auto & GetRepeats() const noexcept
Definition CommandSet.h:60
const auto & GetWraps() const noexcept
Definition CommandSet.h:62
std::shared_ptr< LrIpcOutShared > lr_ipc_out_shared_
Definition LR_IPC_Out.h:104
void MidiCmdCallback(rsj::MidiMessage mm)
Definition LR_IPC_Out.cpp:208
ControlsModel & controls_model_
Definition LR_IPC_Out.h:99
const MidiSender & midi_sender_
Definition LR_IPC_Out.h:95
const Profile & profile_
Definition LR_IPC_Out.h:96
const std::unordered_map< std::string, std::pair< std::string, std::string > > & repeat_cmd_
Definition LR_IPC_Out.h:97
const std::vector< std::string > & wrap_
Definition LR_IPC_Out.h:98
asio::strand< asio::io_context::executor_type > timer_strand_
Definition LR_IPC_Out.h:93
void AddCallback(_In_ T *const object, _In_ void(T::*const mf)(rsj::MidiMessage))
Definition MIDIReceiver.h:49

References timer_strand_.

◆ ~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::* mf )(bool, bool) )
inline
53 {
54 if (object && mf) {
55 std::scoped_lock lk(callback_mtx_);
56 callbacks_.emplace_back(std::bind_front(mf, object));
57 }
58 }
std::mutex callback_mtx_
Definition LR_IPC_Out.h:100
std::vector< std::function< void(bool, bool)> > callbacks_
Definition LR_IPC_Out.h:108

◆ Connect()

void LrIpcOut::Connect ( std::shared_ptr< LrIpcOutShared > lr_ipc_out_shared)
private
160{
161 try {
162 lr_ipc_out_shared->socket_.async_connect(
163 asio::ip::tcp::endpoint(asio::ip::address_v4::loopback(), kLrOutPort),
164 [this, lr_ipc_out_shared](const asio::error_code& error) mutable {
165 // copy shared alive token so the handler can detect owner shutdown
166 auto alive {lr_ipc_out_shared->owner_alive_};
167 if (!alive || !alive->load(std::memory_order_acquire)) { return; }
168 if (!error) {
169 // check again just before calling member functions
170 if (alive->load(std::memory_order_acquire)) { ConnectionMade(); }
171 LrIpcOutShared::SendOut(std::move(lr_ipc_out_shared));
172 }
173 else {
174 rsj::Log(fmt::format("LR_IPC_Out did not connect. {}.", error.message()),
175 std::source_location::current());
176 asio::error_code ec2;
177 std::ignore = lr_ipc_out_shared->socket_.close(ec2);
178 if (ec2) {
179 rsj::Log(fmt::format("LR_IPC_Out socket close error {}.", ec2.message()),
180 std::source_location::current());
181 }
182 }
183 });
184 }
185 catch (const std::exception& e) {
186 rsj::ExceptionResponse(e, std::source_location::current());
187 throw;
188 }
189}
void ConnectionMade()
Definition LR_IPC_Out.cpp:191
std::shared_ptr< std::atomic< bool > > owner_alive_
Definition LR_IPC_Out.cpp:51
static void SendOut(std::shared_ptr< LrIpcOutShared > lr_ipc_out_shared)
Definition LR_IPC_Out.cpp:277
asio::ip::tcp::socket socket_
Definition LR_IPC_Out.cpp:46
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:112

Referenced by Start().

◆ ConnectionMade()

void LrIpcOut::ConnectionMade ( )
private
192{
193 try {
194 {
195 std::scoped_lock lk(callback_mtx_);
196 connected_ = true;
197 auto stopped {sending_stopped_.load(std::memory_order_acquire)};
198 for (const auto& cb : callbacks_) { cb(true, stopped); }
199 }
200 rsj::Log("Socket connected in LR_IPC_Out.", std::source_location::current());
201 }
202 catch (const std::exception& e) {
203 rsj::ExceptionResponse(e, std::source_location::current());
204 throw;
205 }
206}
std::atomic< bool > sending_stopped_
Definition LR_IPC_Out.h:101
bool connected_
Definition LR_IPC_Out.h:94

References connected_, and sending_stopped_.

◆ MidiCmdCallback()

void LrIpcOut::MidiCmdCallback ( rsj::MidiMessage mm)
private
209{
210 try {
211 const rsj::MidiMessageId message {mm};
212 if (profile_.MessageExistsInMap(message)) { ProcessMessage(message, mm); }
213 }
214 catch (const std::exception& e) {
215 rsj::ExceptionResponse(e, std::source_location::current());
216 throw;
217 }
218}
void ProcessMessage(const rsj::MidiMessageId &message, const rsj::MidiMessage &mm)
Definition LR_IPC_Out.cpp:220

References rsj::MidiMessageId::MidiMessageId(), Profile::MessageExistsInMap(), ProcessMessage(), and profile_.

◆ operator=() [1/2]

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

◆ operator=() [2/2]

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

◆ ProcessMessage()

void LrIpcOut::ProcessMessage ( const rsj::MidiMessageId & message,
const rsj::MidiMessage & mm )
private
221{
222 const auto command_to_send {profile_.GetCommandForMessage(message)};
223 if (rollbear::none_of("PrevPro", "NextPro", CommandSet::kUnassigned) == command_to_send) {
224 if (const auto repeats {repeat_cmd_.find(command_to_send)}; repeats == repeat_cmd_.end()) {
225 SendNonRepeatedCommand(command_to_send, mm);
226 }
227 else {
228 ProcessRepeatedCommand(repeats, mm, message);
229 }
230 }
231}
static const std::string kUnassigned
Definition CommandSet.h:70
void SendNonRepeatedCommand(const std::string &command_to_send, const rsj::MidiMessage &mm) const
Definition LR_IPC_Out.cpp:265
void ProcessRepeatedCommand(const RepeatCmdIterator &repeats, const rsj::MidiMessage &mm, const rsj::MidiMessageId &message)
Definition LR_IPC_Out.cpp:233

References Profile::GetCommandForMessage(), profile_, repeat_cmd_, and SendNonRepeatedCommand().

Referenced by MidiCmdCallback().

◆ ProcessRepeatedCommand()

void LrIpcOut::ProcessRepeatedCommand ( const RepeatCmdIterator & repeats,
const rsj::MidiMessage & mm,
const rsj::MidiMessageId & message )
private
235{
236 auto lock {std::unique_lock(recenter_timers_mtx_)}; // Always lock first keep iterator stable
237 auto [it, _] {recenter_timers_.try_emplace(message, timer_strand_)};
238 if (const auto now {Clock::now()}; it->second.timepoint < now) {
239 it->second.timepoint = now + kDelay;
240 if (ShouldSetRecenter(mm)) { SetRecenter(message, it->second); }
241 lock.unlock(); // Unlock before sending command
242 SendRepeatedCommand(repeats, mm);
243 }
244}
void SendRepeatedCommand(const RepeatCmdIterator &repeats, const rsj::MidiMessage &mm) const
Definition LR_IPC_Out.cpp:254
std::map< rsj::MidiMessageId, RecenterInfo > recenter_timers_
Definition LR_IPC_Out.h:102
void SetRecenter(const rsj::MidiMessageId &, RecenterInfo &)
Definition LR_IPC_Out.cpp:304
bool ShouldSetRecenter(const rsj::MidiMessage &mm) const
Definition LR_IPC_Out.cpp:246
std::mutex recenter_timers_mtx_
Definition LR_IPC_Out.h:103

References recenter_timers_, SetRecenter(), ShouldSetRecenter(), and timer_strand_.

◆ SendCommand() [1/2]

void LrIpcOut::SendCommand ( const std::string & command) const
77{
78 if (!sending_stopped_.load(std::memory_order_acquire)) {
79 lr_ipc_out_shared_->command_.push(command);
80 }
81}

References sending_stopped_.

◆ SendCommand() [2/2]

void LrIpcOut::SendCommand ( std::string && command) const
70{
71 if (!sending_stopped_.load(std::memory_order_acquire)) {
72 lr_ipc_out_shared_->command_.push(std::move(command));
73 }
74}

References sending_stopped_.

Referenced by SettingsManager::ConnectionCallback(), SendingRestart(), SettingsManager::SetPickupEnabled(), and SettingsManager::WriteDebugInfo().

◆ SendingRestart()

void LrIpcOut::SendingRestart ( )
84{
85 try {
86 {
87 std::scoped_lock lk(callback_mtx_);
88 sending_stopped_.store(false, std::memory_order_release);
89 for (const auto& cb : callbacks_) { cb(connected_, false); }
90 }
91 SendCommand("FullRefresh 1\n"); /* synchronize controls */
92 }
93 catch (const std::exception& e) {
94 rsj::ExceptionResponse(e, std::source_location::current());
95 throw;
96 }
97}
void SendCommand(std::string &&command) const
Definition LR_IPC_Out.cpp:69

References SendCommand(), and sending_stopped_.

Referenced by MainContentComponent::DisconnectClicked().

◆ SendingStop()

void LrIpcOut::SendingStop ( )
100{
101 try {
102 std::scoped_lock lk(callback_mtx_);
103 sending_stopped_.store(true, std::memory_order_release);
104 for (const auto& cb : callbacks_) { cb(connected_, true); }
105 }
106 catch (const std::exception& e) {
107 rsj::ExceptionResponse(e, std::source_location::current());
108 throw;
109 }
110}

References sending_stopped_.

Referenced by MainContentComponent::DisconnectClicked().

◆ SendNonRepeatedCommand()

void LrIpcOut::SendNonRepeatedCommand ( const std::string & command_to_send,
const rsj::MidiMessage & mm ) const
private
267{
268#ifdef __cpp_lib_ranges_contains
269 const auto wrap {std::ranges::contains(wrap_, command_to_send)};
270#else
271 const auto wrap {std::ranges::find(wrap_, command_to_send) != wrap_.end()};
272#endif
273 const auto computed_value {controls_model_.ControllerToPlugin(mm, wrap)};
274 SendCommand(fmt::format("{} {}\n", command_to_send, computed_value));
275}

References ControlsModel::ControllerToPlugin(), and controls_model_.

Referenced by ProcessMessage().

◆ SendRepeatedCommand()

void LrIpcOut::SendRepeatedCommand ( const RepeatCmdIterator & repeats,
const rsj::MidiMessage & mm ) const
private
256{
257 if (const auto change {controls_model_.MeasureChange(mm)}; change > 0) {
258 SendCommand(repeats->second.first); /* turned clockwise */
259 }
260 else if (change < 0) {
261 SendCommand(repeats->second.second); /* turned counterclockwise */
262 }
263}

References controls_model_, and ControlsModel::MeasureChange().

◆ SetRecenter()

void LrIpcOut::SetRecenter ( const rsj::MidiMessageId & mm,
RecenterInfo & info )
private
305{
306 try {
307 info.timer.expires_after(kRecenterTimer);
308 info.timer.async_wait(
309 [this, mm, alive = lr_ipc_out_shared_->owner_alive_](const asio::error_code& error) {
310 if (error) { return; } // Handle cancellation/errors
311 if (!alive || !alive->load(std::memory_order_acquire)) { return; }
312 midi_sender_.Send(mm, controls_model_.SetToCenter(mm));
313 });
314 }
315 catch (const std::exception& e) {
316 rsj::ExceptionResponse(e, std::source_location::current());
317 throw;
318 }
319}

References LrIpcOut::RecenterInfo::timer.

Referenced by ProcessRepeatedCommand().

◆ ShouldSetRecenter()

bool LrIpcOut::ShouldSetRecenter ( const rsj::MidiMessage & mm) const
nodiscardprivate
247{
249 && controls_model_.GetCcMethod(mm.channel, mm.control_number)
252}
@ kAbsolute
Definition ControlsModel.h:33
@ kCc
Definition MidiUtilities.h:45
@ kPw
Definition MidiUtilities.h:48
int control_number
Definition MidiUtilities.h:123
int channel
Definition MidiUtilities.h:122
MessageType message_type_byte
Definition MidiUtilities.h:121

References rsj::MidiMessage::channel, rsj::MidiMessage::control_number, controls_model_, ControlsModel::GetCcMethod(), rsj::kAbsolute, rsj::kCc, rsj::kPw, and rsj::MidiMessage::message_type_byte.

Referenced by ProcessRepeatedCommand().

◆ Start()

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

References Connect(), and lr_ipc_out_shared_.

◆ Stop()

void LrIpcOut::Stop ( )
113{
114 lr_ipc_out_shared_->owner_alive_->store(false, std::memory_order_release);
115 /* clear output queue before port closed */
116 if (const auto m {lr_ipc_out_shared_->command_.clear_count_emplace(kTerminate)}) {
117 rsj::Log(fmt::format("{} left in queue in LrIpcOut destructor.", m),
118 std::source_location::current());
119 }
120 {
121 std::scoped_lock lk(callback_mtx_);
122 callbacks_.clear(); /* no more connect/disconnect notifications */
123 }
124 {
125 std::scoped_lock lk2(recenter_timers_mtx_);
126 for (auto& [_, centerinfo] : recenter_timers_) {
127 asio::error_code ec;
128 centerinfo.timer.cancel(ec);
129 if (ec) {
130 rsj::Log(fmt::format("Error cancelling recenter timer: {}.", ec.message()),
131 std::source_location::current());
132 }
133 }
134 }
135 if (auto& sock {lr_ipc_out_shared_->socket_}; sock.is_open()) {
136 asio::error_code ec;
137 /* For portable behaviour with respect to graceful closure of a connected socket, call
138 * shutdown() before closing the socket. */
139 try { /* ignore exceptions from shutdown, always close */
140 std::ignore = sock.shutdown(asio::ip::tcp::socket::shutdown_both, ec);
141 if (ec) {
142 rsj::Log(fmt::format("LR_IPC_Out socket shutdown error {}.", ec.message()),
143 std::source_location::current());
144 ec.clear();
145 }
146 }
147 catch (const std::exception& e) {
148 rsj::Log(fmt::format("Exception during socket shutdown: {}", e.what()),
149 std::source_location::current());
150 }
151 std::ignore = sock.close(ec);
152 if (ec) {
153 rsj::Log(fmt::format("LR_IPC_Out socket close error {}.", ec.message()),
154 std::source_location::current());
155 }
156 }
157}

Member Data Documentation

◆ callback_mtx_

std::mutex LrIpcOut::callback_mtx_
mutableprivate

◆ callbacks_

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

◆ connected_

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

Referenced by ConnectionMade().

◆ controls_model_

ControlsModel& LrIpcOut::controls_model_
private

◆ lr_ipc_out_shared_

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

Referenced by Start().

◆ midi_sender_

const MidiSender& LrIpcOut::midi_sender_
private

◆ profile_

const Profile& LrIpcOut::profile_
private

Referenced by MidiCmdCallback(), and ProcessMessage().

◆ recenter_timers_

std::map<rsj::MidiMessageId, RecenterInfo> LrIpcOut::recenter_timers_
private

Referenced by ProcessRepeatedCommand().

◆ recenter_timers_mtx_

std::mutex LrIpcOut::recenter_timers_mtx_ {}
private
103{};

◆ repeat_cmd_

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

Referenced by ProcessMessage().

◆ sending_stopped_

std::atomic<bool> LrIpcOut::sending_stopped_ {false}
private

◆ timer_strand_

asio::strand<asio::io_context::executor_type> LrIpcOut::timer_strand_
private

Referenced by LrIpcOut(), and ProcessRepeatedCommand().

◆ wrap_

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

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