Reverted Thru changes on branch release/4.0 (breaking thread/merge-safety).

This commit is contained in:
Francois Best 2013-07-07 15:10:13 +02:00
parent cc9927fd50
commit 601ddb3773
3 changed files with 464 additions and 411 deletions

View File

@ -68,7 +68,7 @@ public:
inline void sendAfterTouch(DataByte inPressure,
Channel inChannel);
inline void sendSysEx(unsigned inLength,
inline void sendSysEx(unsigned int inLength,
const byte* inArray,
bool inArrayContainsBoundaries = false);
@ -76,7 +76,7 @@ public:
DataByte inValuesNibble);
inline void sendTimeCodeQuarterFrame(DataByte inData);
inline void sendSongPosition(unsigned inBeats);
inline void sendSongPosition(unsigned int inBeats);
inline void sendSongSelect(DataByte inSongNumber);
inline void sendTuneRequest();
inline void sendRealTime(MidiType inType);
@ -109,7 +109,7 @@ public:
inline DataByte getData1() const;
inline DataByte getData2() const;
inline const byte* getSysExArray() const;
inline unsigned getSysExArrayLength() const;
inline unsigned int getSysExArrayLength() const;
inline bool check() const;
public:
@ -117,11 +117,8 @@ public:
inline void setInputChannel(Channel inChannel);
public:
static inline MidiType getTypeFromStatusByte(StatusByte inStatus);
static inline byte getMessageLength(StatusByte inStatus);
static inline MidiType getTypeFromStatusByte(byte inStatus);
static inline bool isChannelMessage(MidiType inType);
static inline bool isSystemRealtimeMessage(MidiType inType);
static inline bool isSystemCommonMessage(MidiType inType);
private:
bool inputFilter(Channel inChannel);
@ -133,8 +130,8 @@ private:
Channel mInputChannel;
byte mPendingMessage[3]; // SysEx are dumped into mMessage directly.
unsigned mPendingMessageExpectedLenght;
unsigned mPendingMessageIndex; // Extended to unsigned for larger SysEx payloads.
unsigned int mPendingMessageExpectedLenght;
unsigned int mPendingMessageIndex; // Extended to unsigned int for larger SysEx payloads.
Message mMessage;
@ -153,7 +150,7 @@ public:
inline void setHandlePitchBend(void (*fptr)(byte channel, int bend));
inline void setHandleSystemExclusive(void (*fptr)(byte * array, byte size));
inline void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data));
inline void setHandleSongPosition(void (*fptr)(unsigned beats));
inline void setHandleSongPosition(void (*fptr)(unsigned int beats));
inline void setHandleSongSelect(void (*fptr)(byte songnumber));
inline void setHandleTuneRequest(void (*fptr)(void));
inline void setHandleClock(void (*fptr)(void));
@ -178,7 +175,7 @@ private:
void (*mPitchBendCallback)(byte channel, int);
void (*mSystemExclusiveCallback)(byte * array, byte size);
void (*mTimeCodeQuarterFrameCallback)(byte data);
void (*mSongPositionCallback)(unsigned beats);
void (*mSongPositionCallback)(unsigned int beats);
void (*mSongSelectCallback)(byte songnumber);
void (*mTuneRequestCallback)(void);
void (*mClockCallback)(void);
@ -199,19 +196,20 @@ private:
#if MIDI_BUILD_THRU
public:
inline void turnThruOn(ThruFlags inThruFlags = ThruFilterFlags::all);
inline MidiFilterMode getFilterMode() const;
inline bool getThruState() const;
inline void turnThruOn(MidiFilterMode inThruFilterMode = Full);
inline void turnThruOff();
inline void setThruFilterMode(MidiFilterMode inThruFilterMode);
inline void setThruFilterMode(ThruFlags inFlags);
inline ThruFlags getThruFilterMode() const;
inline bool isThruOn() const;
inline bool hasThruFlag(byte inFlag) const;
private:
inline bool shouldMessageBeForwarded(StatusByte inStatus) const;
void thruFilter(byte inChannel);
private:
ThruFlags mThruFlags;
bool mThruActivated : 1;
MidiFilterMode mThruFilterMode : 7;
#endif // MIDI_BUILD_THRU

