diff --git a/examples/midi/midi-bytes-receive.ck b/examples/midi/midi-bytes-receive.ck new file mode 100644 index 000000000..1ee7a7269 --- /dev/null +++ b/examples/midi/midi-bytes-receive.ck @@ -0,0 +1,19 @@ +MidiIn min; + +if (!min.open(0)) { + me.exit(); +} + +int bytes[0]; + +while (min => now) { + while (min.recv(bytes)) { + chout <= "bytes: "; + + for (auto byte : bytes) { + chout <= byte <= " "; + } + + chout <= IO.newline(); + } +} diff --git a/examples/midi/midi-bytes-send.ck b/examples/midi/midi-bytes-send.ck new file mode 100644 index 000000000..82eb52cea --- /dev/null +++ b/examples/midi/midi-bytes-send.ck @@ -0,0 +1,10 @@ +MidiOut mout; + +if (!mout.open(0)) { + me.exit(); +} + +// Full frame midi time code (MTC) message. +// SMPTE timecode: 01:02:03:04.20 (1 hour, 2 minutes, 3 seconds, 4 frames, 20 subframes). +// F0 7F 7F 01 01 01 02 03 04 14 F7 +mout.send([ 0xF0, 0x7F, 0x7F, 0x01, 0x01, 0x01, 0x02, 0x03, 0x04, 0x14, 0xF7 ]); diff --git a/src/core/chuck_io.cpp b/src/core/chuck_io.cpp index 7df75e120..0b62cb356 100644 --- a/src/core/chuck_io.cpp +++ b/src/core/chuck_io.cpp @@ -807,6 +807,14 @@ t_CKBOOL init_class_Midi( Chuck_Env * env ) func->doc = "Return into the MidiMsg argument the next message in the queue from the device. Return 0 if the queue is empty or 1 if a message was in the queue and returned in the argument."; if( !type_engine_import_mfun( env, func ) ) goto error; + + // add recv() + func = make_new_mfun( "int", "recv", MidiIn_recv_bytes ); + func->add_arg( "int[]", "bytes" ); + func->doc = "Return into the int[] argument the next message in the queue from the device. Return 0 if the queue is empty or 1 if a message was in the queue and returned in the argument."; + if( !type_engine_import_mfun( env, func ) ) goto error; + + // add can_wait() func = make_new_mfun( "int", "can_wait", MidiIn_can_wait ); func->doc = "(internal) used by virtual machine for synthronization."; @@ -880,6 +888,13 @@ t_CKBOOL init_class_Midi( Chuck_Env * env ) func->doc = "Send out a MIDI message using a MidiMsg."; if( !type_engine_import_mfun( env, func ) ) goto error; + // add send() + func = make_new_mfun( "int", "send", MidiOut_send_bytes ); + func->add_arg( "int[]", "data" ); + func->doc = "Send out a MIDI message using a MidiMsg."; + if( !type_engine_import_mfun( env, func ) ) goto error; + + // add noteOn() | 1.5.2.5 (cviejo) added func = make_new_mfun( "int", "noteOn", MidiOut_noteOn ); func->add_arg( "int", "channel" ); @@ -2482,6 +2497,19 @@ CK_DLL_MFUN( MidiIn_recv ) } } +CK_DLL_MFUN( MidiIn_recv_bytes ) +{ + MidiIn * min = (MidiIn *)OBJ_MEMBER_INT(SELF, MidiIn_offset_data); + Chuck_ArrayInt * bytes = (Chuck_ArrayInt *)GET_NEXT_OBJECT(ARGS); + if( bytes == NULL ) + { + RETURN->v_int = 0; + } + else + { + RETURN->v_int = min->recv( bytes ); + } +} CK_DLL_MFUN( MidiIn_can_wait ) { @@ -2568,6 +2596,15 @@ CK_DLL_MFUN( MidiOut_send_msg ) RETURN->v_int = mout->send( &the_msg ); } + +CK_DLL_MFUN( MidiOut_send_bytes ) +{ + MidiOut * mout = (MidiOut *)OBJ_MEMBER_INT(SELF, MidiOut_offset_data); + Chuck_ArrayInt * arr = (Chuck_ArrayInt *) GET_NEXT_OBJECT(ARGS); + RETURN->v_int = mout->send( arr ); +} + + CK_DLL_MFUN( MidiOut_noteOn ) // 1.5.2.5 (cviejo) added { MidiOut * mout = (MidiOut *)OBJ_MEMBER_INT(SELF, MidiOut_offset_data); diff --git a/src/core/chuck_io.h b/src/core/chuck_io.h index 13b04d83b..4e90c6736 100644 --- a/src/core/chuck_io.h +++ b/src/core/chuck_io.h @@ -527,6 +527,7 @@ CK_DLL_MFUN( MidiIn_num ); CK_DLL_MFUN( MidiIn_name ); CK_DLL_MFUN( MidiIn_printerr ); CK_DLL_MFUN( MidiIn_recv ); +CK_DLL_MFUN( MidiIn_recv_bytes ); CK_DLL_MFUN( MidiIn_can_wait ); @@ -544,6 +545,7 @@ CK_DLL_MFUN( MidiOut_name ); CK_DLL_MFUN( MidiOut_printerr ); CK_DLL_MFUN( MidiOut_send ); CK_DLL_MFUN( MidiOut_send_msg ); +CK_DLL_MFUN( MidiOut_send_bytes ); CK_DLL_MFUN( MidiOut_noteOn ); CK_DLL_MFUN( MidiOut_noteOff ); CK_DLL_MFUN( MidiOut_controlChange ); diff --git a/src/core/midiio_rtmidi.cpp b/src/core/midiio_rtmidi.cpp index 262925aeb..ee13bfda3 100644 --- a/src/core/midiio_rtmidi.cpp +++ b/src/core/midiio_rtmidi.cpp @@ -50,7 +50,7 @@ #define MIDI_BUFFER_SIZE 8192 std::vector MidiInManager::the_mins; -std::vector< std::map< Chuck_VM *, CBufferAdvance * > > MidiInManager::the_bufs; +std::vector< std::map< Chuck_VM *, CBufferAdvanceVariable * > > MidiInManager::the_bufs; std::vector MidiOutManager::the_mouts; std::map< Chuck_VM *, CBufferSimple * > MidiInManager::m_event_buffers; @@ -85,6 +85,32 @@ MidiOut::~MidiOut() +//----------------------------------------------------------------------------- +// name: send() +// desc: send an array of midi bytes +//----------------------------------------------------------------------------- +t_CKUINT MidiOut::send( Chuck_ArrayInt * arr ) +{ + if( !m_valid ) return 0; + + t_CKINT length = arr->size(); + t_CKUINT byte; + + m_msg.clear(); + + for( int i = 0; i < length; i++ ) + { + arr->get(i, &byte); + m_msg.push_back( byte ); + } + mout->sendMessage( &m_msg ); + + return length; +} + + + + //----------------------------------------------------------------------------- // name: send() // desc: send 1 byte midi message @@ -543,8 +569,9 @@ t_CKBOOL MidiInManager::add_vm( Chuck_VM * vm, t_CKINT device_num, } // allocate the buffer - CBufferAdvance * cbuf = new CBufferAdvance; - if( !cbuf->initialize( MIDI_BUFFER_SIZE, sizeof(MidiMsg), m_event_buffers[vm] ) ) + CBufferAdvanceVariable * cbuf = new CBufferAdvanceVariable; + // buffer size with an estimate of 3 bytes per message + if( !cbuf->initialize( MIDI_BUFFER_SIZE * 3, m_event_buffers[vm] ) ) { if( !suppress_output ) EM_error2( 0, "MidiIn: couldn't allocate CBuffer for port %i...", device_num ); @@ -620,13 +647,58 @@ t_CKBOOL MidiIn::empty() //----------------------------------------------------------------------------- -// name: get() -// desc: get message +// name: recv() +// desc: get a 3-byte midi message //----------------------------------------------------------------------------- t_CKUINT MidiIn::recv( MidiMsg * msg ) { if( !m_valid ) return FALSE; - return m_buffer->get( msg, 1, m_read_index ); + + t_CKUINT size = m_buffer->getNextSize(m_read_index); + + if( size == 0 ) return size; + + // MSVC doesn't play well with VLAs, don't use t_CKBYTE tmp[size] here + std::vector tmp(size, 0); + + m_buffer->get(tmp.data(), m_read_index); + + for (t_CKUINT i = 0; i < size && i < 3; i++) + { + msg->data[i] = tmp[i]; + } + + return size; +} + + + + +//----------------------------------------------------------------------------- +// name: recv() +// desc: get a midi message of any byte length +//----------------------------------------------------------------------------- +t_CKUINT MidiIn::recv( Chuck_ArrayInt * arr ) +{ + if( !m_valid ) return FALSE; + + t_CKUINT size = m_buffer->getNextSize(m_read_index); + + if( size == 0 ) return 0; + + std::vector tmp(size, 0); + + // don't pass arr->m_vector.data() directly, that holds longs + m_buffer->get(tmp.data(), m_read_index); + + arr->clear(); + + for (t_CKUINT i = 0; i < size; i++) + { + arr->push_back(tmp[i]); + } + + return size; } @@ -651,21 +723,18 @@ void MidiInManager::cb_midi_input( double deltatime, std::vector EM_error2( 0, "MidiIn: couldn't find buffers for port %i...", device_num ); return; } - MidiMsg m; - if( nBytes >= 1 ) m.data[0] = msg->at(0); - if( nBytes >= 2 ) m.data[1] = msg->at(1); - if( nBytes >= 3 ) m.data[2] = msg->at(2); // put in all the buffers, make sure not active sensing - if( m.data[2] != 0xfe ) + if( msg->back() != 0xfe ) { - for( std::map< Chuck_VM *, CBufferAdvance * >::iterator it = + for( std::map< Chuck_VM *, CBufferAdvanceVariable * >::iterator it = the_bufs[device_num].begin(); it != the_bufs[device_num].end(); it++ ) { - CBufferAdvance * cbuf = it->second; + CBufferAdvanceVariable * cbuf = it->second; + if( cbuf != NULL ) { - cbuf->put( &m, 1 ); + cbuf->put( msg->data(), msg->size() ); } } } diff --git a/src/core/midiio_rtmidi.h b/src/core/midiio_rtmidi.h index b487f2d59..1f286a4a0 100644 --- a/src/core/midiio_rtmidi.h +++ b/src/core/midiio_rtmidi.h @@ -101,9 +101,11 @@ class MidiOut { return m_suppress_output; } public: + t_CKUINT send( t_CKBYTE status ); t_CKUINT send( t_CKBYTE status, t_CKBYTE data1 ); t_CKUINT send( t_CKBYTE status, t_CKBYTE data1, t_CKBYTE data2 ); + t_CKUINT send( Chuck_ArrayInt * arr ); t_CKUINT send( const MidiMsg * msg ); public: @@ -154,9 +156,10 @@ class MidiIn : public Chuck_Event public: t_CKBOOL empty(); t_CKUINT recv( MidiMsg * msg ); + t_CKUINT recv( Chuck_ArrayInt * msg ); public: - CBufferAdvance * m_buffer; + CBufferAdvanceVariable * m_buffer; t_CKUINT m_read_index; RtMidiIn * min; t_CKBOOL m_valid; @@ -193,7 +196,7 @@ class MidiInManager static t_CKBOOL add_vm( Chuck_VM * vm, t_CKINT device_num, t_CKBOOL suppress_output ); static std::vector the_mins; - static std::vector< std::map< Chuck_VM *, CBufferAdvance * > > the_bufs; + static std::vector< std::map< Chuck_VM *, CBufferAdvanceVariable * > > the_bufs; public: static std::map< Chuck_VM *, CBufferSimple * > m_event_buffers; diff --git a/src/core/util_buffers.cpp b/src/core/util_buffers.cpp index 789c3ee8a..912187d6e 100644 --- a/src/core/util_buffers.cpp +++ b/src/core/util_buffers.cpp @@ -315,6 +315,29 @@ BOOL__ CBufferAdvance::empty( UINT__ read_offset_index ) } +BOOL__ CBufferAdvance::isValidIndex( UINT__ read_offset_index ) +{ + if( read_offset_index >= m_read_offsets.size() ) + { + return FALSE; + } + + SINT__ m_read_offset = m_read_offsets[read_offset_index].read_offset; + + if( m_read_offset < 0 ) + { + return FALSE; + } + + if( m_read_offset == m_write_offset ) + { + return FALSE; + } + + return TRUE; +} + + UINT__ CBufferAdvance::get( void * data, UINT__ num_elem, UINT__ read_offset_index ) { UINT__ i, j; @@ -325,15 +348,7 @@ UINT__ CBufferAdvance::get( void * data, UINT__ num_elem, UINT__ read_offset_ind m_mutex.acquire(); #endif - // make sure index is valid - if( read_offset_index >= m_read_offsets.size() ) - { - #ifndef __DISABLE_THREADS__ - m_mutex.release(); - #endif - return 0; - } - if( m_read_offsets[read_offset_index].read_offset < 0 ) + if (!this->isValidIndex(read_offset_index)) { #ifndef __DISABLE_THREADS__ m_mutex.release(); @@ -343,15 +358,6 @@ UINT__ CBufferAdvance::get( void * data, UINT__ num_elem, UINT__ read_offset_ind SINT__ m_read_offset = m_read_offsets[read_offset_index].read_offset; - // read catch up with write - if( m_read_offset == m_write_offset ) - { - #ifndef __DISABLE_THREADS__ - m_mutex.release(); - #endif - return 0; - } - // copy for( i = 0; i < num_elem; i++ ) { @@ -387,6 +393,165 @@ UINT__ CBufferAdvance::get( void * data, UINT__ num_elem, UINT__ read_offset_ind } +BOOL__ CBufferAdvanceVariable::initialize( UINT__ buffer_size, CBufferSimple * event_buffer ) +{ + CBufferAdvance::initialize( buffer_size, 1, event_buffer ); + m_buffer_size = buffer_size; + return true; +} + + +UINT__ CBufferAdvanceVariable::get( void * data, UINT__ read_offset_index ) +{ + UINT__ i; + BYTE__ * d = (BYTE__ *)data; + + #ifndef __DISABLE_THREADS__ + m_mutex.acquire(); + #endif + + if ( !this->isValidIndex(read_offset_index) ) + { + #ifndef __DISABLE_THREADS__ + m_mutex.release(); + #endif + return 0; + } + + SINT__ read_offset = m_read_offsets[read_offset_index].read_offset; + UINT__ size = m_data[read_offset]; + + for( i = 0; i < size; i++ ) + { + read_offset = advanceIndex( read_offset ); + d[i] = m_data[read_offset]; + } + read_offset = advanceIndex( read_offset ); + + m_read_offsets[read_offset_index].read_offset = read_offset; + + #ifndef __DISABLE_THREADS__ + m_mutex.release(); + #endif + + return size; +} + + +void CBufferAdvanceVariable::put( void * data, UINT__ size ) +{ + if( size == 0 ) return; + + UINT__ i; + BYTE__ * d = (BYTE__ *)data; + + #ifndef __DISABLE_THREADS__ + m_mutex.acquire(); + #endif + + // not enough space, drop the message. this is an edge case, will probably never happen + if( !hasSpace(size) ) + { + #ifndef __DISABLE_THREADS__ + m_mutex.release(); + #endif + return; + } + + // store the size of the message + m_data[m_write_offset] = size; + + for( i = 0; i < size; i++ ) + { + m_write_offset = advanceIndex( m_write_offset ); + m_data[m_write_offset] = d[i]; + } + m_write_offset = advanceIndex( m_write_offset ); + + for( i = 0; i < m_read_offsets.size(); i++ ) + { + if( m_read_offsets[i].event ) + { + m_read_offsets[i].event->queue_broadcast( m_event_buffer ); + } + } + + #ifndef __DISABLE_THREADS__ + m_mutex.release(); + #endif +} + + +void CBufferAdvanceVariable::cleanup() +{ + CBufferAdvance::cleanup(); + m_buffer_size = 0; +} + + +//----------------------------------------------------------------------------- +// name: getNextSize() +// desc: get the size of the next message so we can allocate space for it +// before calling get() +//----------------------------------------------------------------------------- +UINT__ CBufferAdvanceVariable::getNextSize( UINT__ read_offset_index ) +{ + SINT__ read_offset = m_read_offsets[read_offset_index].read_offset; + + if( read_offset == m_write_offset ) + { + return 0; + } + + return m_data[read_offset]; +} + + +//----------------------------------------------------------------------------- +// name: advanceIndex() +// desc: advance the index, wrapping around if necessary +//----------------------------------------------------------------------------- +UINT__ CBufferAdvanceVariable::advanceIndex( UINT__ offset_index ) +{ + offset_index++; + + if( offset_index >= m_buffer_size ) + { + offset_index = 0; + } + + return offset_index; +} + + +//----------------------------------------------------------------------------- +// name: hasSpace() +// desc: covers an edge case where too many messages have been put into the +// buffer without being read. We check all the write positions to see if +// they are already occupied by a read position. +//----------------------------------------------------------------------------- +BOOL__ CBufferAdvanceVariable::hasSpace(UINT__ size) +{ + UINT__ i, j; + // current position we can advance without checking, + // it's the size of the next message + UINT__ pos = advanceIndex( m_write_offset ); + + for( i = 0; i < size; i++ ) + { + pos = advanceIndex( m_write_offset ); + + for( j = 0; j < m_read_offsets.size(); j++ ) + { + if( pos == m_read_offsets[j].read_offset ) + { + return FALSE; + } + } + } + + return TRUE; +} //----------------------------------------------------------------------------- diff --git a/src/core/util_buffers.h b/src/core/util_buffers.h index 32374aa67..efe68a21f 100644 --- a/src/core/util_buffers.h +++ b/src/core/util_buffers.h @@ -83,6 +83,7 @@ class CBufferAdvance void resign( UINT__ read_offset_index ); protected: + BOOL__ isValidIndex( UINT__ read_offset_index ); BYTE__ * m_data; UINT__ m_data_width; //UINT__ m_read_offset; @@ -110,7 +111,27 @@ class CBufferAdvance CBufferSimple * m_event_buffer; }; +//----------------------------------------------------------------------------- +// name: class CBufferAdvanceVariable +// desc: circular buffer with variable length elements - first byte of each +// element is the size of the element +//----------------------------------------------------------------------------- +class CBufferAdvanceVariable : public CBufferAdvance +{ +public: + BOOL__ initialize( UINT__ buffer_size, CBufferSimple * event_buffer = NULL ); + UINT__ getNextSize( UINT__ read_offset_index ); + UINT__ get( void * data, UINT__ read_offset_index ); + void put( void * data, UINT__ size ); + void cleanup(); +protected: + UINT__ m_buffer_size; + +private: + UINT__ advanceIndex( UINT__ offset_index ); + BOOL__ hasSpace( UINT__ size ); +}; //-----------------------------------------------------------------------------