MIDI2LR 6.1.0.0
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:53
const auto & GetWraps() const noexcept
Definition CommandSet.h:55
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:182
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:93

◆ Connect()

void LrIpcOut::Connect ( std::shared_ptr< LrIpcOutShared lr_ipc_out_shared)
private
140{
141 try {
142 lr_ipc_out_shared->socket_.async_connect(
143 asio::ip::tcp::endpoint(asio::ip::address_v4::loopback(), kLrOutPort),
144 [this, lr_ipc_out_shared](const asio::error_code& error) mutable {
145 if (!error) {
147 LrIpcOutShared::SendOut(std::move(lr_ipc_out_shared));
148 }
149 else {
150 rsj::Log(fmt::format(FMT_STRING("LR_IPC_Out did not connect. {}."), error.message()));
151 asio::error_code ec2;
152 std::ignore = lr_ipc_out_shared->socket_.close(ec2);
153 if (ec2) {
154 rsj::Log(fmt::format(FMT_STRING("LR_IPC_Out socket close error {}."),
155 ec2.message()));
156 }
157 }
158 });
159 }
160 catch (const std::exception& e) {
162 throw;
163 }
164}
void ConnectionMade()
Definition LR_IPC_Out.cpp:166
static void SendOut(std::shared_ptr< LrIpcOutShared > lr_ipc_out_shared)
Definition LR_IPC_Out.cpp:248
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:131

◆ ConnectionMade()

void LrIpcOut::ConnectionMade ( )
private
167{
168 try {
169 {
170 std::scoped_lock lk(callback_mtx_);
171 connected_ = true;
172 for (const auto& cb : callbacks_) { cb(true, sending_stopped_); }
173 }
174 rsj::Log("Socket connected in LR_IPC_Out.");
175 }
176 catch (const std::exception& e) {
178 throw;
179 }
180}
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
183{
184 try {
185 const rsj::MidiMessageId message {mm};
186 if (profile_.MessageExistsInMap(message)) { ProcessMessage(message, mm); }
187 }
188 catch (const std::exception& e) {
190 throw;
191 }
192}
void ProcessMessage(const rsj::MidiMessageId &message, const rsj::MidiMessage &mm)
Definition LR_IPC_Out.cpp:194
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
227{
228 if (const auto change {controls_model_.MeasureChange(mm)}; change > 0) {
229 SendCommand(repeats->second.first); /* turned clockwise */
230 }
231 else if (change < 0) {
232 SendCommand(repeats->second.second); /* turned counterclockwise */
233 }
234}
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
195{
196 const auto command_to_send {profile_.GetCommandForMessage(message)};
197 if (rollbear::none_of("PrevPro", "NextPro", CommandSet::kUnassigned) == command_to_send) {
198 if (const auto repeats {repeat_cmd_.find(command_to_send)}; repeats == repeat_cmd_.end()) {
199 ProcessNonRepeatedCommand(command_to_send, mm);
200 }
201 else {
202 ProcessRepeatedCommand(repeats, mm, message);
203 }
204 }
205}
static const std::string kUnassigned
Definition CommandSet.h:63
void ProcessNonRepeatedCommand(const std::string &command_to_send, const rsj::MidiMessage &mm) const
Definition LR_IPC_Out.cpp:236
void ProcessRepeatedCommand(const RepeatCmdIterator &repeats, const rsj::MidiMessage &mm, const rsj::MidiMessageId &message)
Definition LR_IPC_Out.cpp:207
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
238{
239#ifdef __cpp_lib_ranges_contains
240 const auto wrap {std::ranges::contains(wrap_, command_to_send)};
241#else
242 const auto wrap {std::ranges::find(wrap_, command_to_send) != wrap_.end()};
243#endif
244 const auto computed_value {controls_model_.ControllerToPlugin(mm, wrap)};
245 SendCommand(fmt::format(FMT_STRING("{} {}\n"), command_to_send, computed_value));
246}
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
209{
210 static TimePoint next_response {};
211 if (const auto now {Clock::now()}; next_response < now) {
212 next_response = now + kDelay;
213 if (ShouldSetRecenter(mm)) { SetRecenter(message); }
214 ProcessChange(repeats, mm);
215 }
216}
void ProcessChange(const RepeatCmdIterator &repeats, const rsj::MidiMessage &mm) const
Definition LR_IPC_Out.cpp:226
bool ShouldSetRecenter(const rsj::MidiMessage &mm) const
Definition LR_IPC_Out.cpp:218
void SetRecenter(rsj::MidiMessageId mm)
Definition LR_IPC_Out.cpp:270

◆ 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) {
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) {
102 throw;
103 }
104}

◆ SetRecenter()

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

◆ ShouldSetRecenter()

bool LrIpcOut::ShouldSetRecenter ( const rsj::MidiMessage mm) const
private
219{
224}
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:139

◆ 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 }
113 {
114 std::scoped_lock lk(callback_mtx_);
115 callbacks_.clear(); /* no more connect/disconnect notifications */
116 }
117 recenter_timer_.cancel();
118 if (auto& sock {lr_ipc_out_shared_->socket_}; sock.is_open()) {
119 asio::error_code ec;
120 /* For portable behaviour with respect to graceful closure of a connected socket, call
121 * shutdown() before closing the socket. */
122 try { /* ignore exceptions from shutdown, always close */
123 std::ignore = sock.shutdown(asio::ip::tcp::socket::shutdown_both, ec);
124 if (ec) {
125 rsj::Log(fmt::format(FMT_STRING("LR_IPC_Out socket shutdown error {}."), ec.message()));
126 ec.clear();
127 }
128 }
129 catch (const std::exception& e) {
130 rsj::Log(fmt::format(FMT_STRING("Exception during socket shutdown: {}"), e.what()));
131 }
132 std::ignore = sock.close(ec);
133 if (ec) {
134 rsj::Log(fmt::format(FMT_STRING("LR_IPC_Out socket close error {}."), ec.message()));
135 }
136 }
137}

Member Data Documentation

◆ callback_mtx_

std::mutex LrIpcOut::callback_mtx_
mutableprivate

◆ callbacks_

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

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