View File

@ -70,7 +70,7 @@ void MidiInterface<SerialPort>::begin(Channel inChannel)
mRunningStatus_TX = InvalidType;
#endif
#endif // MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS
#if MIDI_BUILD_INPUT
@ -86,14 +86,16 @@ void MidiInterface<SerialPort>::begin(Channel inChannel)
mMessage.data1 = 0;
mMessage.data2 = 0;
#endif
#endif // MIDI_BUILD_INPUT
#if MIDI_BUILD_THRU
#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) // Thru
mThruFlags = ThruFilterFlags::all;
mThruFilterMode = Full;
mThruActivated = true;
#endif // Thru
#endif
}
@ -266,10 +268,11 @@ template<class SerialPort>
void MidiInterface<SerialPort>::sendPitchBend(int inPitchValue,
Channel inChannel)
{
const unsigned bend = inPitchValue - MIDI_PITCHBEND_MIN;
const unsigned int bend = inPitchValue - MIDI_PITCHBEND_MIN;
send(PitchBend, (bend & 0x7F), (bend >> 7) & 0x7F, inChannel);
}
/*! \brief Send a Pitch Bend message using a floating point value.
\param inPitchValue The amount of bend to send (in a floating point format),
between -1.0f (maximum downwards bend)
@ -290,11 +293,11 @@ void MidiInterface<SerialPort>::sendPitchBend(double inPitchValue,
\param inArrayContainsBoundaries When set to 'true', 0xF0 & 0xF7 bytes
(start & stop SysEx) will NOT be sent
(and therefore must be included in the array).
default value for inArrayContainsBoundaries is set to 'false' for compatibility
default value for ArrayContainsBoundaries is set to 'false' for compatibility
with previous versions of the library.
*/
template<class SerialPort>
void MidiInterface<SerialPort>::sendSysEx(unsigned inLength,
void MidiInterface<SerialPort>::sendSysEx(unsigned int inLength,
const byte* inArray,
bool inArrayContainsBoundaries)
{
@ -302,14 +305,14 @@ void MidiInterface<SerialPort>::sendSysEx(unsigned inLength,
{
mSerial.write(0xF0);
for (unsigned i = 0; i < inLength; ++i)
for (unsigned int i = 0; i < inLength; ++i)
mSerial.write(inArray[i]);
mSerial.write(0xF7);
}
else
{
for (unsigned i = 0; i < inLength; ++i)
for (unsigned int i = 0; i < inLength; ++i)
mSerial.write(inArray[i]);
}
@ -364,7 +367,7 @@ void MidiInterface<SerialPort>::sendTimeCodeQuarterFrame(DataByte inData)
\param inBeats The number of beats since the start of the song.
*/
template<class SerialPort>
void MidiInterface<SerialPort>::sendSongPosition(unsigned inBeats)
void MidiInterface<SerialPort>::sendSongPosition(unsigned int inBeats)
{
mSerial.write((byte)SongPosition);
mSerial.write(inBeats & 0x7F);
@ -397,9 +400,20 @@ void MidiInterface<SerialPort>::sendSongSelect(DataByte inSongNumber)
template<class SerialPort>
void MidiInterface<SerialPort>::sendRealTime(MidiType inType)
{
if (isSystemRealtimeMessage(inType) || inType == TuneRequest)
switch (inType)
{
case TuneRequest: // Not really real-time, but one byte anyway.
case Clock:
case Start:
case Stop:
case Continue:
case ActiveSensing:
case SystemReset:
mSerial.write((byte)inType);
break;
default:
// Invalid Real Time marker
break;
}
// Do not cancel Running Status for real-time messages as they can be
@ -456,13 +470,22 @@ bool MidiInterface<SerialPort>::read(Channel inChannel)
if (inChannel >= MIDI_CHANNEL_OFF)
return false; // MIDI Input disabled.
if (parse() && inputFilter(inChannel))
if (parse())
{
if (inputFilter(inChannel))
{
#if (MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU)
thruFilter(inChannel);
#endif
#if MIDI_USE_CALLBACKS
launchCallback();
#endif
return true;
}
}
return false;
}
@ -485,7 +508,7 @@ bool MidiInterface<SerialPort>::parse()
// - Look for other bytes in buffer, call parser recursively,
// until the message is assembled or the buffer is empty.
// Else, add the extracted byte to the pending message, and check validity.
// When the message is recomposed, notify the caller.
// When the message is done, store it.
const byte extracted = mSerial.read();
@ -530,16 +553,17 @@ bool MidiInterface<SerialPort>::parse()
}
}
mPendingMessageExpectedLenght = getMessageLength(mPendingMessage[0]);
if (mPendingMessageExpectedLenght == 1)
switch (getTypeFromStatusByte(mPendingMessage[0]))
{
// RealTime and 1 byte messages -> send & handle rightaway
if (shouldMessageBeForwarded(mPendingMessage[0]))
{
mSerial.write(mPendingMessage[0]);
}
// 1 byte messages
case Start:
case Continue:
case Stop:
case Clock:
case ActiveSensing:
case SystemReset:
case TuneRequest:
// Handle the message type directly here.
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
mMessage.channel = 0;
mMessage.data1 = 0;
@ -555,32 +579,45 @@ bool MidiInterface<SerialPort>::parse()
mPendingMessageExpectedLenght = 0;
return true;
}
else if (mPendingMessageExpectedLenght == 0xff)
{
// SysEx: The message can be any length
break;
// 2 bytes messages
case ProgramChange:
case AfterTouchChannel:
case TimeCodeQuarterFrame:
case SongSelect:
mPendingMessageExpectedLenght = 2;
break;
// 3 bytes messages
case NoteOn:
case NoteOff:
case ControlChange:
case PitchBend:
case AfterTouchPoly:
case SongPosition:
mPendingMessageExpectedLenght = 3;
break;
case SystemExclusive:
// The message can be any lenght
// between 3 and MIDI_SYSEX_ARRAY_SIZE bytes
mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE;
mRunningStatus_RX = InvalidType;
mMessage.sysexArray[0] = SystemExclusive;
break;
}
else if (mPendingMessageExpectedLenght == 0)
{
// Parse error
case InvalidType:
default:
// This is obviously wrong. Let's get the hell out'a here.
resetInput();
return false;
break;
}
// Then update the index of the pending message.
mPendingMessageIndex++;
if (shouldMessageBeForwarded(mPendingMessage[0]))
{
mSerial.write(mPendingMessage[0]);
}
#if USE_1BYTE_PARSING
// Message is not complete.
return false;
@ -589,6 +626,7 @@ bool MidiInterface<SerialPort>::parse()
// to parse the rest of the message.
return parse();
#endif
}
else
{
@ -597,12 +635,14 @@ bool MidiInterface<SerialPort>::parse()
{
// Reception of status bytes in the middle of an uncompleted message
// are allowed only for interleaved Real Time message or EOX
if (isSystemRealtimeMessage(getTypeFromStatusByte(extracted)))
switch (extracted)
{
if (shouldMessageBeForwarded(extracted))
{
mSerial.write(extracted);
}
case Clock:
case Start:
case Continue:
case Stop:
case ActiveSensing:
case SystemReset:
// Here we will have to extract the one-byte message,
// pass it to the structure for being read outside
@ -617,16 +657,13 @@ bool MidiInterface<SerialPort>::parse()
mMessage.channel = 0;
mMessage.valid = true;
return true;
}
else if (extracted == 0xF7)
{
break;
// End of Exclusive
case 0xF7:
if (mMessage.sysexArray[0] == SystemExclusive)
{
if (shouldMessageBeForwarded(SystemExclusive))
{
mSerial.write(extracted);
}
// Store the last byte (EOX)
mMessage.sysexArray[mPendingMessageIndex++] = 0xF7;
@ -641,27 +678,24 @@ bool MidiInterface<SerialPort>::parse()
resetInput();
return true;
}
}
else
{
// Parse error: unexpected status byte
// Well well well.. error.
resetInput();
return false;
}
break;
default:
break;
}
}
// Add extracted data byte to pending message
if (mPendingMessage[0] == SystemExclusive)
{
mMessage.sysexArray[mPendingMessageIndex] = extracted;
}
else
{
mPendingMessage[mPendingMessageIndex] = extracted;
}
if (shouldMessageBeForwarded(mPendingMessage[0]))
{
mSerial.write(extracted);
}
// Now we are going to check if we have reached the end of the message
if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1))
@ -743,18 +777,31 @@ bool MidiInterface<SerialPort>::inputFilter(Channel inChannel)
{
// This method handles recognition of channel
// (to know if the message is destinated to the Arduino)
if (mMessage.type == InvalidType)
return false;
if (isChannelMessage(mMessage.type))
// First, check if the received message is Channel
if (mMessage.type >= NoteOff && mMessage.type <= PitchBend)
{
// Then we need to know if we listen to it
if ((mMessage.channel == mInputChannel) ||
(mInputChannel == MIDI_CHANNEL_OMNI))
{
return (mInputChannel == MIDI_CHANNEL_OMNI ||
mInputChannel == mMessage.channel);
}
// Other message types are always received.
return true;
}
else
{
// We don't listen to this channel
return false;
}
}
else
{
// System messages are always received
return true;
}
}
// Private method: reset input attributes
template<class SerialPort>
@ -812,15 +859,15 @@ const byte* MidiInterface<SerialPort>::getSysExArray() const
return mMessage.sysexArray;
}
/*! \brief Get the length of the System Exclusive array.
/*! \brief Get the lenght of the System Exclusive array.
It is coded using data1 as LSB and data2 as MSB.
\return The array's length, in bytes.
*/
template<class SerialPort>
unsigned MidiInterface<SerialPort>::getSysExArrayLength() const
unsigned int MidiInterface<SerialPort>::getSysExArrayLength() const
{
const unsigned size = ((unsigned)(mMessage.data2) << 8) | mMessage.data1;
const unsigned int size = ((unsigned)(mMessage.data2) << 8) | mMessage.data1;
return (size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : size;
}
@ -857,7 +904,7 @@ void MidiInterface<SerialPort>::setInputChannel(Channel inChannel)
made public so you can handle MidiTypes more easily.
*/
template<class SerialPort>
MidiType MidiInterface<SerialPort>::getTypeFromStatusByte(StatusByte inStatus)
MidiType MidiInterface<SerialPort>::getTypeFromStatusByte(byte inStatus)
{
if ((inStatus < 0x80) ||
(inStatus == 0xF4) ||
@ -868,38 +915,6 @@ MidiType MidiInterface<SerialPort>::getTypeFromStatusByte(StatusByte inStatus)
else return (MidiType)inStatus;
}
template<class SerialPort>
byte MidiInterface<SerialPort>::getMessageLength(StatusByte inStatus)
{
const MidiType type = getTypeFromStatusByte(inStatus);
if (isSystemRealtimeMessage(type) || type == TuneRequest)
{
return 1;
}
else if (type == NoteOn ||
type == NoteOff ||
type == ControlChange ||
type == PitchBend ||
type == AfterTouchPoly ||
type == SongPosition)
{
return 3;
}
else if (type == ProgramChange ||
type == AfterTouchChannel ||
type == TimeCodeQuarterFrame ||
type == SongSelect)
{
return 2;
}
else if (type == SystemExclusive)
{
return 0xff; // SysEx messages can have variable length
}
return 0; // Unknown message type
}
template<class SerialPort>
bool MidiInterface<SerialPort>::isChannelMessage(MidiType inType)
{
@ -912,26 +927,6 @@ bool MidiInterface<SerialPort>::isChannelMessage(MidiType inType)
inType == ProgramChange);
}
template<class SerialPort>
bool MidiInterface<SerialPort>::isSystemRealtimeMessage(MidiType inType)
{
return inType == Clock ||
inType == Start ||
inType == Stop ||
inType == Continue ||
inType == ActiveSensing ||
inType == SystemReset;
}
template<class SerialPort>
bool MidiInterface<SerialPort>::isSystemCommonMessage(MidiType inType)
{
return inType == TimeCodeQuarterFrame ||
inType == SongPosition ||
inType == SongSelect ||
inType == TuneRequest;
}
// -----------------------------------------------------------------------------
#if MIDI_USE_CALLBACKS
@ -949,7 +944,7 @@ template<class SerialPort> void MidiInterface<SerialPort>::setHandleAfterTouchCh
template<class SerialPort> void MidiInterface<SerialPort>::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; }
template<class SerialPort> void MidiInterface<SerialPort>::setHandleSystemExclusive(void (*fptr)(byte* array, byte size)) { mSystemExclusiveCallback = fptr; }
template<class SerialPort> void MidiInterface<SerialPort>::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; }
template<class SerialPort> void MidiInterface<SerialPort>::setHandleSongPosition(void (*fptr)(unsigned beats)) { mSongPositionCallback = fptr; }
template<class SerialPort> void MidiInterface<SerialPort>::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; }
template<class SerialPort> void MidiInterface<SerialPort>::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; }
template<class SerialPort> void MidiInterface<SerialPort>::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; }
template<class SerialPort> void MidiInterface<SerialPort>::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; }
@ -1046,84 +1041,160 @@ void MidiInterface<SerialPort>::launchCallback()
// Thru
// -----------------------------------------------------------------------------
#if MIDI_BUILD_THRU
#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU)
/*! \addtogroup thru
@{
*/
/*! \brief Set the filter for thru mirroring
\param inThruFilterMode a filter mode
@see MidiFilterMode
*/
template<class SerialPort>
void MidiInterface<SerialPort>::turnThruOn(ThruFlags inFlags)
void MidiInterface<SerialPort>::setThruFilterMode(MidiFilterMode inThruFilterMode)
{
mThruFlags = inFlags;
mThruFilterMode = inThruFilterMode;
if (mThruFilterMode != Off)
mThruActivated = true;
else
mThruActivated = false;
}
template<class SerialPort>
MidiFilterMode MidiInterface<SerialPort>::getFilterMode() const
{
return mThruFilterMode;
}
template<class SerialPort>
bool MidiInterface<SerialPort>::getThruState() const
{
return mThruActivated;
}
template<class SerialPort>
void MidiInterface<SerialPort>::turnThruOn(MidiFilterMode inThruFilterMode)
{
mThruActivated = true;
mThruFilterMode = inThruFilterMode;
}
template<class SerialPort>
void MidiInterface<SerialPort>::turnThruOff()
{
mThruFlags = ThruFilterFlags::off;
}
/*! \brief Set the filter for thru mirroring
\param inThruFilterMode a filter mode
@see ThruFilterFlags
*/
template<class SerialPort>
void MidiInterface<SerialPort>::setThruFilterMode(ThruFlags inFlags)
{
mThruFlags = inFlags;
}
template<class SerialPort>
ThruFlags MidiInterface<SerialPort>::getThruFilterMode() const
{
return mThruFlags;
}
template<class SerialPort>
bool MidiInterface<SerialPort>::isThruOn() const
{
return mThruFlags != ThruFilterFlags::off;
}
template<class SerialPort>
bool MidiInterface<SerialPort>::hasThruFlag(byte inFlag) const
{
return mThruFlags & inFlag;
mThruActivated = false;
mThruFilterMode = Off;
}
/*! @} */ // End of doc group MIDI Thru
// -----------------------------------------------------------------------------
// This method is called upon reception of a message
// and takes care of Thru filtering and sending.
template<class SerialPort>
bool MidiInterface<SerialPort>::shouldMessageBeForwarded(StatusByte inStatus) const
void MidiInterface<SerialPort>::thruFilter(Channel inChannel)
{
const MidiType type = getTypeFromStatusByte(inStatus);
if (isChannelMessage(type))
/*
This method handles Soft-Thru filtering.
Soft-Thru filtering:
- All system messages (System Exclusive, Common and Real Time) are passed to output unless filter is set to Off
- Channel messages are passed to the output whether their channel is matching the input channel and the filter setting
*/
// If the feature is disabled, don't do anything.
if (!mThruActivated || (mThruFilterMode == Off))
return;
// First, check if the received message is Channel
if (mMessage.type >= NoteOff && mMessage.type <= PitchBend)
{
const Channel channel = (inStatus & 0x0F) + 1;
const bool forwardSame = hasThruFlag(ThruFilterFlags::channelSame);
const bool forwardDiff = hasThruFlag(ThruFilterFlags::channelDifferent);
return (forwardSame && mInputChannel == channel) ||
(forwardDiff && mInputChannel != channel);
const bool filter_condition = ((mMessage.channel == mInputChannel) ||
(mInputChannel == MIDI_CHANNEL_OMNI));
// Now let's pass it to the output
switch (mThruFilterMode)
{
case Full:
send(mMessage.type,
mMessage.data1,
mMessage.data2,
mMessage.channel);
return;
break;
case SameChannel:
if (filter_condition)
{
send(mMessage.type,
mMessage.data1,
mMessage.data2,
mMessage.channel);
return;
}
else if (isSystemRealtimeMessage(type))
break;
case DifferentChannel:
if (!filter_condition)
{
return hasThruFlag(ThruFilterFlags::systemRealtime);
send(mMessage.type,
mMessage.data1,
mMessage.data2,
mMessage.channel);
return;
}
else if (isSystemCommonMessage(type))
{
return hasThruFlag(ThruFilterFlags::systemCommon);
break;
case Off:
// Do nothing.
// Technically it's impossible to get there because
// the case was already tested earlier.
break;
default:
break;
}
else if (type == SystemExclusive)
{
return hasThruFlag(ThruFilterFlags::systemExclusive);
}
else
{
// Unknown message or junk
return hasThruFlag(ThruFilterFlags::junk);
// Send the message to the output
switch (mMessage.type)
{
// Real Time and 1 byte
case Clock:
case Start:
case Stop:
case Continue:
case ActiveSensing:
case SystemReset:
case TuneRequest:
sendRealTime(mMessage.type);
return;
break;
case SystemExclusive:
// Send SysEx (0xF0 and 0xF7 are included in the buffer)
sendSysEx(mMessage.data1,mMessage.sysexArray,true);
return;
break;
case SongSelect:
sendSongSelect(mMessage.data1);
return;
break;
case SongPosition:
sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2<<7));
return;
break;
case TimeCodeQuarterFrame:
sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2);
return;
break;
default:
break;
}
}
}

