summaryrefslogtreecommitdiff
path: root/muse2/muse/driver/jackmidi.cpp
diff options
context:
space:
mode:
authorTim E. Real <termtech@rogers.com>2011-02-09 22:37:27 +0000
committerTim E. Real <termtech@rogers.com>2011-02-09 22:37:27 +0000
commitd7f4820023196bfc7d40ef5fa134bc46a6e55849 (patch)
treefa00ee04c060c2ece1122f9d3fee382b0d5f043c /muse2/muse/driver/jackmidi.cpp
parente11f572d97bb761d848fad41991d0ed747bad111 (diff)
Mini MIDI overhaul. Please see ChangeLog.
Diffstat (limited to 'muse2/muse/driver/jackmidi.cpp')
-rw-r--r--muse2/muse/driver/jackmidi.cpp242
1 files changed, 172 insertions, 70 deletions
diff --git a/muse2/muse/driver/jackmidi.cpp b/muse2/muse/driver/jackmidi.cpp
index d401c7e1..c950e096 100644
--- a/muse2/muse/driver/jackmidi.cpp
+++ b/muse2/muse/driver/jackmidi.cpp
@@ -1150,7 +1150,8 @@ void MidiJackDevice::collectMidiEvents()
bool MidiJackDevice::putEvent(const MidiPlayEvent& ev)
{
- if(!_writeEnable)
+ //if(!_writeEnable)
+ if(!_writeEnable || !_out_client_jackport) // p4.0.15
//return true;
return false;
@@ -1225,7 +1226,9 @@ bool MidiJackDevice::queueEvent(const MidiPlayEvent& e)
unsigned char* p = jack_midi_event_reserve(pb, ft, 3);
if (p == 0) {
- fprintf(stderr, "MidiJackDevice::queueEvent #1: buffer overflow, event lost\n");
+ #ifdef JACK_MIDI_DEBUG
+ fprintf(stderr, "MidiJackDevice::queueEvent NOTE CTL PAT or PB: buffer overflow, stopping until next cycle\n");
+ #endif
return false;
}
p[0] = e.type() | e.channel();
@@ -1243,7 +1246,9 @@ bool MidiJackDevice::queueEvent(const MidiPlayEvent& e)
unsigned char* p = jack_midi_event_reserve(pb, ft, 2);
if (p == 0) {
- fprintf(stderr, "MidiJackDevice::queueEvent #2: buffer overflow, event lost\n");
+ #ifdef JACK_MIDI_DEBUG
+ fprintf(stderr, "MidiJackDevice::queueEvent PROG or AT: buffer overflow, stopping until next cycle\n");
+ #endif
return false;
}
p[0] = e.type() | e.channel();
@@ -1260,8 +1265,16 @@ bool MidiJackDevice::queueEvent(const MidiPlayEvent& e)
int len = e.len();
unsigned char* p = jack_midi_event_reserve(pb, ft, len+2);
if (p == 0) {
- fprintf(stderr, "MidiJackDevice::queueEvent #3: buffer overflow, event lost\n");
- return false;
+ fprintf(stderr, "MidiJackDevice::queueEvent ME_SYSEX: buffer overflow, sysex too big, event lost\n");
+
+ //return false;
+ // Changed to true. Absorb the sysex if it is too big, to avoid attempting
+ // to resend repeatedly. If the sysex is too big, it would just stay in the
+ // list and never be processed, because Jack could never reserve enough space.
+ // Other types of events should be OK since they are small and can be resent
+ // next cycle. p4.0.15 Tim.
+ // FIXME: We really need to chunk sysex events properly. It's tough. Investigating...
+ return true;
}
p[0] = 0xf0;
p[len+1] = 0xf7;
@@ -1273,8 +1286,10 @@ bool MidiJackDevice::queueEvent(const MidiPlayEvent& e)
case ME_START:
case ME_CONTINUE:
case ME_STOP:
- printf("MidiJackDevice::queueEvent: event type %x not supported\n", e.type());
- return false;
+ if(debugMsg)
+ printf("MidiJackDevice::queueEvent: event type %x not supported\n", e.type());
+ //return false;
+ return true; // Absorb the event. Don't want it hanging around in the list. FIXME: Support these? p4.0.15 Tim.
break;
}
@@ -1283,9 +1298,10 @@ bool MidiJackDevice::queueEvent(const MidiPlayEvent& e)
//---------------------------------------------------------
// processEvent
+// return true if successful
//---------------------------------------------------------
-void MidiJackDevice::processEvent(const MidiPlayEvent& event)
+bool MidiJackDevice::processEvent(const MidiPlayEvent& event)
{
//int frameOffset = audio->getFrameOffset();
//unsigned pos = audio->pos().frame();
@@ -1302,12 +1318,15 @@ void MidiJackDevice::processEvent(const MidiPlayEvent& event)
// Just do this 'standard midi 64T timing thing' for now until we figure out more precise external timings.
// Does require relatively short audio buffers, in order to catch the resolution, but buffer <= 256 should be OK...
// Tested OK so far with 128.
- if(extSyncFlag.value())
+ ///if(extSyncFlag.value())
+ // p4.0.15 Or, is the event marked to be played immediately?
+ // Nothing to do but stamp the event to be queued for frame 0+.
+ if(t == 0 || extSyncFlag.value())
t = audio->getFrameOffset() + audio->pos().frame();
//t = frameOffset + pos;
#ifdef JACK_MIDI_DEBUG
- printf("MidiJackDevice::processEvent time:%d type:%d ch:%d A:%d B:%d\n", event.time(), event.type(), event.channel(), event.dataA(), event.dataB());
+ //printf("MidiJackDevice::processEvent time:%d type:%d ch:%d A:%d B:%d\n", t, event.type(), chn, a, b);
#endif
if(event.type() == ME_PROGRAM)
@@ -1318,15 +1337,18 @@ void MidiJackDevice::processEvent(const MidiPlayEvent& event)
int lb = (a >> 8) & 0xff;
int pr = a & 0x7f;
- // p3.3.44
//printf("MidiJackDevice::processEvent ME_PROGRAM time:%d type:%d ch:%d A:%d B:%d hb:%d lb:%d pr:%d\n",
// event.time(), event.type(), event.channel(), event.dataA(), event.dataB(), hb, lb, pr);
if (hb != 0xff)
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HBANK, hb));
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HBANK, hb)))
+ return false; // p4.0.15 Inform that processing the event in general failed. Ditto all below...
if (lb != 0xff)
- queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, CTRL_LBANK, lb));
- queueEvent(MidiPlayEvent(t+2, port, chn, ME_PROGRAM, pr, 0));
+ if(!queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, CTRL_LBANK, lb)))
+ return false;
+ if(!queueEvent(MidiPlayEvent(t+2, port, chn, ME_PROGRAM, pr, 0)))
+ return false;
+
// }
}
else
@@ -1336,7 +1358,8 @@ void MidiJackDevice::processEvent(const MidiPlayEvent& event)
// p3.3.44
//printf("MidiJackDevice::processEvent ME_PITCHBEND v:%d time:%d type:%d ch:%d A:%d B:%d\n", v, event.time(), event.type(), event.channel(), event.dataA(), event.dataB());
- queueEvent(MidiPlayEvent(t, port, chn, ME_PITCHBEND, v & 0x7f, (v >> 7) & 0x7f));
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_PITCHBEND, v & 0x7f, (v >> 7) & 0x7f)))
+ return false;
}
else
if(event.type() == ME_CONTROLLER)
@@ -1365,7 +1388,8 @@ void MidiJackDevice::processEvent(const MidiPlayEvent& event)
// p3.3.44
//printf("MidiJackDevice::processEvent CTRL_PITCH v:%d time:%d type:%d ch:%d A:%d B:%d\n", v, event.time(), event.type(), event.channel(), event.dataA(), event.dataB());
- queueEvent(MidiPlayEvent(t, port, chn, ME_PITCHBEND, v & 0x7f, (v >> 7) & 0x7f));
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_PITCHBEND, v & 0x7f, (v >> 7) & 0x7f)))
+ return false;
}
else if (a == CTRL_PROGRAM)
{
@@ -1380,28 +1404,36 @@ void MidiJackDevice::processEvent(const MidiPlayEvent& event)
// event.time(), event.type(), event.channel(), event.dataA(), event.dataB(), hb, lb, pr);
if (hb != 0xff)
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HBANK, hb));
+ {
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HBANK, hb)))
+ return false;
+ }
if (lb != 0xff)
- queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, CTRL_LBANK, lb));
- queueEvent(MidiPlayEvent(t+2, port, chn, ME_PROGRAM, pr, 0));
+ {
+ if(!queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, CTRL_LBANK, lb)))
+ return false;
+ }
+ if(!queueEvent(MidiPlayEvent(t+2, port, chn, ME_PROGRAM, pr, 0)))
+ return false;
+
// }
}
- /*
- else if (a == CTRL_MASTER_VOLUME)
+ else if (a == CTRL_MASTER_VOLUME) // Enabled p4.0.15 Tim.
{
unsigned char sysex[] = {
0x7f, 0x7f, 0x04, 0x01, 0x00, 0x00
};
- sysex[1] = deviceId();
+ //sysex[1] = deviceId(); TODO FIXME p4.0.15 Grab the ID from midi port sync info.
sysex[4] = b & 0x7f;
sysex[5] = (b >> 7) & 0x7f;
- queueEvent(MidiPlayEvent(t, port, ME_SYSEX, sysex, 6));
+ if(!queueEvent(MidiPlayEvent(t, port, ME_SYSEX, sysex, 6)))
+ return false;
}
- */
else if (a < CTRL_14_OFFSET)
{ // 7 Bit Controller
- queueEvent(event);
//queueEvent(museport, MidiPlayEvent(t, port, chn, event));
+ if(!queueEvent(event))
+ return false;
}
else if (a < CTRL_RPN_OFFSET)
{ // 14 bit high resolution controller
@@ -1409,46 +1441,62 @@ void MidiJackDevice::processEvent(const MidiPlayEvent& event)
int ctrlL = a & 0x7f;
int dataH = (b >> 7) & 0x7f;
int dataL = b & 0x7f;
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, ctrlH, dataH));
- queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, ctrlL, dataL));
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, ctrlH, dataH)))
+ return false;
+ if(!queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, ctrlL, dataL)))
+ return false;
}
else if (a < CTRL_NRPN_OFFSET)
{ // RPN 7-Bit Controller
int ctrlH = (a >> 8) & 0x7f;
int ctrlL = a & 0x7f;
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HRPN, ctrlH));
- queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, CTRL_LRPN, ctrlL));
- queueEvent(MidiPlayEvent(t+2, port, chn, ME_CONTROLLER, CTRL_HDATA, b));
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HRPN, ctrlH)))
+ return false;
+ if(!queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, CTRL_LRPN, ctrlL)))
+ return false;
+ if(!queueEvent(MidiPlayEvent(t+2, port, chn, ME_CONTROLLER, CTRL_HDATA, b)))
+ return false;
t += 3;
// Select null parameters so that subsequent data controller events do not upset the last *RPN controller.
//sendNullRPNParams(chn, false);
if(nvh != 0xff)
{
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HRPN, nvh & 0x7f));
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HRPN, nvh & 0x7f)))
+ return false;
t += 1;
}
if(nvl != 0xff)
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_LRPN, nvl & 0x7f));
+ {
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_LRPN, nvl & 0x7f)))
+ return false;
+ }
}
//else if (a < CTRL_RPN14_OFFSET)
else if (a < CTRL_INTERNAL_OFFSET)
{ // NRPN 7-Bit Controller
int ctrlH = (a >> 8) & 0x7f;
int ctrlL = a & 0x7f;
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HNRPN, ctrlH));
- queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, CTRL_LNRPN, ctrlL));
- queueEvent(MidiPlayEvent(t+2, port, chn, ME_CONTROLLER, CTRL_HDATA, b));
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HNRPN, ctrlH)))
+ return false;
+ if(!queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, CTRL_LNRPN, ctrlL)))
+ return false;
+ if(!queueEvent(MidiPlayEvent(t+2, port, chn, ME_CONTROLLER, CTRL_HDATA, b)))
+ return false;
t += 3;
//sendNullRPNParams(chn, true);
if(nvh != 0xff)
{
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HNRPN, nvh & 0x7f));
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HNRPN, nvh & 0x7f)))
+ return false;
t += 1;
}
if(nvl != 0xff)
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_LNRPN, nvl & 0x7f));
+ {
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_LNRPN, nvl & 0x7f)))
+ return false;
+ }
}
else if (a < CTRL_NRPN14_OFFSET)
{ // RPN14 Controller
@@ -1456,20 +1504,28 @@ void MidiJackDevice::processEvent(const MidiPlayEvent& event)
int ctrlL = a & 0x7f;
int dataH = (b >> 7) & 0x7f;
int dataL = b & 0x7f;
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HRPN, ctrlH));
- queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, CTRL_LRPN, ctrlL));
- queueEvent(MidiPlayEvent(t+2, port, chn, ME_CONTROLLER, CTRL_HDATA, dataH));
- queueEvent(MidiPlayEvent(t+3, port, chn, ME_CONTROLLER, CTRL_LDATA, dataL));
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HRPN, ctrlH)))
+ return false;
+ if(!queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, CTRL_LRPN, ctrlL)))
+ return false;
+ if(!queueEvent(MidiPlayEvent(t+2, port, chn, ME_CONTROLLER, CTRL_HDATA, dataH)))
+ return false;
+ if(!queueEvent(MidiPlayEvent(t+3, port, chn, ME_CONTROLLER, CTRL_LDATA, dataL)))
+ return false;
t += 4;
//sendNullRPNParams(chn, false);
if(nvh != 0xff)
{
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HRPN, nvh & 0x7f));
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HRPN, nvh & 0x7f)))
+ return false;
t += 1;
}
if(nvl != 0xff)
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_LRPN, nvl & 0x7f));
+ {
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_LRPN, nvl & 0x7f)))
+ return false;
+ }
}
else if (a < CTRL_NONE_OFFSET)
{ // NRPN14 Controller
@@ -1477,31 +1533,44 @@ void MidiJackDevice::processEvent(const MidiPlayEvent& event)
int ctrlL = a & 0x7f;
int dataH = (b >> 7) & 0x7f;
int dataL = b & 0x7f;
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HNRPN, ctrlH));
- queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, CTRL_LNRPN, ctrlL));
- queueEvent(MidiPlayEvent(t+2, port, chn, ME_CONTROLLER, CTRL_HDATA, dataH));
- queueEvent(MidiPlayEvent(t+3, port, chn, ME_CONTROLLER, CTRL_LDATA, dataL));
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HNRPN, ctrlH)))
+ return false;
+ if(!queueEvent(MidiPlayEvent(t+1, port, chn, ME_CONTROLLER, CTRL_LNRPN, ctrlL)))
+ return false;
+ if(!queueEvent(MidiPlayEvent(t+2, port, chn, ME_CONTROLLER, CTRL_HDATA, dataH)))
+ return false;
+ if(!queueEvent(MidiPlayEvent(t+3, port, chn, ME_CONTROLLER, CTRL_LDATA, dataL)))
+ return false;
t += 4;
//sendNullRPNParams(chn, true);
if(nvh != 0xff)
{
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HNRPN, nvh & 0x7f));
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_HNRPN, nvh & 0x7f)))
+ return false;
t += 1;
}
if(nvl != 0xff)
- queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_LNRPN, nvl & 0x7f));
+ {
+ if(!queueEvent(MidiPlayEvent(t, port, chn, ME_CONTROLLER, CTRL_LNRPN, nvl & 0x7f)))
+ return false;
+ }
}
else
{
- printf("MidiJackDevice::processEvent: unknown controller type 0x%x\n", a);
+ if(debugMsg)
+ printf("MidiJackDevice::processEvent: unknown controller type 0x%x\n", a);
+ //return false; // Just ignore it.
}
}
else
{
- queueEvent(event);
//queueEvent(MidiPlayEvent(t, port, chn, event));
+ if(!queueEvent(event))
+ return false;
}
+
+ return true;
}
//---------------------------------------------------------
@@ -1511,40 +1580,58 @@ void MidiJackDevice::processEvent(const MidiPlayEvent& event)
void MidiJackDevice::processMidi()
{
//if(!_client_jackport)
- if(!_out_client_jackport) // p3.3.55
- return;
+ //if(!_out_client_jackport) // p3.3.55
+ // return;
+
//void* port_buf = jack_port_get_buffer(_client_jackport, segmentSize);
- void* port_buf = jack_port_get_buffer(_out_client_jackport, segmentSize); // p3.3.55
- jack_midi_clear_buffer(port_buf);
+ //void* port_buf = jack_port_get_buffer(_out_client_jackport, segmentSize); // p3.3.55
+ void* port_buf = 0;
+ if(_out_client_jackport && _writeEnable) // p4.0.15
+ {
+ port_buf = jack_port_get_buffer(_out_client_jackport, segmentSize);
+ jack_midi_clear_buffer(port_buf);
+ }
while(!eventFifo.isEmpty())
{
- MidiPlayEvent e(eventFifo.get());
- int evTime = e.time();
- // Is event marked to be played immediately?
- if(evTime == 0)
- {
+ ///MidiPlayEvent e(eventFifo.get());
+ MidiPlayEvent e(eventFifo.peek()); // p4.0.15
+
+ ///int evTime = e.time();
+ // Is event marked to be played immediately? p4.0.15 Moved into processEvent().
+ ///if(evTime == 0)
+ ///{
// Nothing to do but stamp the event to be queued for frame 0+.
//e.setTime(frameOffset + pos);
- e.setTime(audio->getFrameOffset() + audio->pos().frame());
- }
+ /// e.setTime(audio->getFrameOffset() + audio->pos().frame());
+ ///}
- #ifdef JACK_MIDI_DEBUG
- printf("MidiJackDevice::processMidi eventFifo time:%d type:%d ch:%d A:%d B:%d\n", e.time(), e.type(), e.channel(), e.dataA(), e.dataB());
- #endif
+ //#ifdef JACK_MIDI_DEBUG
+ //printf("MidiJackDevice::processMidi eventFifo time:%d type:%d ch:%d A:%d B:%d\n", e.time(), e.type(), e.channel(), e.dataA(), e.dataB());
+ //#endif
//el->insert(eventFifo.get());
//el->insert(e);
- processEvent(e);
+ ///processEvent(e);
+ // p4.0.15 Try to process only until full, keep rest for next cycle. If no out client port or no write enable, eat up events.
+ if(port_buf && !processEvent(e))
+ return; // Give up. The Jack buffer is full. Nothing left to do.
+ eventFifo.remove(); // Successfully processed event. Remove it from FIFO.
}
MPEventList* el = playEvents();
if(el->empty())
+ {
+ //printf("MidiJackDevice::processMidi play events empty\n");
return;
+ }
- iMPEvent i = nextPlayEvent();
+ //printf("MidiJackDevice::processMidi play events:\n");
+ ///iMPEvent i = nextPlayEvent();
+ iMPEvent i = el->begin(); // p4.0.15
for(; i != el->end(); ++i)
{
+ //printf("MidiJackDevice::processMidi playEvent time:%d type:%d ch:%d A:%d B:%d\n", i->time(), i->type(), i->channel(), i->dataA(), i->dataB());
// p3.3.39 Update hardware state so knobs and boxes are updated. Optimize to avoid re-setting existing values.
// Same code as in MidiPort::sendEvent()
if(_port != -1)
@@ -1562,7 +1649,6 @@ void MidiJackDevice::processMidi()
else
if(i->type() == ME_PITCHBEND)
{
- // p3.3.44
//printf("MidiJackDevice::processMidi playEvents ME_PITCHBEND time:%d type:%d ch:%d A:%d B:%d\n", (*i).time(), (*i).type(), (*i).channel(), (*i).dataA(), (*i).dataB());
int da = mp->limitValToInstrCtlRange(CTRL_PITCH, i->dataA());
@@ -1581,10 +1667,26 @@ void MidiJackDevice::processMidi()
}
}
- processEvent(*i);
+ ///processEvent(*i);
+ // p4.0.15 Try to process only until full, keep rest for next cycle. If no out client port or no write enable, eat up events.
+ if(port_buf && !processEvent(*i))
+ {
+ //setNextPlayEvent(i);
+ //return;
+ break;
+ }
}
- setNextPlayEvent(i);
+ ///setNextPlayEvent(i);
+ // p4.0.15 We are done with these events. Let us erase them here instead of Audio::processMidi.
+ // That way we can simply set the next play event to the beginning.
+ // This also allows other events to be inserted without the problems caused by the next play event
+ // being at the 'end' iterator and not being *easily* set to some new place beginning of the newer insertions.
+ // The way that MPEventList sorts made it difficult to predict where the iterator of the first newly inserted items was.
+ // The erasure in Audio::processMidi was missing some events because of that.
+ el->erase(el->begin(), i);
+ // setNextPlayEvent(el->begin()); // Removed p4.0.15 Tim.
+
}
//---------------------------------------------------------