From a7b6ddce90db35d06c91a57b5cc3aa39f721e553 Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Wed, 4 Mar 2015 22:48:04 +0100 Subject: [PATCH] [DEV] add muxer interface --- data/hardwareLinux.json | 28 +++- data/virtual.json | 6 + lutin_river.py | 1 + river/Interface.cpp | 14 +- river/Interface.h | 2 + river/io/Manager.cpp | 6 + river/io/Node.cpp | 3 +- river/io/NodeMuxer.cpp | 314 ++++++++++++++++++++++++++++++++++++++++ river/io/NodeMuxer.h | 66 +++++++++ test/testEchoDelay.h | 4 +- test/testMuxer.h | 51 +++++++ 11 files changed, 488 insertions(+), 7 deletions(-) create mode 100644 river/io/NodeMuxer.cpp create mode 100644 river/io/NodeMuxer.h diff --git a/data/hardwareLinux.json b/data/hardwareLinux.json index efa6624..caa3299 100644 --- a/data/hardwareLinux.json +++ b/data/hardwareLinux.json @@ -103,5 +103,31 @@ algo-mode:"cutter", feedback-delay:10000, # in nanosecond mux-demux-type:"int16", - } + }, + # virtual Nodes : + microphone-muxed:{ + io:"muxer", + # connect in input mode + map-on-input-1:{ + # generic virtual definition + io:"input", + map-on:"microphone", + resampling-type:"speexdsp", + resampling-option:"quality=10" + }, + # connect in feedback mode + map-on-input-2:{ + io:"feedback", + map-on:"speaker", + resampling-type:"speexdsp", + resampling-option:"quality=10", + }, + #classical format configuration: + frequency:48000, + channel-map:[ + "front-left", "front-right", "rear-left", "rear-right" + ], + type:"int16", + mux-demux-type:"int16", + }, } \ No newline at end of file diff --git a/data/virtual.json b/data/virtual.json index 0692b61..eba6eee 100644 --- a/data/virtual.json +++ b/data/virtual.json @@ -29,4 +29,10 @@ resampling-type:"speexdsp", resampling-option:"quality=10" }, + microphone-muxed:{ + io:"input", + map-on:"microphone-muxed", + resampling-type:"speexdsp", + resampling-option:"quality=10" + }, } \ No newline at end of file diff --git a/lutin_river.py b/lutin_river.py index 7527cf6..549a0f7 100644 --- a/lutin_river.py +++ b/lutin_river.py @@ -20,6 +20,7 @@ def create(target): 'river/io/NodeAirTAudio.cpp', 'river/io/NodePortAudio.cpp', 'river/io/NodeAEC.cpp', + 'river/io/NodeMuxer.cpp', 'river/io/Manager.cpp' ]) myModule.add_optionnal_module_depend('airtaudio', "__AIRTAUDIO_INFERFACE__") diff --git a/river/Interface.cpp b/river/Interface.cpp index 82c32e3..19fc97f 100644 --- a/river/Interface.cpp +++ b/river/Interface.cpp @@ -31,6 +31,7 @@ bool river::Interface::init(const std::string& _name, audio::format _format, const std11::shared_ptr& _node, const std11::shared_ptr& _config) { + std::vector map(_map); m_name = _name; m_node = _node; m_volume = 0.0f; @@ -46,6 +47,13 @@ bool river::Interface::init(const std::string& _name, } // register interface to be notify from the volume change. m_node->registerAsRemote(shared_from_this()); + + if (map.size() == 0) { + RIVER_INFO("Select auto map system ..."); + map = m_node->getInterfaceFormat().getMap(); + RIVER_INFO(" ==> " << map); + } + // Create convertion interface if ( m_node->isInput() == true && m_mode == river::modeInterface_input) { @@ -61,10 +69,10 @@ bool river::Interface::init(const std::string& _name, RIVER_INFO(" add volume for node"); algo->addVolumeStage(tmpVolume); } - m_process.setOutputConfig(drain::IOFormatInterface(_map, _format, _freq)); + m_process.setOutputConfig(drain::IOFormatInterface(map, _format, _freq)); } else if ( m_node->isOutput() == true && m_mode == river::modeInterface_output) { - m_process.setInputConfig(drain::IOFormatInterface(_map, _format, _freq)); + m_process.setInputConfig(drain::IOFormatInterface(map, _format, _freq)); // add all time the volume stage : std11::shared_ptr algo = drain::Volume::create(); //algo->setOutputFormat(m_node->getInterfaceFormat()); @@ -88,7 +96,7 @@ bool river::Interface::init(const std::string& _name, m_process.pushBack(algo); */ // note : feedback has no volume stage ... - m_process.setOutputConfig(drain::IOFormatInterface(_map, _format, _freq)); + m_process.setOutputConfig(drain::IOFormatInterface(map, _format, _freq)); } else { RIVER_ERROR("Can not link virtual interface with type : " << m_mode << " to a hardware interface " << (m_node->isInput()==true?"input":"output")); return false; diff --git a/river/Interface.h b/river/Interface.h index 9bbec40..a8f4045 100644 --- a/river/Interface.h +++ b/river/Interface.h @@ -27,6 +27,7 @@ namespace river { class Node; class NodeAirTAudio; class NodeAEC; + class NodeMuxer; } enum modeInterface { modeInterface_unknow, @@ -38,6 +39,7 @@ namespace river { friend class io::Node; friend class io::NodeAirTAudio; friend class io::NodeAEC; + friend class io::NodeMuxer; friend class Manager; protected: uint32_t m_uid; //!< unique ID for interface diff --git a/river/io/Manager.cpp b/river/io/Manager.cpp index 7bcad3d..3547bcb 100644 --- a/river/io/Manager.cpp +++ b/river/io/Manager.cpp @@ -8,6 +8,7 @@ #include #include "Node.h" #include "NodeAEC.h" +#include "NodeMuxer.h" #include "NodeAirTAudio.h" #include "NodePortAudio.h" #include @@ -156,6 +157,11 @@ std11::shared_ptr river::io::Manager::getNode(const std::string m_list.push_back(tmp); return tmp; } + if (ioType == "muxer") { + std11::shared_ptr tmp = river::io::NodeMuxer::create(_name, tmpObject); + m_list.push_back(tmp); + return tmp; + } } } RIVER_ERROR("Can not create the interface : '" << _name << "' the node is not DEFINED in the configuration file availlable : " << m_config.getKeys()); diff --git a/river/io/Node.cpp b/river/io/Node.cpp index 4cc28f4..3eb54b0 100644 --- a/river/io/Node.cpp +++ b/river/io/Node.cpp @@ -37,7 +37,8 @@ river::io::Node::Node(const std::string& _name, const std11::shared_ptrgetStringValue("io"); if ( interfaceType == "input" || interfaceType == "PAinput" - || interfaceType == "aec") { + || interfaceType == "aec" + || interfaceType == "muxer") { m_isInput = true; } else { m_isInput = false; diff --git a/river/io/NodeMuxer.cpp b/river/io/NodeMuxer.cpp new file mode 100644 index 0000000..d30248e --- /dev/null +++ b/river/io/NodeMuxer.cpp @@ -0,0 +1,314 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2015, Edouard DUPIN, all right reserved + * @license APACHE v2.0 (see license file) + */ + +#include +#include +#include +#include +#include + +#undef __class__ +#define __class__ "io::NodeMuxer" + +std11::shared_ptr river::io::NodeMuxer::create(const std::string& _name, const std11::shared_ptr& _config) { + return std11::shared_ptr(new river::io::NodeMuxer(_name, _config)); +} + +std11::shared_ptr river::io::NodeMuxer::createInput(float _freq, + const std::vector& _map, + audio::format _format, + const std::string& _objectName, + const std::string& _name) { + // check if the output exist + const std11::shared_ptr tmppp = m_config->getObject(_objectName); + if (tmppp == nullptr) { + RIVER_ERROR("can not open a non existance virtual interface: '" << _objectName << "' not present in : " << m_config->getKeys()); + return std11::shared_ptr(); + } + std::string streamName = tmppp->getStringValue("map-on", "error"); + + + // check if it is an Output: + std::string type = tmppp->getStringValue("io", "error"); + if ( type != "input" + && type != "feedback") { + RIVER_ERROR("can not open in output a virtual interface: '" << streamName << "' configured has : " << type); + return std11::shared_ptr(); + } + // get global hardware interface: + std11::shared_ptr manager = river::io::Manager::getInstance(); + // get the output or input channel : + std11::shared_ptr node = manager->getNode(streamName); + // create user iterface: + std11::shared_ptr interface; + interface = river::Interface::create(_name, _freq, _map, _format, node, tmppp); + return interface; +} + + +river::io::NodeMuxer::NodeMuxer(const std::string& _name, const std11::shared_ptr& _config) : + Node(_name, _config) { + drain::IOFormatInterface interfaceFormat = getInterfaceFormat(); + drain::IOFormatInterface hardwareFormat = getHarwareFormat(); + m_sampleTime = std11::chrono::nanoseconds(1000000000/int64_t(hardwareFormat.getFrequency())); + /** + # connect in input mode + map-on-input-1:{ + # generic virtual definition + io:"input", + map-on:"microphone", + resampling-type:"speexdsp", + resampling-option:"quality=10" + }, + # connect in feedback mode + map-on-input-2:{ + io:"feedback", + map-on:"speaker", + resampling-type:"speexdsp", + resampling-option:"quality=10", + }, + # AEC algo definition + algo:"river-remover", + algo-mode:"cutter", + */ + RIVER_INFO("Create IN 1 : "); + m_interfaceInput1 = createInput(hardwareFormat.getFrequency(), + std::vector(), + hardwareFormat.getFormat(), + "map-on-input-1", + _name + "-muxer-in1"); + if (m_interfaceInput1 == nullptr) { + RIVER_ERROR("Can not opne virtual device ... map-on-input-1 in " << _name); + return; + } + m_mapInput1 = m_interfaceInput1->getInterfaceFormat().getMap(); + + RIVER_INFO("Create IN 2 : "); + m_interfaceInput2 = createInput(hardwareFormat.getFrequency(), + std::vector(), + hardwareFormat.getFormat(), + "map-on-input-2", + _name + "-muxer-in2"); + if (m_interfaceInput2 == nullptr) { + RIVER_ERROR("Can not opne virtual device ... map-on-input-2 in " << _name); + return; + } + m_mapInput2 = m_interfaceInput1->getInterfaceFormat().getMap(); + + // set callback mode ... + m_interfaceInput1->setInputCallback(std11::bind(&river::io::NodeMuxer::onDataReceivedFeedBack, + this, + std11::placeholders::_1, + std11::placeholders::_2, + std11::placeholders::_3, + std11::placeholders::_4, + std11::placeholders::_5, + std11::placeholders::_6)); + // set callback mode ... + m_interfaceInput2->setInputCallback(std11::bind(&river::io::NodeMuxer::onDataReceivedMicrophone, + this, + std11::placeholders::_1, + std11::placeholders::_2, + std11::placeholders::_3, + std11::placeholders::_4, + std11::placeholders::_5, + std11::placeholders::_6)); + + m_bufferInput1.setCapacity(std11::chrono::milliseconds(1000), + audio::getFormatBytes(hardwareFormat.getFormat())*m_mapInput1.size(), + hardwareFormat.getFrequency()); + m_bufferInput2.setCapacity(std11::chrono::milliseconds(1000), + audio::getFormatBytes(hardwareFormat.getFormat())*m_mapInput2.size(), + hardwareFormat.getFrequency()); + + m_process.updateInterAlgo(); +} + +river::io::NodeMuxer::~NodeMuxer() { + RIVER_INFO("close input stream"); + stop(); + m_interfaceInput1.reset(); + m_interfaceInput2.reset(); +}; + +void river::io::NodeMuxer::start() { + std11::unique_lock lock(m_mutex); + RIVER_INFO("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") ); + if (m_interfaceInput1 != nullptr) { + RIVER_INFO("Start FEEDBACK : "); + m_interfaceInput1->start(); + } + if (m_interfaceInput2 != nullptr) { + RIVER_INFO("Start Microphone : "); + m_interfaceInput2->start(); + } +} + +void river::io::NodeMuxer::stop() { + std11::unique_lock lock(m_mutex); + if (m_interfaceInput1 != nullptr) { + m_interfaceInput1->stop(); + } + if (m_interfaceInput2 != nullptr) { + m_interfaceInput2->stop(); + } +} + + +void river::io::NodeMuxer::onDataReceivedMicrophone(const void* _data, + const std11::chrono::system_clock::time_point& _time, + size_t _nbChunk, + enum audio::format _format, + uint32_t _frequency, + const std::vector& _map) { + RIVER_DEBUG("Microphone Time=" << _time << " _nbChunk=" << _nbChunk << " _map=" << _map << " _format=" << _format << " freq=" << _frequency); + RIVER_DEBUG(" next=" << _time + std11::chrono::nanoseconds(_nbChunk*1000000000LL/int64_t(_frequency)) ); + if (_format != audio::format_int16) { + RIVER_ERROR("call wrong type ... (need int16_t)"); + } + // push data synchronize + std11::unique_lock lock(m_mutex); + m_bufferInput1.write(_data, _nbChunk, _time); + //RIVER_SAVE_FILE_MACRO(int16_t, "REC_Microphone.raw", _data, _nbChunk*_map.size()); + process(); +} + +void river::io::NodeMuxer::onDataReceivedFeedBack(const void* _data, + const std11::chrono::system_clock::time_point& _time, + size_t _nbChunk, + enum audio::format _format, + uint32_t _frequency, + const std::vector& _map) { + RIVER_DEBUG("FeedBack Time=" << _time << " _nbChunk=" << _nbChunk << " _map=" << _map << " _format=" << _format << " freq=" << _frequency); + RIVER_DEBUG(" next=" << _time + std11::chrono::nanoseconds(_nbChunk*1000000000LL/int64_t(_frequency)) ); + if (_format != audio::format_int16) { + RIVER_ERROR("call wrong type ... (need int16_t)"); + } + // push data synchronize + std11::unique_lock lock(m_mutex); + m_bufferInput2.write(_data, _nbChunk, _time); + //RIVER_SAVE_FILE_MACRO(int16_t, "REC_FeedBack.raw", _data, _nbChunk*_map.size()); + process(); +} + +void river::io::NodeMuxer::process() { + if (m_bufferInput1.getSize() <= 256) { + return; + } + if (m_bufferInput2.getSize() <= 256) { + return; + } + std11::chrono::system_clock::time_point in1Time = m_bufferInput1.getReadTimeStamp(); + std11::chrono::system_clock::time_point in2Time = m_bufferInput2.getReadTimeStamp(); + std11::chrono::nanoseconds delta; + if (in1Time < in2Time) { + delta = in2Time - in1Time; + } else { + delta = in1Time - in2Time; + } + + RIVER_INFO("check delta " << delta.count() << " > " << m_sampleTime.count()); + if (delta > m_sampleTime) { + // Synchronize if possible + if (in1Time < in2Time) { + RIVER_INFO("in1Time < in2Time : Change Microphone time start " << in2Time); + RIVER_INFO(" old time stamp=" << m_bufferInput1.getReadTimeStamp()); + m_bufferInput1.setReadPosition(in2Time); + RIVER_INFO(" new time stamp=" << m_bufferInput1.getReadTimeStamp()); + } + if (in1Time > in2Time) { + RIVER_INFO("in1Time > in2Time : Change FeedBack time start " << in1Time); + RIVER_INFO(" old time stamp=" << m_bufferInput2.getReadTimeStamp()); + m_bufferInput2.setReadPosition(in1Time); + RIVER_INFO(" new time stamp=" << m_bufferInput2.getReadTimeStamp()); + } + } + // check if enought time after synchronisation ... + if (m_bufferInput1.getSize() <= 256) { + return; + } + if (m_bufferInput2.getSize() <= 256) { + return; + } + + in1Time = m_bufferInput1.getReadTimeStamp(); + in2Time = m_bufferInput2.getReadTimeStamp(); + + if (in1Time-in2Time > m_sampleTime) { + RIVER_ERROR("Can not synchronize flow ... : " << in1Time << " != " << in2Time << " delta = " << (in1Time-in2Time).count()/1000 << " µs"); + return; + } + std::vector dataIn1; + std::vector dataIn2; + dataIn1.resize(256*sizeof(int16_t)*m_mapInput1.size(), 0); + dataIn2.resize(256*sizeof(int16_t)*m_mapInput2.size(), 0); + while (true) { + in1Time = m_bufferInput1.getReadTimeStamp(); + in2Time = m_bufferInput2.getReadTimeStamp(); + RIVER_INFO(" process 256 samples ... in1Time=" << in1Time << " in2Time=" << in2Time << " delta = " << (in1Time-in2Time).count()); + m_bufferInput1.read(&dataIn1[0], 256); + m_bufferInput2.read(&dataIn2[0], 256); + RIVER_SAVE_FILE_MACRO(int16_t, "REC_INPUT1.raw", &dataIn1[0], 256 * m_mapInput1.size()); + RIVER_SAVE_FILE_MACRO(int16_t, "REC_INPUT2.raw", &dataIn2[0], 256 * m_mapInput2.size()); + // if threaded : send event / otherwise, process ... + processMuxer(&dataIn1[0], &dataIn2[0], 256, in1Time); + if ( m_bufferInput1.getSize() <= 256 + || m_bufferInput2.getSize() <= 256) { + return; + } + } +} + + +void river::io::NodeMuxer::processMuxer(void* _dataIn1, void* _dataIn2, uint32_t _nbChunk, const std11::chrono::system_clock::time_point& _time) { + RIVER_INFO("must Mux data : " << m_mapInput1 << " + " << m_mapInput2 << " ==> " << getInterfaceFormat().getMap()); + //newInput(_dataIn1, _nbChunk, _time); +} + + +void river::io::NodeMuxer::generateDot(etk::FSNode& _node) { + _node << "subgraph clusterNode_" << m_uid << " {\n"; + _node << " color=blue;\n"; + _node << " label=\"[" << m_uid << "] IO::Node : " << m_name << "\";\n"; + + _node << " node [shape=box];\n"; + // TODO : Create a structure ... + _node << " NODE_" << m_uid << "_HW_AEC [ label=\"AEC\" ];\n"; + _node << " subgraph clusterNode_" << m_uid << "_process {\n"; + _node << " label=\"Drain::Process\";\n"; + _node << " node [shape=ellipse];\n"; + _node << " node_ALGO_" << m_uid << "_in [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\" ];\n"; + _node << " node_ALGO_" << m_uid << "_out [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\" ];\n"; + + _node << " }\n"; + _node << " node [shape=square];\n"; + _node << " NODE_" << m_uid << "_demuxer [ label=\"DEMUXER\n format=xxx\" ];\n"; + // Link all nodes : + _node << " NODE_" << m_uid << "_HW_AEC -> node_ALGO_" << m_uid << "_in;\n"; + _node << " node_ALGO_" << m_uid << "_in -> node_ALGO_" << m_uid << "_out;\n"; + _node << " node_ALGO_" << m_uid << "_out -> NODE_" << m_uid << "_demuxer;\n"; + _node << "}\n"; + if (m_interfaceInput2 != nullptr) { + _node << " API_" << m_interfaceInput2->m_uid << "_input -> NODE_" << m_uid << "_HW_AEC;\n"; + } + if (m_interfaceInput1 != nullptr) { + _node << " API_" << m_interfaceInput1->m_uid << "_feedback -> NODE_" << m_uid << "_HW_AEC;\n"; + } + + for (size_t iii=0; iiigetMode() == modeInterface_input) { + m_list[iii]->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_demuxer"); + } else if (m_list[iii]->getMode() == modeInterface_output) { + m_list[iii]->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_muxer"); + } else if (m_list[iii]->getMode() == modeInterface_feedback) { + m_list[iii]->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_demuxer"); + } else { + + } + } + } +} \ No newline at end of file diff --git a/river/io/NodeMuxer.h b/river/io/NodeMuxer.h new file mode 100644 index 0000000..3b62127 --- /dev/null +++ b/river/io/NodeMuxer.h @@ -0,0 +1,66 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2015, Edouard DUPIN, all right reserved + * @license APACHE v2.0 (see license file) + */ + +#ifndef __RIVER_IO_NODE_MUXER_H__ +#define __RIVER_IO_NODE_MUXER_H__ + +#include +#include +#include + +namespace river { + namespace io { + class Manager; + class NodeMuxer : public Node { + protected: + /** + * @brief Constructor + */ + NodeMuxer(const std::string& _name, const std11::shared_ptr& _config); + public: + static std11::shared_ptr create(const std::string& _name, const std11::shared_ptr& _config); + /** + * @brief Destructor + */ + virtual ~NodeMuxer(); + protected: + virtual void start(); + virtual void stop(); + std11::shared_ptr m_interfaceInput1; + std11::shared_ptr m_interfaceInput2; + std11::shared_ptr createInput(float _freq, + const std::vector& _map, + audio::format _format, + const std::string& _streamName, + const std::string& _name); + void onDataReceivedMicrophone(const void* _data, + const std11::chrono::system_clock::time_point& _time, + size_t _nbChunk, + enum audio::format _format, + uint32_t _frequency, + const std::vector& _map); + void onDataReceivedFeedBack(const void* _data, + const std11::chrono::system_clock::time_point& _time, + size_t _nbChunk, + enum audio::format _format, + uint32_t _frequency, + const std::vector& _map); + std::vector m_mapInput1; + std::vector m_mapInput2; + river::CircularBuffer m_bufferInput1; + river::CircularBuffer m_bufferInput2; + std11::chrono::nanoseconds m_sampleTime; //!< represent the sample time at the specify frequency. + void process(); + void processMuxer(void* _dataMic, void* _dataFB, uint32_t _nbChunk, const std11::chrono::system_clock::time_point& _time); + std::vector m_data; + public: + virtual void generateDot(etk::FSNode& _node); + }; + } +} + +#endif + diff --git a/test/testEchoDelay.h b/test/testEchoDelay.h index 99f7f9c..e4443f9 100644 --- a/test/testEchoDelay.h +++ b/test/testEchoDelay.h @@ -269,8 +269,8 @@ namespace river_test_echo_delay { if ( valueMax > m_volumeInputMax && valueMin < m_volumeInputMin && ( m_gain == 0.0 - || ( valueMax > INT16_MAX/2 - && valueMin < INT16_MIN/2 + || ( valueMax > INT16_MAX*2/3 + && valueMin < INT16_MIN*2/3 ) ) ) { diff --git a/test/testMuxer.h b/test/testMuxer.h index b8c8125..153be50 100644 --- a/test/testMuxer.h +++ b/test/testMuxer.h @@ -7,11 +7,62 @@ #ifndef __RIVER_TEST_MUXER_H__ #define __RIVER_TEST_MUXER_H__ +#include + #undef __class__ #define __class__ "test_muxer" namespace river_test_muxer { + class TestClass { + private: + std11::shared_ptr m_manager; + std11::shared_ptr m_interfaceIn; + public: + TestClass(std11::shared_ptr _manager) : + m_manager(_manager) { + //Set stereo output: + m_interfaceIn = m_manager->createInput(48000, + std::vector(), + audio::format_int16, + "microphone-muxed", + "microphone-muxed-local-name"); + // set callback mode ... + m_interfaceIn->setInputCallback(std11::bind(&TestClass::onDataReceived, + this, + std11::placeholders::_1, + std11::placeholders::_2, + std11::placeholders::_3, + std11::placeholders::_4, + std11::placeholders::_5, + std11::placeholders::_6)); + m_manager->generateDotAll("activeProcess.dot"); + } + void onDataReceived(const void* _data, + const std11::chrono::system_clock::time_point& _time, + size_t _nbChunk, + enum audio::format _format, + uint32_t _frequency, + const std::vector& _map) { + if (_format != audio::format_int16) { + APPL_ERROR("call wrong type ... (need int16_t)"); + } + RIVER_SAVE_FILE_MACRO(int16_t, "REC_MicrophoneMuxed.raw", _data, _nbChunk*_map.size()); + } + void run() { + m_interfaceIn->start(); + usleep(10000000); + m_interfaceIn->stop(); + } + }; + TEST(TestMuxer, testMuxing) { + std11::shared_ptr manager; + manager = river::Manager::create("testApplication"); + std11::shared_ptr process = std11::make_shared(manager); + process->run(); + process.reset(); + usleep(500000); + } }; #undef __class__