View File

@ -61,30 +61,14 @@ enum MidiType
// -----------------------------------------------------------------------------
/*! Enumeration of Thru filter modes
@see setThruFilterMode
*/
struct ThruFilterFlags
/*! Enumeration of Thru filter modes */
enum MidiFilterMode
{
enum
{
off = 0 ///< Thru disabled (nothing passes through).
, channelSame = (1 << 0) ///< Only the messages on the Input Channel will be sent back.
, channelDifferent = (1 << 1) ///< All the messages but the ones on the Input Channel will be sent back.
, channel = channelSame | channelDifferent
, systemExclusive = (1 << 2)
, systemCommon = (1 << 3)
, systemRealtime = (1 << 4)
, system = systemExclusive | systemCommon | systemRealtime
, junk = (1 << 7) ///< Send mis-formated data back (unadvisable)
/// Fully enabled Thru (every incoming message is sent back).
, all = channel | systemExclusive | systemCommon | systemRealtime
Off = 0, ///< Thru disabled (nothing passes through).
Full = 1, ///< Fully enabled Thru (every incoming message is sent back).
SameChannel = 2, ///< Only the messages on the Input Channel will be sent back.
DifferentChannel = 3, ///< All the messages but the ones on the Input Channel will be sent back.
};
};
typedef byte ThruFlags;
// -----------------------------------------------------------------------------