Merge branch 'master' of gitea.larke.org:kevin/libcw
This commit is contained in:
commit
021a1c2808
15
Makefile.am
15
Makefile.am
@ -7,6 +7,9 @@ libcwSRC += src/libcw/cwCommonImpl.cpp src/libcw/cwMem.cpp
|
||||
libcwHDR += src/libcw/cwString.h src/libcw/cwMath.h src/libcw/cwVectOps.h src/libcw/cwMtx.h src/libcw/cwVariant.h
|
||||
libcwSRC += src/libcw/cwString.cpp src/libcw/cwMath.cpp src/libcw/cwMtx.cpp src/libcw/cwVariant.cpp
|
||||
|
||||
libcwHDR += src/libcw/cwB23Tree.h
|
||||
libcwSRC += src/libcw/cwB23Tree.cpp
|
||||
|
||||
libcwHDR += src/libcw/cwFileSys.h src/libcw/cwText.h src/libcw/cwFile.h src/libcw/cwTime.h src/libcw/cwLex.h src/libcw/cwNumericConvert.h
|
||||
libcwSRC += src/libcw/cwFileSys.cpp src/libcw/cwText.cpp src/libcw/cwFile.cpp src/libcw/cwTime.cpp src/libcw/cwLex.cpp
|
||||
|
||||
@ -28,8 +31,8 @@ libcwSRC += src/libcw/cwAudioFile.cpp src/libcw/cwMidiFile.cpp
|
||||
libcwHDR += src/libcw/cwAudioFileOps.h src/libcw/cwAudioTransforms.h src/libcw/cwDspTransforms.h src/libcw/cwAudioFileProc.h src/libcw/cwPvAudioFileProc.h
|
||||
libcwSRC += src/libcw/cwAudioFileOps.cpp src/libcw/cwAudioTransforms.cpp src/libcw/cwDspTransforms.cpp src/libcw/cwAudioFileProc.cpp src/libcw/cwPvAudioFileProc.cpp
|
||||
|
||||
libcwHDR += src/libcw/cwFlow.h src/libcw/cwFlowTypes.h src/libcw/cwFlowProc.h src/libcw/cwFlowCross.h
|
||||
libcwSRC += src/libcw/cwFlow.cpp src/libcw/cwFlowTypes.cpp src/libcw/cwFlowProc.cpp src/libcw/cwFlowCross.cpp
|
||||
libcwHDR += src/libcw/cwFlow.h src/libcw/cwFlowDecl.h src/libcw/cwFlowTypes.h src/libcw/cwFlowProc.h src/libcw/cwFlowCross.h
|
||||
libcwSRC += src/libcw/cwFlow.cpp src/libcw/cwFlowTypes.cpp src/libcw/cwFlowProc.cpp src/libcw/cwFlowCross.cpp
|
||||
|
||||
if cwWEBSOCK
|
||||
libcwHDR += src/libcw/cwWebSock.h src/libcw/cwWebSockSvr.h
|
||||
@ -54,8 +57,12 @@ libcwSRC += src/libcw/cwAudioDevice.cpp
|
||||
|
||||
if cwALSA
|
||||
|
||||
libcwHDR += src/libcw/cwMidiPort.h src/libcw/cwAudioDeviceAlsa.h src/libcw/cwAudioDeviceFile.h
|
||||
libcwSRC += src/libcw/cwMidiPort.cpp src/libcw/cwMidiAlsa.cpp src/libcw/cwAudioDeviceAlsa.cpp src/libcw/cwAudioDeviceFile.cpp src/libcw/cwAudioDeviceTest.cpp
|
||||
libcwHDR += src/libcw/cwMidiDevice.h src/libcw/cwMidiParser.h src/libcw/cwMidiAlsa.h src/libcw/cwMidiFileDev.h src/libcw/cwMidiDeviceTest.h
|
||||
libcwSRC += src/libcw/cwMidiDevice.cpp src/libcw/cwMidiParser.cpp src/libcw/cwMidiAlsa.cpp src/libcw/cwMidiFileDev.cpp src/libcw/cwMidiDeviceTest.cpp
|
||||
|
||||
libcwHDR += src/libcw/cwAudioDeviceAlsa.h src/libcw/cwAudioDeviceFile.h
|
||||
libcwSRC += src/libcw/cwAudioDeviceAlsa.cpp src/libcw/cwAudioDeviceFile.cpp src/libcw/cwAudioDeviceTest.cpp
|
||||
|
||||
|
||||
if cwWEBSOCK
|
||||
libcwHDR += src/libcw/cwIo.h src/libcw/cwIoTest.h src/libcw/cwIoMinTest.h src/libcw/cwIoSocketChat.h src/libcw/cwIoAudioPanel.h src/libcw/cwIoAudioMidi.h
|
||||
|
@ -1368,7 +1368,7 @@ cw::rc_t cw::audio::device::alsa::create( handle_t& hRef, struct driver_str*& dr
|
||||
p->pollfds = mem::allocZ<struct pollfd>( p->pollfdsAllocCnt );
|
||||
p->pollfdsDesc = mem::allocZ<pollfdsDesc_t>(p->pollfdsAllocCnt );
|
||||
|
||||
if((rc = thread::create(p->thH,_threadFunc,p)) != kOkRC )
|
||||
if((rc = thread::create(p->thH,_threadFunc,p,"alsa_audio")) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Thread create failed.");
|
||||
}
|
||||
|
@ -664,7 +664,7 @@ cw::rc_t cw::audio::device::file::create( handle_t& hRef, struct driver_str*&
|
||||
p->driver.deviceSeek = deviceSeek;
|
||||
p->driver.deviceRealTimeReport = deviceRealTimeReport;
|
||||
|
||||
if((rc = create( p->threadH, _threadCbFunc, p )) != kOkRC )
|
||||
if((rc = create( p->threadH, _threadCbFunc, p, "audio_dev_test" )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Audio device file thread create failed.");
|
||||
goto errLabel;
|
||||
|
@ -412,7 +412,7 @@ int main( int argc, const char* argv[] )
|
||||
|
||||
|
||||
// create the TCP listening thread
|
||||
if((rc = thread::create( app.tcpThreadH, tcpReceiveCallback, &app )) != kOkRC )
|
||||
if((rc = thread::create( app.tcpThreadH, tcpReceiveCallback, &app, "avahi_suf" )) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
// Allocate Avahi thread
|
||||
|
47
cwB23Tree.cpp
Normal file
47
cwB23Tree.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include "cwCommon.h"
|
||||
#include "cwLog.h"
|
||||
#include "cwCommonImpl.h"
|
||||
#include "cwMem.h"
|
||||
#include "cwObject.h"
|
||||
#include "cwB23Tree.h"
|
||||
|
||||
cw::rc_t cw::b23::test( const object_t* cfg )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
/*
|
||||
typedef struct tree<unsigned,char*,kInvalidIdx> tree_t;
|
||||
|
||||
tree_t* t = create<unsigned,char*,kInvalidIdx>(4);
|
||||
|
||||
destroy(t);
|
||||
*/
|
||||
|
||||
typedef struct tree_str<unsigned,const char*,kInvalidIdx> tree_t;
|
||||
|
||||
tree_t t;
|
||||
typedef struct kv_str
|
||||
{
|
||||
unsigned k;
|
||||
const char* v;
|
||||
} kv_t;
|
||||
|
||||
kv_t kvA[] = { {0,"zero"}, {1,"one"}, {2,"two"}, {3,"three"}, {4,"four"}, {5,"five"},
|
||||
{6,"siz"}, {7,"seven"}, {8,"eight"}, {9,"nine"}, {10,"ten"}, {11,"eleven"},
|
||||
{12,"twelve"}, {13,"thirt"}, {14,"fourt"}, {15,"fift"}, {16,"sixt"} };
|
||||
|
||||
unsigned kvN = sizeof(kvA)/sizeof(kvA[0]);
|
||||
|
||||
t.create(4);
|
||||
|
||||
for(unsigned i=0; i<kvN; ++i)
|
||||
{
|
||||
t.insert(kvA[i].k,kvA[i].v);
|
||||
t.print();
|
||||
}
|
||||
|
||||
t.destroy();
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
791
cwB23Tree.h
Normal file
791
cwB23Tree.h
Normal file
@ -0,0 +1,791 @@
|
||||
#ifndef cwB23Tree_h
|
||||
#define cwB23Tree_h
|
||||
|
||||
namespace cw
|
||||
{
|
||||
namespace b23
|
||||
{
|
||||
|
||||
/*
|
||||
This is a binary tree implemented as a (2-3 tree)
|
||||
See: docs/2-3-trees.pdf
|
||||
or https://cs.wellesley.edu/~cs231/handouts/2-3-trees.pdf
|
||||
*/
|
||||
|
||||
|
||||
template< typename K, typename V, K null_key >
|
||||
struct tree_str
|
||||
{
|
||||
|
||||
typedef enum {
|
||||
kInvalidNodeTId,
|
||||
k1LeafTId, // leaf where kv0 is in use but kv1 is not
|
||||
k2LeafTId, // leaf where kv0 and kv1 are in use
|
||||
k2NodeTId, // node with a lo,hi branch but no middle branch
|
||||
k3NodeTId // node with a lo,hi, and middle branch
|
||||
} node_tid_t;
|
||||
|
||||
typedef struct value_str
|
||||
{
|
||||
V value;
|
||||
struct value_str* link;
|
||||
} value_t;
|
||||
|
||||
typedef struct key_value_str
|
||||
{
|
||||
K key; // Key for this k/v pair
|
||||
value_t* valueL; // Linked list of values which share the same key.
|
||||
|
||||
bool is_empty() const { return key==null_key; }
|
||||
bool is_not_empty() const { return !is_empty(); }
|
||||
void set_empty() { key=null_key; valueL=nullptr; }
|
||||
|
||||
} key_value_t;
|
||||
|
||||
struct node_str;
|
||||
typedef struct match_result_str
|
||||
{
|
||||
struct node_str* node;
|
||||
key_value_t* kv;
|
||||
unsigned kv_idx; // 0 or 1
|
||||
} match_result_t;
|
||||
|
||||
typedef struct node_str
|
||||
{
|
||||
|
||||
unsigned nid;
|
||||
|
||||
struct node_str* parent;
|
||||
struct node_str* l_link; // low link
|
||||
struct node_str* m_link; // middle link
|
||||
struct node_str* h_link; // high link
|
||||
|
||||
// If kv1 is not empty then kv1.key is > kv0.key
|
||||
key_value_t kv0; // kv0 always contains a valid key-value pair
|
||||
key_value_t kv1; // kv1 is only valid if this is a 3 node
|
||||
|
||||
unsigned key_value_count() const { return kv1.is_empty() ? 1 : 2; }
|
||||
|
||||
const K& min_key() const { return kv0.key; }
|
||||
const K& max_key() const { return kv1.is_empty() ? kv0.key : kv1.key; }
|
||||
|
||||
// Leaf nodes have no child pointers, but may have one or two key-value pairs.
|
||||
bool is_leaf() const { return this->l_link == nullptr; }
|
||||
bool is_not_leaf() const { return !is_leaf(); }
|
||||
|
||||
bool is_1_leaf() const { return is_leaf() && kv1.is_empty(); }
|
||||
bool is_2_leaf() const { return is_leaf() && kv1.is_not_empty(); }
|
||||
|
||||
// 2 nodes have one key-value pair and valid l_link and h_link;
|
||||
bool is_2_node() const { return !this->is_leaf() && this->m_link == nullptr; }
|
||||
|
||||
// 3 nodes have two key-value pairs and valid l,m,h links
|
||||
bool is_3_node() const { return !this->is_leaf() && this->m_link != nullptr; }
|
||||
|
||||
node_tid_t type_id() const { return is_leaf() ? (is_1_leaf() ? k1LeafTId : k2LeafTId) : (is_2_node() ? k2NodeTId : k3NodeTId); }
|
||||
|
||||
bool is_valid()
|
||||
{
|
||||
// if l_link is null then h_link and m_link are also null. if l_link is not null then neither is h_link.
|
||||
bool fl0 = l_link==nullptr ? (h_link==nullptr && m_link==nullptr) : h_link!=nullptr;
|
||||
|
||||
// kv0 is never empty
|
||||
bool fl1 = kv0.is_not_empty() && kv0.valueL != nullptr;
|
||||
|
||||
// m_link is null if kv1 is empty
|
||||
bool fl2 = kv1.is_empty() ? m_link==nullptr : m_link!=nullptr;
|
||||
|
||||
return fl0 && fl1 && fl2;
|
||||
}
|
||||
|
||||
|
||||
unsigned height() const
|
||||
{
|
||||
if( is_leaf() )
|
||||
return 0;
|
||||
|
||||
return l_link->height() + 1;
|
||||
}
|
||||
|
||||
|
||||
match_result_t is_key_in_node( K key )
|
||||
{
|
||||
match_result_t r;
|
||||
|
||||
if( kv0.key == key )
|
||||
{
|
||||
r.node = this;
|
||||
r.kv = &kv0;
|
||||
r.kv_idx = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( kv1.is_not_empty() and kv1.key == key )
|
||||
{
|
||||
r.node = this;
|
||||
r.kv = &kv1;
|
||||
r.kv_idx = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.node = nullptr;
|
||||
r.kv = nullptr;
|
||||
r.kv_idx = 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
// Return the next node to this node given the key.
|
||||
// Return nullptr if this is a leaf node.
|
||||
struct node_str* next( K key )
|
||||
{
|
||||
node_t* n = nullptr;
|
||||
|
||||
assert( is_key_in_node(key) == false );
|
||||
|
||||
if( key < kv0.key )
|
||||
{
|
||||
n = l_link;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( key > (kv1.is_not_empty() ? kv1.key : kv0.key) )
|
||||
n = h_link;
|
||||
else
|
||||
n = m_link;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
} node_t;
|
||||
|
||||
typedef struct node_block_str
|
||||
{
|
||||
struct node_block_str* link;
|
||||
node_t* nodeA;
|
||||
unsigned nodeN;
|
||||
unsigned next_avail_node_idx; // index next empty slot
|
||||
} node_block_t;
|
||||
|
||||
typedef struct value_block_str
|
||||
{
|
||||
struct value_block_str* link;
|
||||
value_t* valueA;
|
||||
unsigned valueN;
|
||||
unsigned next_avail_value_idx; // index of next empty slot
|
||||
} value_block_t;
|
||||
|
||||
node_t* _root = nullptr;
|
||||
node_block_t* _beg_node_block = nullptr; // First node in node block linked list
|
||||
node_block_t* _end_node_block = nullptr; // Last block in node block linked list (always partially empty)
|
||||
value_block_t* _beg_value_block = nullptr; // First node in value block linked list
|
||||
value_block_t* _end_value_block = nullptr; // Last block in value block linked list (always partially empty)
|
||||
node_t* _free_node_list = nullptr; // Linked list, through 'parent' of avail nodes.
|
||||
value_t* _free_value_list = nullptr; //
|
||||
unsigned _nodes_per_block = 0;
|
||||
unsigned _values_per_block = 0;
|
||||
unsigned _nid = 0;
|
||||
|
||||
const char* node_tid_to_label( node_tid_t tid )
|
||||
{
|
||||
switch(tid)
|
||||
{
|
||||
case kInvalidNodeTId: return "<inv>";
|
||||
case k1LeafTId: return "1L";
|
||||
case k2LeafTId: return "2L";
|
||||
case k2NodeTId: return "2N";
|
||||
case k3NodeTId: return "3N";
|
||||
}
|
||||
return "<unk>";
|
||||
}
|
||||
|
||||
|
||||
// Return the node and kv that matches the key.
|
||||
match_result_t key_to_node( K key )
|
||||
{
|
||||
match_result_t r;
|
||||
node_t* n = _root;
|
||||
while(n != nullptr)
|
||||
{
|
||||
r = n->is_key_in_node(key);
|
||||
|
||||
if( r.node != nullptr )
|
||||
break;
|
||||
|
||||
n = n->next(key);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
node_block_t* _alloc_node_block( unsigned nodes_per_block )
|
||||
{
|
||||
node_block_t* b = mem::allocZ<node_block_t>();
|
||||
|
||||
b->nodeA = mem::allocZ<node_t>( nodes_per_block );
|
||||
b->nodeN = nodes_per_block;
|
||||
|
||||
if( _beg_node_block == nullptr )
|
||||
_beg_node_block = b;
|
||||
else
|
||||
_end_node_block->link = b;
|
||||
|
||||
_end_node_block = b;
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
value_block_t* _alloc_value_block( unsigned values_per_block )
|
||||
{
|
||||
value_block_t* b = mem::allocZ<value_block_t>();
|
||||
|
||||
b->valueA = mem::allocZ<value_t>( values_per_block );
|
||||
b->valueN = values_per_block;
|
||||
|
||||
if( _beg_value_block == nullptr )
|
||||
_beg_value_block = b;
|
||||
else
|
||||
_end_value_block->link = b;
|
||||
|
||||
_end_value_block = b;
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
void _alloc_value( key_value_t& kv, V new_value )
|
||||
{
|
||||
value_t* v = nullptr;
|
||||
|
||||
if( _free_value_list != nullptr )
|
||||
{
|
||||
v = _free_value_list;
|
||||
_free_value_list = v->link;
|
||||
v->link = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( _end_value_block==nullptr || _end_value_block->next_avail_value_idx >= _end_value_block->valueN )
|
||||
_alloc_value_block(_values_per_block);
|
||||
|
||||
assert( _end_value_block!= nullptr && _end_value_block->next_avail_value_idx < _end_value_block->valueN );
|
||||
|
||||
v = _end_value_block->valueA + _end_value_block->next_avail_value_idx++;
|
||||
}
|
||||
|
||||
v->value = new_value;
|
||||
v->link = kv.valueL;
|
||||
kv.valueL = v;
|
||||
|
||||
}
|
||||
|
||||
// Initialize a kv with a new key, value pair
|
||||
void _init_key_value( key_value_t& kv, K key, V value )
|
||||
{
|
||||
kv.key = key;
|
||||
_alloc_value(kv,value);
|
||||
}
|
||||
|
||||
// Initialize a kv from an existing kv pair
|
||||
void _move_key_value( key_value_t& lhs_kv, key_value_t& rhs_kv )
|
||||
{
|
||||
lhs_kv = rhs_kv;
|
||||
rhs_kv.set_empty();
|
||||
}
|
||||
|
||||
node_t* _alloc_node( node_t* parent )
|
||||
{
|
||||
node_t* n = nullptr;
|
||||
|
||||
if( _free_node_list != nullptr )
|
||||
{
|
||||
n = _free_node_list;
|
||||
memset(n,0,sizeof(*n));
|
||||
_free_node_list = _free_node_list->parent;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the current node block has no available nodes then create a new node block
|
||||
if( _end_node_block==nullptr || _end_node_block->next_avail_node_idx >= _end_node_block->nodeN )
|
||||
_alloc_node_block(_nodes_per_block);
|
||||
|
||||
// a node block with available nodes must now exist
|
||||
assert( _end_node_block!= nullptr && _end_node_block->next_avail_node_idx < _end_node_block->nodeN );
|
||||
|
||||
// get the next available node
|
||||
n = _end_node_block->nodeA + _end_node_block->next_avail_node_idx++;
|
||||
}
|
||||
|
||||
n->nid = _nid++;
|
||||
n->parent = parent;
|
||||
n->kv0.key = null_key;
|
||||
n->kv1.key = null_key;
|
||||
return n;
|
||||
}
|
||||
|
||||
// allocate a new node with a new key / value pair
|
||||
node_t* _alloc_node( node_t* parent, K new_key, V new_value )
|
||||
{
|
||||
node_t* n = _alloc_node(parent);
|
||||
|
||||
// all new nodes have a valid k/v pair in kv0
|
||||
_init_key_value( n->kv0, new_key, new_value );
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
// allocate a new node with an existing k/v apir.
|
||||
node_t* _alloc_node( node_t* parent, key_value_t& kv )
|
||||
{
|
||||
node_t* n = _alloc_node(parent);
|
||||
_move_key_value( n->kv0, kv );
|
||||
return n;
|
||||
}
|
||||
|
||||
void _free_key_value( key_value_t& kv )
|
||||
{
|
||||
// Free values by placing the values on the _free_value_list;
|
||||
value_t* v = kv.valueL;
|
||||
while( v != nullptr )
|
||||
{
|
||||
value_t* v0 = v->link;
|
||||
|
||||
// TODO: figure out how to call release on v->value
|
||||
// if release<T>(v->value) exists
|
||||
// release<T>(v->value);
|
||||
|
||||
v->link = _free_value_list;
|
||||
_free_value_list = v;
|
||||
v = v0;
|
||||
}
|
||||
|
||||
kv.set_empty();
|
||||
}
|
||||
|
||||
void _free_node( node_t* node )
|
||||
{
|
||||
_free_key_value(node->kv0);
|
||||
_free_key_value(node->kv1);
|
||||
|
||||
// track free nodes by forming a list using the 'parent' pointer
|
||||
node->parent = _free_node_list;
|
||||
_free_node_list = node;
|
||||
}
|
||||
|
||||
rc_t create( unsigned nodes_per_block )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
_nodes_per_block = nodes_per_block;
|
||||
_values_per_block= nodes_per_block;
|
||||
|
||||
_alloc_node_block(nodes_per_block);
|
||||
_alloc_value_block(nodes_per_block);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void destroy()
|
||||
{
|
||||
value_block_t* vb = _beg_value_block;
|
||||
while( vb!=nullptr )
|
||||
{
|
||||
value_block_t* vb0 = vb->link;
|
||||
mem::release(vb->valueA);
|
||||
mem::release(vb);
|
||||
vb=vb0;
|
||||
}
|
||||
|
||||
node_block_t* nb = _beg_node_block;
|
||||
while( nb !=nullptr )
|
||||
{
|
||||
node_block_t* nb0 = nb->link;
|
||||
mem::release(nb->nodeA);
|
||||
mem::release(nb);
|
||||
nb=nb0;
|
||||
}
|
||||
}
|
||||
|
||||
void _insert_into_1_leaf(node_t* n, K key, V value )
|
||||
{
|
||||
if( key > n->kv0.key )
|
||||
_init_key_value(n->kv1,key,value);
|
||||
else
|
||||
{
|
||||
_move_key_value(n->kv1,n->kv0);
|
||||
_init_key_value(n->kv0,key,value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
node_t* _2_leaf_to_2_node_sub_tree( node_t* n, K key, V value )
|
||||
{
|
||||
assert( n->is_2_leaf() );
|
||||
|
||||
if( key < n->kv0.key )
|
||||
{
|
||||
n->l_link = _alloc_node(n,key,value);
|
||||
n->h_link = _alloc_node(n,n->kv1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if( key > n->kv1.key )
|
||||
{
|
||||
n->l_link = _alloc_node(n,n->kv0);
|
||||
n->h_link = _alloc_node(n,key,value);
|
||||
_move_key_value(n->kv0,n->kv1);
|
||||
}
|
||||
else
|
||||
{
|
||||
n->l_link = _alloc_node(n,n->kv0);
|
||||
n->h_link = _alloc_node(n,n->kv1);
|
||||
_init_key_value(n->kv0,key,value);
|
||||
}
|
||||
}
|
||||
|
||||
assert(n->is_2_node());
|
||||
return n;
|
||||
}
|
||||
|
||||
void _link_to_parent_l( node_t* parent, node_t* child )
|
||||
{
|
||||
parent->l_link = child;
|
||||
child->parent = parent;
|
||||
}
|
||||
|
||||
void _link_to_parent_m( node_t* parent, node_t* child )
|
||||
{
|
||||
parent->m_link = child;
|
||||
child->parent = parent;
|
||||
}
|
||||
void _link_to_parent_h( node_t* parent, node_t* child )
|
||||
{
|
||||
parent->h_link = child;
|
||||
child->parent = parent;
|
||||
}
|
||||
|
||||
|
||||
// convert 'n', a 2-node, into a 3-node by absorbing it's 2-node child
|
||||
void _2_node_to_3_node( node_t* n, node_t* child )
|
||||
{
|
||||
assert( n->is_2_node() );
|
||||
|
||||
// if child is the l subtree
|
||||
if( child == n->l_link )
|
||||
{
|
||||
_move_key_value(n->kv1,n->kv0);
|
||||
_move_key_value(n->kv0,child->kv0);
|
||||
|
||||
_link_to_parent_l(n,child->l_link);
|
||||
_link_to_parent_m(n,child->h_link);
|
||||
|
||||
}
|
||||
else // child must be the h subtree
|
||||
{
|
||||
assert( child == n->h_link );
|
||||
|
||||
_move_key_value(n->kv1,child->kv0);
|
||||
|
||||
_link_to_parent_m(n,child->l_link);
|
||||
_link_to_parent_h(n,child->h_link);
|
||||
}
|
||||
|
||||
_free_node(child);
|
||||
|
||||
assert( n->is_3_node() );
|
||||
}
|
||||
|
||||
node_t* _2node_from_parts( node_t* parent, key_value_t& kv, node_t* l_subtree, node_t* h_subtree )
|
||||
{
|
||||
node_t* h_node = _alloc_node(parent,kv);
|
||||
_link_to_parent_l(h_node,l_subtree);
|
||||
_link_to_parent_h(h_node,h_subtree);
|
||||
return h_node;
|
||||
}
|
||||
|
||||
// Convert a 3-node to a balanced 2-node.
|
||||
// 'child' is a balanced sub-trees of 'n'
|
||||
void _3_node_to_balanced_2_node( node_t* n, node_t* child )
|
||||
{
|
||||
assert( n->is_3_node() );
|
||||
assert( child->is_2_node() );
|
||||
|
||||
// if the child is on the l-subtree
|
||||
if( child == n->l_link )
|
||||
{
|
||||
// make l-key the central node and the h-key the balanced h-subtree
|
||||
n->h_link = _2node_from_parts(n,n->kv1,n->m_link,n->h_link);
|
||||
}
|
||||
else
|
||||
{
|
||||
if( child == n->h_link )
|
||||
{
|
||||
// make h-key the central node and l-key the balanced l-subtree
|
||||
n->l_link = _2node_from_parts(n,n->kv0,n->l_link,n->m_link);
|
||||
_move_key_value(n->kv0,n->kv1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// make m-subtree he central node and l-key the balanced l-subtree and h-key the balanced h-subtree
|
||||
assert( child == n->m_link );
|
||||
n->l_link = _2node_from_parts(n,n->kv0,n->l_link,child->l_link);
|
||||
n->h_link = _2node_from_parts(n,n->kv1,child->h_link,n->h_link);
|
||||
_move_key_value(n->kv0,child->kv0);
|
||||
}
|
||||
}
|
||||
|
||||
// n is now a 2-node - remove the old m_link
|
||||
n->m_link = nullptr;
|
||||
|
||||
assert( n->is_2_node() );
|
||||
assert( n->l_link->is_2_node() );
|
||||
assert( n->h_link->is_2_node() );
|
||||
}
|
||||
|
||||
void _insert_up( node_t* n, node_t* sub_tree )
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
// if n is null then the root was already processed and we are done
|
||||
if( n == nullptr )
|
||||
break;
|
||||
|
||||
if( n->is_2_node() )
|
||||
{
|
||||
// if n is a 2-node the sub-tree is absorbed ...
|
||||
_2_node_to_3_node(n,sub_tree);
|
||||
break; // .. and we are done
|
||||
}
|
||||
else // n is a 3-node
|
||||
{
|
||||
// only 2-nodes and 3-nodes can be accessed when going up the tree
|
||||
assert( n->is_3_node() );
|
||||
|
||||
// create a balanced 2-node from the 3-node + sub-tree
|
||||
_3_node_to_balanced_2_node(n,sub_tree);
|
||||
|
||||
// the tree may now be imbalanced so continue upward
|
||||
sub_tree = n;
|
||||
n = n->parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _insert_down( node_t* n, K key, V value )
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
// If the key already exists at node n->kv0 then insert it in the kv0 value list
|
||||
if( key == n->kv0.key )
|
||||
{
|
||||
_alloc_value(n->kv0,value);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the key already exists at node n->kv1 then inser it in the kv1 value list
|
||||
if( n->kv1.is_not_empty() && key == n->kv1.key )
|
||||
{
|
||||
_alloc_value(n->kv1,value);
|
||||
return;
|
||||
}
|
||||
|
||||
switch( n->type_id() )
|
||||
{
|
||||
case k1LeafTId:
|
||||
_insert_into_1_leaf(n,key,value);
|
||||
return; // the new k/v was absorbed - we're done.
|
||||
|
||||
case k2LeafTId:
|
||||
if( key == 10 )
|
||||
{
|
||||
printf("break\n");
|
||||
}
|
||||
_insert_up( n->parent, _2_leaf_to_2_node_sub_tree(n, key, value ));
|
||||
return; // the new k/v inserted on the upward path
|
||||
|
||||
case k2NodeTId:
|
||||
n = key < n->kv0.key ? n->l_link : n->h_link;
|
||||
break;
|
||||
|
||||
case k3NodeTId:
|
||||
n = key < n->kv0.key ? n->l_link : (key < n->kv1.key ? n->m_link : n->h_link);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void insert( K key, V value )
|
||||
{
|
||||
if( _root == nullptr )
|
||||
_root = _alloc_node(nullptr,key,value);
|
||||
else
|
||||
_insert_down(_root,key,value);
|
||||
}
|
||||
|
||||
|
||||
match_result_t _in_order_successor( const match_result_t& mr0 )
|
||||
{
|
||||
assert( mr0.node != nullptr && mr0.node->is_not_leaf() );
|
||||
|
||||
match_result_t r;
|
||||
node_t* n;
|
||||
|
||||
// if mr0 is a 2 node or the high value of a 3 node
|
||||
if( mr0.node->is_2_node() || (mr0.node->is_3_node() && mr0.kv_idx == 1) )
|
||||
n = mr0.node->h_link; // get right subtree
|
||||
else
|
||||
{
|
||||
assert( mr0.node->is_3_node() && mr0.kv_idx == 0 );
|
||||
n = mr0.node->m_link;
|
||||
}
|
||||
|
||||
// go to left most leaf
|
||||
while( n->is_not_leaf() )
|
||||
n = n->l_link;
|
||||
|
||||
r.node = n;
|
||||
r.kv = &n->kv0;
|
||||
r.kv_idx = 0;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void remove_key_value( K key, const V& value )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
rc_t remove_key( K key )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
match_result_t mr0 = key_to_node(key);
|
||||
match_result_t mr1;
|
||||
|
||||
// the key does not exist in the tree.
|
||||
if( mr0.node == nullptr )
|
||||
{
|
||||
rc = cwLogError(kEleNotFoundRC,"The element to remove was not found.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// if the target node is a leaf
|
||||
if( mr0.node->is_leaf() )
|
||||
{
|
||||
if( mr0.node->is_2_leaf() )
|
||||
{
|
||||
if( mr0.kv_idx == 0 )
|
||||
_move_key_value(*mr0.kv0,*mr0.kv1);
|
||||
|
||||
//done: no hole exists in the leaf node
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
mr1 = mr0;
|
||||
}
|
||||
else // the target node is a 2 or 3 node
|
||||
{
|
||||
// locate the in-order sucessor
|
||||
mr1 = _in_order_successor(mr0);
|
||||
|
||||
// the in-order successor must exist if n is a 2 or 3 node
|
||||
assert( mr0->kv!= nullptr && mr1.kv != nullptr );
|
||||
|
||||
// move the in order successor value to the target node
|
||||
_move_key_value(*mr0.kv,*mr1.kv);
|
||||
|
||||
// mr1.kv is now empty
|
||||
|
||||
// if mr1.node->kv0 is now empty
|
||||
if(mr1.node->is_2_leaf() && mr1.kv_idx == 0 )
|
||||
{
|
||||
_move_key_value(*mr1.kv0,*mr1.kv1);
|
||||
|
||||
// done: mr1.node is now a 1 leaf - we're done
|
||||
assert( mr1.node->is_1_leaf() );
|
||||
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
assert( mr1.node != nullptr && mr1.node->is_leaf() );
|
||||
|
||||
if( mr1->is_2_leaf() )
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// if key is found on internal node - replace with in-order successor.
|
||||
// if in-order successor is on a non-leaf node continue replacing
|
||||
// with in-order successor until the replacement leaves a hole
|
||||
// in a leaf node.
|
||||
// If the terminal node with the hole is a 2-leaf then change it to a 1-leaf : DONE
|
||||
// if the terminal node is a 3-leaf then
|
||||
|
||||
errLabel:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void _print( const node_t* n, unsigned level )
|
||||
{
|
||||
if( n->l_link != nullptr )
|
||||
_print(n->l_link,level + 1);
|
||||
|
||||
unsigned pnid = n->parent==nullptr ? 666 : n->parent->nid;
|
||||
printf("%i h:%i k0:%i %s id:%i par:%i\n",level,n->height(),n->kv0.key,node_tid_to_label(n->type_id()),n->nid,pnid);
|
||||
|
||||
if( n->m_link != nullptr )
|
||||
_print(n->m_link,level+1);
|
||||
|
||||
if( n->kv1.is_not_empty() )
|
||||
printf("%i h:%i k1:%i %s id:%i par:%i\n",level,n->height(),n->kv1.key,node_tid_to_label(n->type_id()),n->nid,pnid);
|
||||
|
||||
if( n->h_link != nullptr )
|
||||
_print(n->h_link,level+1);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void print(const node_t* n = nullptr)
|
||||
{
|
||||
unsigned level = 0;
|
||||
|
||||
if( n == nullptr )
|
||||
n = _root;
|
||||
_print(n,level);
|
||||
printf("done\n");
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
rc_t test( const object_t* cfg );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif
|
@ -38,7 +38,7 @@ namespace cw
|
||||
kOpFailRC, // 19
|
||||
kSyntaxErrorRC, // 20
|
||||
kBufTooSmallRC, // 21
|
||||
kLabelNotFoundRC, // 22 - use by cwObject to indicate that an optional value does not exist.
|
||||
kEleNotFoundRC, // 22 - use by cwObject to indicate that an optional value does not exist.
|
||||
kDuplicateRC, // 23 - an invalid duplicate was detected
|
||||
kAssertFailRC, // 24 - used with cwLogFatal
|
||||
kInvalidDataTypeRC, // 25
|
||||
|
@ -237,7 +237,7 @@ namespace cw
|
||||
for(unsigned i=0; i<titleN; ++i)
|
||||
if( _title_to_col_index( p, titleA[i] ) == kInvalidIdx )
|
||||
{
|
||||
rc = cwLogError(kLabelNotFoundRC,"The required column '%s' does not exist.",titleA[i]);
|
||||
rc = cwLogError(kEleNotFoundRC,"The required column '%s' does not exist.",titleA[i]);
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
@ -420,6 +420,12 @@ unsigned cw::csv::title_col_index( handle_t h, const char* title )
|
||||
return _title_to_col_index(p,title);
|
||||
}
|
||||
|
||||
bool cw::csv::has_field( handle_t h, const char* title )
|
||||
{
|
||||
csv_t* p = _handleToPtr(h);
|
||||
return _title_to_col_index(p,title) != kInvalidIdx;
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::csv::rewind( handle_t h )
|
||||
{
|
||||
|
1
cwCsv.h
1
cwCsv.h
@ -22,6 +22,7 @@ namespace cw
|
||||
|
||||
const char* col_title( handle_t h, unsigned idx );
|
||||
unsigned title_col_index( handle_t h, const char* title );
|
||||
bool has_field( handle_t h, const char* title );
|
||||
|
||||
// Reset the CSV to make the title line current.
|
||||
// The next call to 'next_line()' will make the first data row current.
|
||||
|
@ -942,7 +942,7 @@ namespace cw
|
||||
return cwLogError(rc,"Unable to create EuCon server.");
|
||||
|
||||
// Create the application thread
|
||||
if((rc = thread::create( app.thH, appThreadFunc, &app )) != kOkRC )
|
||||
if((rc = thread::create( app.thH, appThreadFunc, &app, "eu_con" )) != kOkRC )
|
||||
return cwLogError(rc,"App thread create failed.");
|
||||
|
||||
// Start the application thread
|
||||
|
968
cwFlow.cpp
968
cwFlow.cpp
File diff suppressed because it is too large
Load Diff
5
cwFlow.h
5
cwFlow.h
@ -46,6 +46,7 @@ namespace cw
|
||||
|
||||
} external_device_t;
|
||||
|
||||
|
||||
void print_abuf( const struct abuf_str* abuf );
|
||||
void print_external_device( const external_device_t* dev );
|
||||
|
||||
@ -58,6 +59,7 @@ namespace cw
|
||||
|
||||
rc_t destroy( handle_t& hRef );
|
||||
|
||||
unsigned preset_cfg_flags( handle_t h );
|
||||
|
||||
// Run one cycle of the network.
|
||||
rc_t exec_cycle( handle_t h );
|
||||
@ -66,6 +68,9 @@ namespace cw
|
||||
rc_t exec( handle_t h );
|
||||
|
||||
rc_t apply_preset( handle_t h, const char* presetLabel );
|
||||
rc_t apply_preset( handle_t h, const multi_preset_selector_t& multi_preset_sel );
|
||||
rc_t apply_dual_preset( handle_t h, const char* presetLabel_0, const char* presetLabel_1, double coeff );
|
||||
|
||||
|
||||
rc_t set_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, bool value );
|
||||
rc_t set_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, int value );
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "cwMtx.h"
|
||||
#include "cwDspTypes.h" // real_t, sample_t
|
||||
#include "cwDspTransforms.h"
|
||||
#include "cwFlowDecl.h"
|
||||
#include "cwFlow.h"
|
||||
#include "cwFlowTypes.h"
|
||||
#include "cwFlowProc.h"
|
||||
@ -355,6 +356,15 @@ cw::rc_t cw::flow_cross::destroy( handle_t& hRef )
|
||||
return rc;
|
||||
}
|
||||
|
||||
unsigned cw::flow_cross::preset_cfg_flags( handle_t h )
|
||||
{
|
||||
flow_cross_t* p = _handleToPtr(h);
|
||||
if( p->netN > 0 )
|
||||
return preset_cfg_flags( p->netA[0].flowH );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::flow_cross::exec_cycle( handle_t h )
|
||||
{
|
||||
@ -425,6 +435,18 @@ cw::rc_t cw::flow_cross::apply_preset( handle_t h, destId_t destId, const char*
|
||||
return rc;
|
||||
}
|
||||
|
||||
cw::rc_t cw::flow_cross::apply_preset( handle_t h, destId_t destId, const flow::multi_preset_selector_t& multi_preset_sel )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
flow_cross_t* p = _handleToPtr(h);
|
||||
unsigned flow_idx = _get_flow_index(p, destId );
|
||||
|
||||
if((rc = flow::apply_preset( p->netA[flow_idx].flowH, multi_preset_sel )) != kOkRC )
|
||||
rc = cwLogError(rc,"Muti-preset application failed.");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
cw::rc_t cw::flow_cross::set_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, bool value )
|
||||
{ return _set_variable_value(h,destId,inst_label,var_label,chIdx,value); }
|
||||
|
||||
|
@ -25,10 +25,13 @@ namespace cw
|
||||
|
||||
rc_t destroy( handle_t& hRef );
|
||||
|
||||
unsigned preset_cfg_flags( handle_t h );
|
||||
|
||||
// Run one cycle of the network.
|
||||
rc_t exec_cycle( handle_t h );
|
||||
|
||||
rc_t apply_preset( handle_t h, destId_t destId, const char* presetLabel );
|
||||
rc_t apply_preset( handle_t h, destId_t destId, const flow::multi_preset_selector_t& multi_preset_sel );
|
||||
|
||||
rc_t set_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, bool value );
|
||||
rc_t set_variable_value( handle_t h, destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, int value );
|
||||
|
35
cwFlowDecl.h
Normal file
35
cwFlowDecl.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef cwFlowDecl_h
|
||||
#define cwFlowDecl_h
|
||||
|
||||
namespace cw
|
||||
{
|
||||
namespace flow
|
||||
{
|
||||
enum {
|
||||
kPriPresetProbFl = 0x01,
|
||||
kSecPresetProbFl = 0x02,
|
||||
kInterpPresetFl = 0x04
|
||||
};
|
||||
|
||||
typedef struct preset_order_str
|
||||
{
|
||||
const char* preset_label;
|
||||
unsigned order;
|
||||
} preset_order_t;
|
||||
|
||||
typedef struct multi_preset_selector_str
|
||||
{
|
||||
unsigned flags;
|
||||
const double* coeffV;
|
||||
const double* coeffMinV;
|
||||
const double* coeffMaxV;
|
||||
unsigned coeffN;
|
||||
|
||||
const preset_order_t* presetA;
|
||||
unsigned presetN;
|
||||
} multi_preset_selector_t;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -8,6 +8,7 @@
|
||||
#include "cwMtx.h"
|
||||
|
||||
#include "cwDspTypes.h" // real_t, sample_t
|
||||
#include "cwFlowDecl.h"
|
||||
#include "cwFlow.h"
|
||||
#include "cwFlowTypes.h"
|
||||
#include "cwFlowProc.h"
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "cwVectOps.h"
|
||||
#include "cwMtx.h"
|
||||
#include "cwDspTypes.h" // real_t, sample_t
|
||||
#include "cwFlowDecl.h"
|
||||
#include "cwFlow.h"
|
||||
#include "cwFlowTypes.h"
|
||||
|
||||
|
@ -210,7 +210,10 @@ namespace cw
|
||||
|
||||
const object_t* presetCfg; // presets designed for this network
|
||||
|
||||
unsigned framesPerCycle; // sample frames per cycle (64)
|
||||
unsigned framesPerCycle; // sample frames per cycle (64)
|
||||
bool multiPriPresetProbFl; // If set then probability is used to choose presets on multi-preset application
|
||||
bool multiSecPresetProbFl; //
|
||||
bool multiPresetInterpFl; // If set then interpolation is applied between two selectedd presets on multi-preset application
|
||||
unsigned cycleIndex; // Incremented with each processing cycle
|
||||
unsigned maxCycleCount; // count of cycles to run on flow::exec() or 0 if there is no limit.
|
||||
|
||||
|
160
cwIo.cpp
160
cwIo.cpp
@ -9,7 +9,7 @@
|
||||
#include "cwIo.h"
|
||||
|
||||
#include "cwMidi.h"
|
||||
#include "cwMidiPort.h"
|
||||
#include "cwMidiDevice.h"
|
||||
|
||||
|
||||
#include "cwObject.h"
|
||||
@ -31,6 +31,7 @@
|
||||
#include "cwWebSock.h"
|
||||
#include "cwUi.h"
|
||||
|
||||
#include "cwVectOps.h"
|
||||
|
||||
namespace cw
|
||||
{
|
||||
@ -165,6 +166,10 @@ namespace cw
|
||||
unsigned uiMapN; //
|
||||
bool uiAsyncFl;
|
||||
|
||||
sample_t latency_meas_thresh_db;
|
||||
sample_t latency_meas_thresh_lin;
|
||||
bool latency_meas_enable_fl;
|
||||
latency_meas_result_t latency_meas_result;
|
||||
} io_t;
|
||||
|
||||
|
||||
@ -366,7 +371,7 @@ namespace cw
|
||||
|
||||
time::get(t->nextTime);
|
||||
|
||||
if((rc = thread_mach::add(p->threadMachH,_timerThreadCb,t)) != kOkRC )
|
||||
if((rc = thread_mach::add(p->threadMachH,_timerThreadCb,t, label)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Timer thread assignment failed.");
|
||||
}
|
||||
@ -604,7 +609,7 @@ namespace cw
|
||||
msg_t m;
|
||||
midi_msg_t mm;
|
||||
const midi::packet_t* pkt = pktArray + i;
|
||||
io_t* p = reinterpret_cast<io_t*>(pkt->cbDataPtr);
|
||||
io_t* p = reinterpret_cast<io_t*>(pkt->cbArg);
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
|
||||
@ -628,7 +633,6 @@ namespace cw
|
||||
rc_t _midiPortCreate( io_t* p, const object_t* c )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
unsigned parserBufByteN = 1024;
|
||||
const object_t* cfg = nullptr;
|
||||
|
||||
// get the MIDI port cfg
|
||||
@ -638,14 +642,12 @@ namespace cw
|
||||
return kOkRC;
|
||||
}
|
||||
|
||||
if((rc = cfg->getv("parserBufByteN", parserBufByteN,
|
||||
"asyncFl", p->midiAsyncFl )) != kOkRC )
|
||||
if((rc = cfg->getv("asyncFl", p->midiAsyncFl )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(kSyntaxErrorRC,"MIDI configuration parse failed.");
|
||||
}
|
||||
|
||||
// initialie the MIDI system
|
||||
if((rc = create(p->midiH, _midiCallback, p, parserBufByteN, "app")) != kOkRC )
|
||||
if((rc = create(p->midiH, _midiCallback, p, cfg)) != kOkRC )
|
||||
return rc;
|
||||
|
||||
|
||||
@ -846,7 +848,7 @@ namespace cw
|
||||
|
||||
// create the socket thread
|
||||
if( p->sockN > 0 )
|
||||
if((rc = thread_mach::add(p->threadMachH,_socketThreadFunc,p)) != kOkRC )
|
||||
if((rc = thread_mach::add(p->threadMachH,_socketThreadFunc,p,"io_socket")) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Error creating socket thread.");
|
||||
goto errLabel;
|
||||
@ -1070,7 +1072,7 @@ namespace cw
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get sample ponters or advance the pointers on the buffer associates with each device.
|
||||
// Get sample pointers or advance the pointers on the buffer associates with each device.
|
||||
enum { kAudioGroupGetBuf, kAudioGroupAdvBuf };
|
||||
void _audioGroupProcSampleBufs( io_t* p, audioGroup_t* ag, unsigned processTypeId, unsigned inputFl )
|
||||
{
|
||||
@ -1100,6 +1102,39 @@ namespace cw
|
||||
}
|
||||
}
|
||||
|
||||
void _audio_latency_measure( io_t* p, const audio_msg_t& msg )
|
||||
{
|
||||
if( p->latency_meas_enable_fl )
|
||||
{
|
||||
|
||||
if( msg.iBufChCnt && p->latency_meas_result.audio_in_ts.tv_nsec == 0 )
|
||||
{
|
||||
sample_t rms = vop::rms(msg.iBufArray[0], msg.dspFrameCnt );
|
||||
|
||||
if( rms > p->latency_meas_thresh_lin )
|
||||
time::get(p->latency_meas_result.audio_in_ts);
|
||||
|
||||
if( rms > p->latency_meas_result.audio_in_rms_max )
|
||||
p->latency_meas_result.audio_in_rms_max = rms;
|
||||
}
|
||||
|
||||
if( msg.oBufChCnt && p->latency_meas_result.audio_out_ts.tv_nsec == 0 )
|
||||
{
|
||||
sample_t rms = vop::rms(msg.oBufArray[0], msg.dspFrameCnt );
|
||||
|
||||
if( rms > p->latency_meas_thresh_lin )
|
||||
time::get(p->latency_meas_result.audio_out_ts);
|
||||
|
||||
if( rms > p->latency_meas_result.audio_out_rms_max )
|
||||
p->latency_meas_result.audio_out_rms_max = rms;
|
||||
}
|
||||
|
||||
p->latency_meas_enable_fl = p->latency_meas_result.audio_in_ts.tv_nsec == 0 ||
|
||||
p->latency_meas_result.audio_out_ts.tv_nsec == 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// This is the audio processing thread function. Block on the audio group condition var
|
||||
// which is triggered when all the devices in the group are ready by _audioGroupNotifyIfReady().
|
||||
bool _audioGroupThreadFunc( void* arg )
|
||||
@ -1134,6 +1169,8 @@ namespace cw
|
||||
if((rc = _ioCallback( ag->p, ag->asyncFl, &msg)) != kOkRC )
|
||||
cwLogError(rc,"Audio app callback failed %i.",ag->asyncFl);
|
||||
|
||||
_audio_latency_measure( ag->p, ag->msg );
|
||||
|
||||
_audioGroupProcSampleBufs( ag->p, ag, kAudioGroupAdvBuf, true );
|
||||
_audioGroupProcSampleBufs( ag->p, ag, kAudioGroupAdvBuf, false );
|
||||
}
|
||||
@ -1530,7 +1567,7 @@ namespace cw
|
||||
}
|
||||
|
||||
// create the audio group thread
|
||||
if((rc = thread_mach::add(p->threadMachH,_audioGroupThreadFunc,p->audioGroupA+i)) != kOkRC )
|
||||
if((rc = thread_mach::add(p->threadMachH,_audioGroupThreadFunc,p->audioGroupA+i,"io_audio_group")) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Error creating audio group thread.");
|
||||
goto errLabel;
|
||||
@ -2107,6 +2144,8 @@ namespace cw
|
||||
|
||||
ui::ws::releaseArgs(args);
|
||||
|
||||
//ui::enableCache( ui::ws::uiHandle(p->wsUiH) );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -2364,6 +2403,7 @@ cw::rc_t cw::io::exec( handle_t h, void* execCbArg )
|
||||
if( p->wsUiH.isValid() )
|
||||
{
|
||||
ui::flushCache( ui::ws::uiHandle( p->wsUiH ));
|
||||
// Note this call blocks on the websocket handle: See cwUi.h:ws:exec()
|
||||
rc = ui::ws::exec( p->wsUiH );
|
||||
}
|
||||
|
||||
@ -2422,7 +2462,7 @@ void cw::io::realTimeReport( handle_t h )
|
||||
// Thread
|
||||
//
|
||||
|
||||
cw::rc_t cw::io::threadCreate( handle_t h, unsigned id, bool asyncFl, void* arg )
|
||||
cw::rc_t cw::io::threadCreate( handle_t h, unsigned id, bool asyncFl, void* arg, const char* label )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
io_t* p = _handleToPtr(h);
|
||||
@ -2435,7 +2475,7 @@ cw::rc_t cw::io::threadCreate( handle_t h, unsigned id, bool asyncFl, void* arg
|
||||
t->link = p->threadL;
|
||||
p->threadL = t;
|
||||
|
||||
if((rc = thread_mach::add( p->threadMachH, _threadFunc, t )) != kOkRC )
|
||||
if((rc = thread_mach::add( p->threadMachH, _threadFunc, t, label )) != kOkRC )
|
||||
rc = cwLogError(rc,"Thread create failed.");
|
||||
|
||||
return rc;
|
||||
@ -2668,6 +2708,19 @@ cw::rc_t cw::io::midiDeviceSend( handle_t h, unsigned devIdx, unsigned portIdx,
|
||||
// Audio
|
||||
//
|
||||
|
||||
bool cw::io::audioIsEnabled( handle_t h )
|
||||
{
|
||||
io_t* p = _handleToPtr(h);
|
||||
for(unsigned devIdx=0; devIdx<p->audioDevN; ++devIdx)
|
||||
{
|
||||
audioDev_t* ad;
|
||||
if((ad = _audioDeviceIndexToRecd(p,devIdx)) != nullptr && ad->activeFl )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned cw::io::audioDeviceCount( handle_t h )
|
||||
{
|
||||
io_t* p = _handleToPtr(h);
|
||||
@ -3728,6 +3781,87 @@ cw::rc_t cw::io::uiSendValue( handle_t h, unsigned uuId, const char* value )
|
||||
return rc;
|
||||
}
|
||||
|
||||
cw::rc_t cw::io::uiSendMsg( handle_t h, const char* msg )
|
||||
{
|
||||
rc_t rc;
|
||||
ui::handle_t uiH;
|
||||
if((rc = _handleToUiHandle(h,uiH)) == kOkRC )
|
||||
rc = ui::sendMsg(uiH, msg);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void cw::io::latency_measure_setup(handle_t h)
|
||||
{
|
||||
io_t* p = _handleToPtr(h);
|
||||
p->latency_meas_enable_fl = true;
|
||||
p->latency_meas_thresh_db = -50;
|
||||
p->latency_meas_thresh_lin = pow(10.0,p->latency_meas_thresh_db/20.0);
|
||||
p->latency_meas_result.note_on_input_ts = {0};
|
||||
p->latency_meas_result.note_on_output_ts = {0};
|
||||
p->latency_meas_result.audio_in_ts = {0};
|
||||
p->latency_meas_result.audio_out_ts = {0};
|
||||
p->latency_meas_result.audio_in_rms_max = 0;
|
||||
p->latency_meas_result.audio_out_rms_max = 0;
|
||||
|
||||
if( p->midiH.isValid() )
|
||||
latency_measure_reset(p->midiH);
|
||||
}
|
||||
|
||||
cw::io::latency_meas_result_t cw::io::latency_measure_result(handle_t h)
|
||||
{
|
||||
io_t* p = _handleToPtr(h);
|
||||
|
||||
if( p->midiH.isValid() )
|
||||
{
|
||||
midi::device::latency_meas_combined_result_t r = latency_measure_result(p->midiH);
|
||||
|
||||
p->latency_meas_result.note_on_input_ts = r.alsa_dev.note_on_input_ts;
|
||||
p->latency_meas_result.note_on_output_ts = r.alsa_dev.note_on_output_ts;
|
||||
}
|
||||
|
||||
return p->latency_meas_result;
|
||||
}
|
||||
|
||||
void cw::io::latency_measure_report(handle_t h)
|
||||
{
|
||||
io_t* p = _handleToPtr(h);
|
||||
latency_meas_result_t r = latency_measure_result(h);
|
||||
unsigned t0,t1;
|
||||
|
||||
if( r.note_on_input_ts.tv_nsec )
|
||||
{
|
||||
if( r.note_on_output_ts.tv_nsec )
|
||||
{
|
||||
t0 = time::elapsedMicros(r.note_on_input_ts,r.note_on_output_ts);
|
||||
printf("midi in - midi out: %6i\n",t0);
|
||||
}
|
||||
|
||||
if( r.audio_in_ts.tv_nsec )
|
||||
{
|
||||
t0 = time::elapsedMicros(r.note_on_output_ts,r.audio_in_ts);
|
||||
t1 = time::elapsedMicros(r.note_on_input_ts,r.audio_in_ts);
|
||||
printf("midi out - audio in: %6i %6i\n",t0,t1);
|
||||
}
|
||||
|
||||
if( r.audio_out_ts.tv_nsec )
|
||||
{
|
||||
t0 = time::elapsedMicros(r.audio_in_ts,r.audio_out_ts);
|
||||
t1 = time::elapsedMicros(r.note_on_input_ts,r.audio_out_ts);
|
||||
printf("audio in - audio out: %6i %6i\n",t0,t1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
printf("Thresh: %f %f db\n",p->latency_meas_thresh_db,p->latency_meas_thresh_lin);
|
||||
if( r.audio_in_rms_max != 0 )
|
||||
printf("audio in max:%f %f dB\n", r.audio_in_rms_max, 20.0*log10(r.audio_in_rms_max));
|
||||
|
||||
if( r.audio_out_rms_max != 0 )
|
||||
printf("audio out max:%f %f dB\n", r.audio_out_rms_max, 20.0*log10(r.audio_out_rms_max));
|
||||
|
||||
}
|
||||
|
||||
|
||||
void cw::io::uiReport( handle_t h )
|
||||
{
|
||||
ui::handle_t uiH;
|
||||
|
25
cwIo.h
25
cwIo.h
@ -164,7 +164,11 @@ namespace cw
|
||||
rc_t start( handle_t h );
|
||||
rc_t pause( handle_t h );
|
||||
rc_t stop( handle_t h );
|
||||
|
||||
// Note that this call blocks on the the UI websocket handle.
|
||||
// See ui:ws:exec().
|
||||
rc_t exec( handle_t h, void* execCbArg=nullptr );
|
||||
|
||||
bool isShuttingDown( handle_t h );
|
||||
void report( handle_t h );
|
||||
void realTimeReport( handle_t h );
|
||||
@ -173,7 +177,7 @@ namespace cw
|
||||
//
|
||||
// Thread
|
||||
//
|
||||
rc_t threadCreate( handle_t h, unsigned id, bool asyncFl, void* arg );
|
||||
rc_t threadCreate( handle_t h, unsigned id, bool asyncFl, void* arg, const char* label );
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
//
|
||||
@ -231,7 +235,9 @@ namespace cw
|
||||
// Audio
|
||||
//
|
||||
|
||||
bool audioIsEnabled( handle_t h );
|
||||
unsigned audioDeviceCount( handle_t h );
|
||||
unsigned audioActiveDeviceCount( handle_t h );
|
||||
unsigned audioDeviceLabelToIndex( handle_t h, const char* label );
|
||||
const char* audioDeviceLabel( handle_t h, unsigned devIdx );
|
||||
rc_t audioDeviceSetUserId( handle_t h, unsigned devIdx, unsigned userId );
|
||||
@ -398,6 +404,23 @@ namespace cw
|
||||
rc_t uiSendValue( handle_t h, unsigned uuId, double value );
|
||||
rc_t uiSendValue( handle_t h, unsigned uuId, const char* value );
|
||||
|
||||
rc_t uiSendMsg( handle_t h, const char* msg );
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
time::spec_t note_on_input_ts;
|
||||
time::spec_t note_on_output_ts;
|
||||
time::spec_t audio_in_ts;
|
||||
time::spec_t audio_out_ts;
|
||||
sample_t audio_in_rms_max;
|
||||
sample_t audio_out_rms_max;
|
||||
} latency_meas_result_t;
|
||||
|
||||
void latency_measure_setup(handle_t h);
|
||||
latency_meas_result_t latency_measure_result(handle_t h);
|
||||
void latency_measure_report(handle_t h);
|
||||
|
||||
void uiRealTimeReport( handle_t h );
|
||||
void uiReport( handle_t h );
|
||||
|
||||
|
@ -870,8 +870,8 @@ namespace cw
|
||||
am->devIdx = pkt->devIdx;
|
||||
am->portIdx = pkt->portIdx;
|
||||
am->timestamp = mm->timeStamp;
|
||||
am->ch = mm->status & 0x0f;
|
||||
am->status = mm->status & 0xf0;
|
||||
am->ch = mm->ch;
|
||||
am->status = mm->status;
|
||||
am->d0 = mm->d0;
|
||||
am->d1 = mm->d1;
|
||||
|
||||
|
14
cwIoFlow.cpp
14
cwIoFlow.cpp
@ -10,6 +10,7 @@
|
||||
#include "cwMtx.h"
|
||||
|
||||
#include "cwDspTypes.h"
|
||||
#include "cwFlowDecl.h"
|
||||
#include "cwFlow.h"
|
||||
#include "cwFlowTypes.h"
|
||||
#include "cwFlowCross.h"
|
||||
@ -361,8 +362,10 @@ cw::rc_t cw::io_flow::create( handle_t& hRef,
|
||||
|
||||
errLabel:
|
||||
if( rc != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"io_flow create failed.");
|
||||
_destroy(p);
|
||||
|
||||
}
|
||||
|
||||
|
||||
return rc;
|
||||
@ -385,6 +388,12 @@ cw::rc_t cw::io_flow::destroy( handle_t& hRef )
|
||||
return rc;
|
||||
}
|
||||
|
||||
unsigned cw::io_flow::preset_cfg_flags( handle_t h )
|
||||
{
|
||||
io_flow_t* p = _handleToPtr(h);
|
||||
return preset_cfg_flags(p->crossFlowH);
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::io_flow::exec( handle_t h, const io::msg_t& msg )
|
||||
{
|
||||
@ -419,6 +428,9 @@ cw::rc_t cw::io_flow::apply_preset( handle_t h, unsigned crossFadeMs, const char
|
||||
cw::rc_t cw::io_flow::apply_preset( handle_t h, flow_cross::destId_t destId, const char* presetLabel )
|
||||
{ return apply_preset( _handleToPtr(h)->crossFlowH, destId, presetLabel ); }
|
||||
|
||||
cw::rc_t cw::io_flow::apply_preset( handle_t h, flow_cross::destId_t destId, const flow::multi_preset_selector_t& multi_preset_sel )
|
||||
{ return apply_preset( _handleToPtr(h)->crossFlowH, destId, multi_preset_sel ); }
|
||||
|
||||
cw::rc_t cw::io_flow::set_variable_value( handle_t h, flow_cross::destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, bool value )
|
||||
{ return flow_cross::set_variable_value( _handleToPtr(h)->crossFlowH, destId, inst_label, var_label, chIdx, value ); }
|
||||
|
||||
|
@ -10,6 +10,8 @@ namespace cw
|
||||
rc_t create( handle_t& hRef, io::handle_t ioH, double srate, unsigned crossFadeCnt, const object_t& flow_class_dict, const object_t& cfg );
|
||||
rc_t destroy( handle_t& hRef );
|
||||
|
||||
unsigned preset_cfg_flags( handle_t h );
|
||||
|
||||
rc_t exec( handle_t h, const io::msg_t& msg );
|
||||
|
||||
|
||||
@ -21,6 +23,8 @@ namespace cw
|
||||
// activate the next network.
|
||||
rc_t apply_preset( handle_t h, flow_cross::destId_t destId, const char* presetLabel );
|
||||
|
||||
rc_t apply_preset( handle_t h, flow_cross::destId_t destId, const flow::multi_preset_selector_t& multi_preset_sel );
|
||||
|
||||
rc_t set_variable_value( handle_t h, flow_cross::destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, bool value );
|
||||
rc_t set_variable_value( handle_t h, flow_cross::destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, int value );
|
||||
rc_t set_variable_value( handle_t h, flow_cross::destId_t destId, const char* inst_label, const char* var_label, unsigned chIdx, unsigned value );
|
||||
|
@ -213,7 +213,7 @@ namespace cw
|
||||
|
||||
void _xmit_midi( midi_record_play_t* p, unsigned devIdx, uint8_t ch, uint8_t status, uint8_t d0, uint8_t d1 )
|
||||
{
|
||||
if( !p->supressMidiXmitFl )
|
||||
if( !p->supressMidiXmitFl && p->midiDevA[devIdx].enableFl )
|
||||
io::midiDeviceSend( p->ioH, p->midiDevA[devIdx].midiOutDevIdx, p->midiDevA[devIdx].midiOutPortIdx, status + ch, d0, d1 );
|
||||
}
|
||||
|
||||
@ -1355,7 +1355,7 @@ namespace cw
|
||||
{
|
||||
//printf("R:%i %i %i\n",mm->status, mm->d0, mm->d1);
|
||||
|
||||
const am_midi_msg_t* am = _midi_store( p, pkt->devIdx, pkt->portIdx, mm->timeStamp, mm->status & 0x0f, mm->status & 0xf0, mm->d0, mm->d1 );
|
||||
const am_midi_msg_t* am = _midi_store( p, pkt->devIdx, pkt->portIdx, mm->timeStamp, mm->ch, mm->status, mm->d0, mm->d1 );
|
||||
|
||||
if( p->thruFl && am != nullptr )
|
||||
_transmit_msg( p, am, false );
|
||||
@ -1465,8 +1465,6 @@ cw::rc_t cw::midi_record_play::create( handle_t& hRef,
|
||||
|
||||
p = mem::allocZ<midi_record_play_t>();
|
||||
|
||||
printf("P0:%p\n",p);
|
||||
|
||||
p->ioH = ioH; // p->ioH is used in _destory() so initialize it here
|
||||
|
||||
// parse the cfg
|
||||
@ -2159,25 +2157,24 @@ unsigned cw::midi_record_play::dev_count( handle_t h )
|
||||
|
||||
cw::rc_t cw::midi_record_play::vel_table_disable( handle_t h, unsigned devIdx )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
midi_record_play_t* p = _handleToPtr(h);
|
||||
rc_t rc = kOkRC;
|
||||
midi_record_play_t* p = _handleToPtr(h);
|
||||
unsigned begDevIdx = devIdx==kInvalidIdx ? 0 : devIdx;
|
||||
unsigned endDevIdx = devIdx==kInvalidIdx ? p->midiDevN : begDevIdx+1;
|
||||
|
||||
if( devIdx != kInvalidIdx )
|
||||
if( devIdx != kInvalidIdx && devIdx > p->midiDevN )
|
||||
{
|
||||
if( devIdx > p->midiDevN )
|
||||
{
|
||||
rc = cwLogError(kInvalidArgRC,"The device index '%i'is invalid.",devIdx);
|
||||
goto errLabel;
|
||||
}
|
||||
rc = cwLogError(kInvalidArgRC,"The device index '%i'is invalid.",devIdx);
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
p->midiDevA[devIdx].velTableArray = nullptr;
|
||||
}
|
||||
else
|
||||
for(unsigned i=begDevIdx; i<endDevIdx; ++i)
|
||||
{
|
||||
for(unsigned i=0; i<p->midiDevN; ++i)
|
||||
p->midiDevA[i].velTableArray = nullptr;
|
||||
mem::release(p->midiDevA[i].velTableArray);
|
||||
p->midiDevA[i].velTableN = 0;
|
||||
}
|
||||
|
||||
|
||||
errLabel:
|
||||
return rc;
|
||||
}
|
||||
|
@ -94,13 +94,13 @@ cw::rc_t cw::min_test( const object_t* cfg )
|
||||
if((rc = create(app.ioH,cfg,minTestCb,&app)) != kOkRC )
|
||||
return rc;
|
||||
|
||||
if((rc = threadCreate( app.ioH, kThread0Id, asyncFl, &app )) != kOkRC )
|
||||
if((rc = threadCreate( app.ioH, kThread0Id, asyncFl, &app, "min_test_0" )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Thread 0 create failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if((rc = threadCreate( app.ioH, kThread1Id, asyncFl, &app )) != kOkRC )
|
||||
if((rc = threadCreate( app.ioH, kThread1Id, asyncFl, &app, "min_test_1" )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Thread 1 create failed.");
|
||||
goto errLabel;
|
||||
|
@ -16,25 +16,25 @@
|
||||
#include "cwScoreFollowerPerf.h"
|
||||
#include "cwIoMidiRecordPlay.h"
|
||||
#include "cwIoPresetSelApp.h"
|
||||
#include "cwPianoScore.h"
|
||||
#include "cwVectOps.h"
|
||||
#include "cwMath.h"
|
||||
#include "cwDspTypes.h"
|
||||
#include "cwMtx.h"
|
||||
#include "cwFlowDecl.h"
|
||||
#include "cwFlow.h"
|
||||
#include "cwFlowTypes.h"
|
||||
#include "cwFlowCross.h"
|
||||
#include "cwIoFlow.h"
|
||||
#include "cwPresetSel.h"
|
||||
#include "cwVelTableTuner.h"
|
||||
//#include "cwCmInterface.h"
|
||||
#include "cwDynRefTbl.h"
|
||||
#include "cwScoreParse.h"
|
||||
#include "cwSfScore.h"
|
||||
#include "cwSfTrack.h"
|
||||
#include "cwPerfMeas.h"
|
||||
#include "cwPianoScore.h"
|
||||
#include "cwScoreFollower.h"
|
||||
|
||||
#define INVALID_LOC (0)
|
||||
|
||||
namespace cw
|
||||
{
|
||||
@ -49,6 +49,8 @@ namespace cw
|
||||
kIoReportBtnId,
|
||||
kNetPrintBtnId,
|
||||
kReportBtnId,
|
||||
kLatencyBtnId,
|
||||
|
||||
|
||||
kStartBtnId,
|
||||
kStopBtnId,
|
||||
@ -76,6 +78,11 @@ namespace cw
|
||||
kPerfSelId,
|
||||
kAltSelId,
|
||||
|
||||
kPriPresetProbCheckId,
|
||||
kSecPresetProbCheckId,
|
||||
kPresetInterpCheckId,
|
||||
|
||||
|
||||
kEnaRecordCheckId,
|
||||
kMidiSaveBtnId,
|
||||
kMidiLoadBtnId,
|
||||
@ -148,6 +155,7 @@ namespace cw
|
||||
{ kPanelDivId, kIoReportBtnId, "ioReportBtnId" },
|
||||
{ kPanelDivId, kNetPrintBtnId, "netPrintBtnId" },
|
||||
{ kPanelDivId, kReportBtnId, "reportBtnId" },
|
||||
{ kPanelDivId, kLatencyBtnId, "latencyBtnId" },
|
||||
|
||||
{ kPanelDivId, kStartBtnId, "startBtnId" },
|
||||
{ kPanelDivId, kStopBtnId, "stopBtnId" },
|
||||
@ -174,6 +182,9 @@ namespace cw
|
||||
{ kPanelDivId, kLoadBtnId, "loadBtnId" },
|
||||
{ kPanelDivId, kPerfSelId, "perfSelId" },
|
||||
{ kPanelDivId, kAltSelId, "altSelId" },
|
||||
{ kPanelDivId, kPriPresetProbCheckId, "presetProbPriCheckId" },
|
||||
{ kPanelDivId, kSecPresetProbCheckId, "presetProbSecCheckId" },
|
||||
{ kPanelDivId, kPresetInterpCheckId, "presetInterpCheckId" },
|
||||
|
||||
{ kPanelDivId, kEnaRecordCheckId, "enaRecordCheckId" },
|
||||
{ kPanelDivId, kMidiSaveBtnId, "midiSaveBtnId" },
|
||||
@ -294,14 +305,16 @@ namespace cw
|
||||
score_follower::handle_t sfH;
|
||||
midi_record_play::handle_t mrpH;
|
||||
|
||||
perf_score::handle_t scoreH;
|
||||
perf_score::handle_t perfScoreH;
|
||||
loc_map_t* locMap;
|
||||
unsigned locMapN;
|
||||
|
||||
unsigned insertLoc; // last valid insert location id received from the GUI
|
||||
|
||||
unsigned minLoc; // min/max locations of the currently loaded performance
|
||||
unsigned maxLoc;
|
||||
unsigned minScoreLoc; // min/max locations of the currently loaded score
|
||||
unsigned maxScoreLoc; //
|
||||
unsigned minPerfLoc; // min/max locations of the currently loaded performance
|
||||
unsigned maxPerfLoc;
|
||||
|
||||
unsigned beg_play_loc; // beg/end play loc's from the UI
|
||||
unsigned end_play_loc;
|
||||
@ -318,6 +331,8 @@ namespace cw
|
||||
|
||||
bool printMidiFl;
|
||||
|
||||
unsigned multiPresetFlags;
|
||||
|
||||
bool seqActiveFl; // true if the sequence is currently active (set by 'Play Seq' btn)
|
||||
unsigned seqStartedFl; // set by the first seq idle callback
|
||||
unsigned seqFragId; //
|
||||
@ -567,7 +582,7 @@ namespace cw
|
||||
preset_sel::destroy(app.psH);
|
||||
io_flow::destroy(app.ioFlowH);
|
||||
midi_record_play::destroy(app.mrpH);
|
||||
perf_score::destroy( app.scoreH );
|
||||
perf_score::destroy( app.perfScoreH );
|
||||
mem::release(app.locMap);
|
||||
return kOkRC;
|
||||
}
|
||||
@ -873,37 +888,77 @@ namespace cw
|
||||
cwLogInfo("No preset fragment was found for the requested timestamp.");
|
||||
else
|
||||
{
|
||||
unsigned preset_idx;
|
||||
unsigned preset_idx = kInvalidIdx;
|
||||
const char* preset_label = nullptr;
|
||||
const char* preset_type_label = "<None>";
|
||||
rc_t apply_rc = kOkRC;
|
||||
|
||||
// if the preset sequence player is active then apply the next selected seq. preset
|
||||
// otherwise select the next primary preset for ths fragment
|
||||
unsigned seq_idx_n = app->seqActiveFl ? app->seqPresetIdx : kInvalidIdx;
|
||||
|
||||
// get the preset index to play for this fragment
|
||||
if((preset_idx = fragment_play_preset_index(app->psH, frag,seq_idx_n)) == kInvalidIdx )
|
||||
cwLogInfo("No preset has been assigned to the fragment at end loc. '%i'.",frag->endLoc );
|
||||
if( score_evt != nullptr )
|
||||
{
|
||||
//printf("Meas:e:%f d:%f t:%f c:%f\n",score_evt->even,score_evt->dyn,score_evt->tempo,score_evt->cost);
|
||||
printf("Meas:e:%f d:%f t:%f c:%f\n",score_evt->featV[perf_meas::kEvenValIdx],score_evt->featV[perf_meas::kDynValIdx],score_evt->featV[perf_meas::kTempoValIdx],score_evt->featV[perf_meas::kMatchCostValIdx]);
|
||||
}
|
||||
|
||||
// if we are not automatically sequencing through the presets and a score event was given
|
||||
if( seq_idx_n == kInvalidIdx && score_evt != nullptr && frag->multiPresetN>0 )
|
||||
{
|
||||
//double coeffV[] = { score_evt->even, score_evt->dyn, score_evt->tempo, score_evt->cost };
|
||||
//unsigned coeffN = sizeof(coeffV)/sizeof(coeffV[0]);
|
||||
|
||||
flow::multi_preset_selector_t mp_sel =
|
||||
{ .flags = app->multiPresetFlags,
|
||||
.coeffV = score_evt->featV,
|
||||
.coeffMinV = score_evt->featMinV,
|
||||
.coeffMaxV = score_evt->featMaxV,
|
||||
.coeffN = perf_meas::kValCnt,
|
||||
.presetA = frag->multiPresetA,
|
||||
.presetN = frag->multiPresetN
|
||||
};
|
||||
|
||||
if( app->ioFlowH.isValid() )
|
||||
apply_rc = io_flow::apply_preset( app->ioFlowH, flow_cross::kNextDestId, mp_sel );
|
||||
|
||||
preset_label = mp_sel.presetN>0 && mp_sel.presetA[0].preset_label!=nullptr ? mp_sel.presetA[0].preset_label : nullptr;
|
||||
|
||||
preset_type_label = "multi";
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* preset_label = preset_sel::preset_label(app->psH,preset_idx);
|
||||
|
||||
// don't display preset updates unless the score is actually loaded
|
||||
printf("Apply preset: '%s' : loc:%i\n", preset_idx==kInvalidIdx ? "<invalid>" : preset_label, loc );
|
||||
|
||||
_set_status(app,"Apply preset: '%s'.", preset_idx == kInvalidIdx ? "<invalid>" : preset_label);
|
||||
|
||||
if( score_evt != nullptr )
|
||||
printf("Meas:e:%f d:%f t:%f c:%f\n",score_evt->even,score_evt->dyn,score_evt->tempo,score_evt->cost);
|
||||
// get the preset index to play for this fragment
|
||||
if((preset_idx = fragment_play_preset_index(app->psH, frag, seq_idx_n)) == kInvalidIdx )
|
||||
cwLogInfo("No preset has been assigned to the fragment at end loc. '%i'.",frag->endLoc );
|
||||
else
|
||||
preset_label = preset_sel::preset_label(app->psH,preset_idx);
|
||||
|
||||
if( preset_label != nullptr )
|
||||
{
|
||||
io_flow::apply_preset( app->ioFlowH, flow_cross::kNextDestId, preset_label );
|
||||
if( app->ioFlowH.isValid() )
|
||||
apply_rc = io_flow::apply_preset( app->ioFlowH, flow_cross::kNextDestId, preset_label );
|
||||
|
||||
io_flow::set_variable_value( app->ioFlowH, flow_cross::kNextDestId, "wet_in_gain", "gain", flow::kAnyChIdx, (dsp::real_t)frag->igain );
|
||||
io_flow::set_variable_value( app->ioFlowH, flow_cross::kNextDestId, "wet_out_gain","gain", flow::kAnyChIdx, (dsp::real_t)frag->ogain );
|
||||
io_flow::set_variable_value( app->ioFlowH, flow_cross::kNextDestId, "wd_bal", "in", flow::kAnyChIdx, (dsp::real_t)frag->wetDryGain );
|
||||
|
||||
io_flow::begin_cross_fade( app->ioFlowH, frag->fadeOutMs );
|
||||
preset_type_label = "single";
|
||||
}
|
||||
|
||||
// don't display preset updates unless the score is actually loaded
|
||||
printf("Apply %s preset: '%s' : loc:%i status:%i\n", preset_type_label, preset_label==nullptr ? "<invalid>" : preset_label, loc, apply_rc );
|
||||
|
||||
_set_status(app,"Apply %s preset: '%s'.", preset_type_label, preset_label==nullptr ? "<invalid>" : preset_label);
|
||||
|
||||
}
|
||||
|
||||
// apply the fragment defined gain settings
|
||||
if( app->ioFlowH.isValid() )
|
||||
{
|
||||
io_flow::set_variable_value( app->ioFlowH, flow_cross::kNextDestId, "wet_in_gain", "gain", flow::kAnyChIdx, (dsp::real_t)frag->igain );
|
||||
io_flow::set_variable_value( app->ioFlowH, flow_cross::kNextDestId, "wet_out_gain","gain", flow::kAnyChIdx, (dsp::real_t)frag->ogain );
|
||||
io_flow::set_variable_value( app->ioFlowH, flow_cross::kNextDestId, "wd_bal", "in", flow::kAnyChIdx, (dsp::real_t)frag->wetDryGain );
|
||||
|
||||
// activate the cross-fade
|
||||
io_flow::begin_cross_fade( app->ioFlowH, frag->fadeOutMs );
|
||||
}
|
||||
}
|
||||
|
||||
@ -970,7 +1025,7 @@ namespace cw
|
||||
|
||||
unsigned _get_loc_from_score_follower( app_t* app, double secs, unsigned muid, uint8_t status, uint8_t d0, uint8_t d1 )
|
||||
{
|
||||
unsigned loc = INVALID_LOC;
|
||||
unsigned loc = score_parse::kInvalidLocId;
|
||||
|
||||
// if this is a MIDI note-on event - then udpate the score follower
|
||||
if( midi::isNoteOn(status,d1) && muid != kInvalidIdx )
|
||||
@ -1036,7 +1091,7 @@ namespace cw
|
||||
snprintf(buf,buf_byte_cnt,"ch:%i status:0x%02x d0:%i d1:%i",ch,status,d0,d1);
|
||||
}
|
||||
else
|
||||
perf_score::event_to_string( app->scoreH, id, buf, buf_byte_cnt );
|
||||
perf_score::event_to_string( app->perfScoreH, id, buf, buf_byte_cnt );
|
||||
printf("%s\n",buf);
|
||||
}
|
||||
|
||||
@ -1052,7 +1107,7 @@ namespace cw
|
||||
|
||||
// TODO: ZERO SHOULD BE A VALID LOC VALUE - MAKE -1 THE INVALID LOC VALUE
|
||||
|
||||
if( loc != INVALID_LOC && app->trackMidiFl )
|
||||
if( loc != score_parse::kInvalidLocId && app->trackMidiFl )
|
||||
{
|
||||
if( preset_sel::track_loc( app->psH, loc, f ) )
|
||||
{
|
||||
@ -1070,15 +1125,15 @@ namespace cw
|
||||
}
|
||||
}
|
||||
|
||||
rc_t _on_live_midi( app_t* app, const io::msg_t& msg )
|
||||
rc_t _on_live_midi_event( app_t* app, const io::msg_t& msg )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
if( msg.u.midi != nullptr )
|
||||
{
|
||||
|
||||
const io::midi_msg_t& m = *msg.u.midi;
|
||||
const io::midi_msg_t& m = *msg.u.midi;
|
||||
const midi::packet_t* pkt = m.pkt;
|
||||
|
||||
// for each midi msg
|
||||
for(unsigned j = 0; j<pkt->msgCnt; ++j)
|
||||
{
|
||||
@ -1089,8 +1144,8 @@ namespace cw
|
||||
}
|
||||
else // this is a triple
|
||||
{
|
||||
midi::msg_t* mm = pkt->msgArray + j;
|
||||
time::spec_t timestamp;
|
||||
midi::msg_t* mm = pkt->msgArray + j;
|
||||
unsigned id = app->enableRecordFl ? last_store_index(app->mrpH) : kInvalidId;
|
||||
unsigned loc = app->beg_play_loc;
|
||||
|
||||
@ -1098,8 +1153,8 @@ namespace cw
|
||||
|
||||
if( midi::isChStatus(mm->status) )
|
||||
{
|
||||
if(midi_record_play::send_midi_msg( app->mrpH, midi_record_play::kSampler_MRP_DevIdx, mm->status & 0x0f, mm->status & 0xf0, mm->d0, mm->d1 ) == kOkRC )
|
||||
_midi_play_callback( app, midi_record_play::kMidiEventActionId, id, timestamp, loc, nullptr, mm->status & 0x0f, mm->status & 0xf0, mm->d0, mm->d1 );
|
||||
if(midi_record_play::send_midi_msg( app->mrpH, midi_record_play::kSampler_MRP_DevIdx, mm->ch, mm->status, mm->d0, mm->d1 ) == kOkRC )
|
||||
_midi_play_callback( app, midi_record_play::kMidiEventActionId, id, timestamp, loc, nullptr, mm->ch, mm->status, mm->d0, mm->d1 );
|
||||
}
|
||||
|
||||
}
|
||||
@ -1138,16 +1193,16 @@ namespace cw
|
||||
|
||||
frameIdxRef = kInvalidIdx;
|
||||
|
||||
if( app->in_audio_begin_loc != INVALID_LOC )
|
||||
if( app->in_audio_begin_loc != score_parse::kInvalidLocId )
|
||||
{
|
||||
if((e0 = loc_to_event(app->scoreH,app->in_audio_begin_loc)) == nullptr )
|
||||
if((e0 = loc_to_event(app->perfScoreH,app->in_audio_begin_loc)) == nullptr )
|
||||
{
|
||||
rc = cwLogError(kInvalidArgRC,"The score event associated with the 'in_audio_beg_loc' loc:%i could not be found.",loc);
|
||||
goto errLabel;
|
||||
}
|
||||
}
|
||||
|
||||
if((e1 = loc_to_event(app->scoreH,loc)) == nullptr )
|
||||
if((e1 = loc_to_event(app->perfScoreH,loc)) == nullptr )
|
||||
{
|
||||
rc = cwLogError(kInvalidArgRC,"The score event associated with the begin play loc:%i could not be found.",loc);
|
||||
goto errLabel;
|
||||
@ -1176,7 +1231,7 @@ namespace cw
|
||||
|
||||
bool _is_performance_loaded( app_t* app )
|
||||
{
|
||||
return app->scoreH.isValid() and event_count(app->scoreH) > 0;
|
||||
return app->perfScoreH.isValid() and event_count(app->perfScoreH) > 0;
|
||||
}
|
||||
|
||||
rc_t _do_sf_reset( app_t* app, unsigned loc )
|
||||
@ -1304,9 +1359,6 @@ namespace cw
|
||||
evt_cnt = midi_record_play::event_count(app->mrpH);
|
||||
|
||||
io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), evt_cnt );
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
errLabel:
|
||||
@ -1416,7 +1468,7 @@ namespace cw
|
||||
//io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), midi_record_play::event_index(app->mrpH) );
|
||||
//io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kTotalMidiEvtCntId), midi_record_play::event_count(app->mrpH) );
|
||||
|
||||
io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kTotalMidiEvtCntId), app->maxLoc-app->minLoc );
|
||||
io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kTotalMidiEvtCntId), app->maxPerfLoc-app->minPerfLoc );
|
||||
}
|
||||
|
||||
// Update the UI with the value from the the fragment data record.
|
||||
@ -1585,7 +1637,10 @@ namespace cw
|
||||
// _clear_status(app);
|
||||
//else
|
||||
if( !enableFl )
|
||||
{
|
||||
_set_status(app,"Invalid fragment play range. beg:%i end:%i",begPlayLoc,endPlayLoc);
|
||||
cwLogError(kInvalidArgRC,"Invalid fragment play range. beg:%i end:%i",begPlayLoc,endPlayLoc);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1776,9 +1831,9 @@ namespace cw
|
||||
|
||||
// Set the fragment beg/end play range
|
||||
get_value( app->psH, fragId, preset_sel::kBegPlayLocVarId, kInvalidId, fragBegLoc );
|
||||
uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, fragPanelUuId, kFragBegPlayLocId, fragChanId), app->minLoc, app->maxLoc, 1, 0, fragBegLoc );
|
||||
uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, fragPanelUuId, kFragEndPlayLocId, fragChanId), app->minLoc, app->maxLoc, 1, 0, endLoc );
|
||||
|
||||
uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, fragPanelUuId, kFragBegPlayLocId, fragChanId), app->minScoreLoc, app->maxScoreLoc, 1, 0, fragBegLoc );
|
||||
uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, fragPanelUuId, kFragEndPlayLocId, fragChanId), app->minScoreLoc, app->maxScoreLoc, 1, 0, endLoc );
|
||||
|
||||
// Attach blobs to the UI to allow convenient access back to the prese_sel data record
|
||||
_frag_set_ui_blob(app, io::uiFindElementUuId(app->ioH, fragPanelUuId, kFragInGainId, fragChanId), fragId, preset_sel::kInGainVarId, kInvalidId );
|
||||
@ -1831,6 +1886,8 @@ namespace cw
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
get_loc_range(app->psH,app->minScoreLoc,app->maxScoreLoc);
|
||||
|
||||
// Settting psNextFrag to a non-null value causes the
|
||||
app->psNextFrag = preset_sel::get_fragment_base(app->psH);
|
||||
|
||||
@ -1872,6 +1929,8 @@ namespace cw
|
||||
if( app->psNextFrag == nullptr )
|
||||
{
|
||||
|
||||
io::uiSendMsg( app->ioH, "{ \"op\":\"attach\" }" );
|
||||
|
||||
// the fragments are loaded enable the 'load' and 'alt' menu
|
||||
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kPerfSelId ), true );
|
||||
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kAltSelId ), true );
|
||||
@ -1890,6 +1949,7 @@ namespace cw
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
rc_t _restore_fragment_data( app_t* app )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
@ -1920,6 +1980,8 @@ namespace cw
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
get_loc_range(app->psH,app->minScoreLoc,app->maxScoreLoc);
|
||||
|
||||
//preset_sel::report( app->psH );
|
||||
|
||||
f = preset_sel::get_fragment_base(app->psH);
|
||||
@ -1944,7 +2006,7 @@ namespace cw
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
*/
|
||||
rc_t _on_ui_save( app_t* app )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
@ -1994,19 +2056,19 @@ namespace cw
|
||||
midiEventCntRef = 0;
|
||||
|
||||
// get the count of MIDI events
|
||||
if((e = perf_score::base_event( app->scoreH )) != nullptr )
|
||||
if((e = perf_score::base_event( app->perfScoreH )) != nullptr )
|
||||
for(; e != nullptr; e=e->link)
|
||||
if( e->status != 0 )
|
||||
midiEventN += 1;
|
||||
|
||||
// copy the MIDI events
|
||||
if((e = perf_score::base_event( app->scoreH )) != nullptr )
|
||||
if((e = perf_score::base_event( app->perfScoreH )) != nullptr )
|
||||
{
|
||||
// allocate the locMap[]
|
||||
app->locMap = mem::resizeZ<loc_map_t>( app->locMap, midiEventN );
|
||||
app->locMapN = midiEventN;
|
||||
app->minLoc = INVALID_LOC;
|
||||
app->maxLoc = INVALID_LOC;
|
||||
app->minPerfLoc = score_parse::kInvalidLocId;
|
||||
app->maxPerfLoc = score_parse::kInvalidLocId;
|
||||
|
||||
// allocate the the player msg array
|
||||
m = mem::allocZ<midi_record_play::midi_msg_t>( midiEventN );
|
||||
@ -2027,17 +2089,17 @@ namespace cw
|
||||
app->locMap[i].loc = e->loc;
|
||||
app->locMap[i].timestamp = m[i].timestamp;
|
||||
|
||||
if( e->loc != INVALID_LOC )
|
||||
if( e->loc != score_parse::kInvalidLocId )
|
||||
{
|
||||
if( app->minLoc == INVALID_LOC )
|
||||
app->minLoc = e->loc;
|
||||
if( app->minPerfLoc == score_parse::kInvalidLocId )
|
||||
app->minPerfLoc = e->loc;
|
||||
else
|
||||
app->minLoc = std::min(app->minLoc,e->loc);
|
||||
app->minPerfLoc = std::min(app->minPerfLoc,e->loc);
|
||||
|
||||
if( app->maxLoc == INVALID_LOC )
|
||||
app->maxLoc = e->loc;
|
||||
if( app->maxPerfLoc == score_parse::kInvalidLocId )
|
||||
app->maxPerfLoc = e->loc;
|
||||
else
|
||||
app->maxLoc = std::max(app->maxLoc,e->loc);
|
||||
app->maxPerfLoc = std::max(app->maxPerfLoc,e->loc);
|
||||
}
|
||||
|
||||
++i;
|
||||
@ -2054,7 +2116,7 @@ namespace cw
|
||||
|
||||
midiEventCntRef = midiEventN;
|
||||
|
||||
cwLogInfo("%i MIDI events loaded from score. Loc Min:%i Max:%i", midiEventN , app->minLoc, app->maxLoc);
|
||||
cwLogInfo("%i MIDI events loaded from score. Loc Min:%i Max:%i", midiEventN , app->minPerfLoc, app->maxPerfLoc);
|
||||
}
|
||||
|
||||
errLabel:
|
||||
@ -2114,7 +2176,7 @@ namespace cw
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc_t _do_load( app_t* app, const char* perf_fn, const vel_tbl_t* vtA=nullptr, unsigned vtN=0 )
|
||||
rc_t _do_load_perf_score( app_t* app, const char* perf_fn, const vel_tbl_t* vtA=nullptr, unsigned vtN=0 )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
unsigned midiEventN = 0;
|
||||
@ -2122,8 +2184,8 @@ namespace cw
|
||||
cwLogInfo("Loading");
|
||||
_set_status(app,"Loading...");
|
||||
|
||||
// load the performance or score
|
||||
if((rc= perf_score::create( app->scoreH, perf_fn )) != kOkRC )
|
||||
// load the performance
|
||||
if((rc= perf_score::create( app->perfScoreH, perf_fn )) != kOkRC )
|
||||
{
|
||||
cwLogError(rc,"Score create failed on '%s'.",perf_fn);
|
||||
goto errLabel;
|
||||
@ -2153,12 +2215,12 @@ namespace cw
|
||||
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kInsertLocId ), true );
|
||||
|
||||
// set the UI begin/end play to the locations of the newly loaded performance
|
||||
app->end_play_loc = app->maxLoc;
|
||||
app->beg_play_loc = app->minLoc;
|
||||
app->end_play_loc = app->maxPerfLoc;
|
||||
app->beg_play_loc = app->minPerfLoc;
|
||||
|
||||
// Update the master range of the play beg/end number widgets
|
||||
io::uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, kBegPlayLocNumbId), app->minLoc, app->maxLoc, 1, 0, app->beg_play_loc );
|
||||
io::uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, kEndPlayLocNumbId), app->minLoc, app->maxLoc, 1, 0, app->end_play_loc );
|
||||
io::uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, kBegPlayLocNumbId), app->minPerfLoc, app->maxPerfLoc, 1, 0, app->beg_play_loc );
|
||||
io::uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, kEndPlayLocNumbId), app->minPerfLoc, app->maxPerfLoc, 1, 0, app->end_play_loc );
|
||||
|
||||
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kBegPlayLocNumbId ), true );
|
||||
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kEndPlayLocNumbId ), true );
|
||||
@ -2170,8 +2232,8 @@ namespace cw
|
||||
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kSfResetBtnId ), true );
|
||||
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kSfResetLocNumbId ), true );
|
||||
|
||||
io::uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, kSfResetLocNumbId), app->minLoc, app->maxLoc, 1, 0, app->beg_play_loc );
|
||||
io::uiSendValue( app->ioH, io::uiFindElementUuId(app->ioH, kSfResetLocNumbId), app->minLoc);
|
||||
io::uiSetNumbRange( app->ioH, io::uiFindElementUuId(app->ioH, kSfResetLocNumbId), app->minPerfLoc, app->maxPerfLoc, 1, 0, app->beg_play_loc );
|
||||
io::uiSendValue( app->ioH, io::uiFindElementUuId(app->ioH, kSfResetLocNumbId), app->minPerfLoc);
|
||||
|
||||
cwLogInfo("'%s' loaded.",perf_fn);
|
||||
|
||||
@ -2216,10 +2278,10 @@ namespace cw
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
printf("Loading:%s %p %i\n",prp->fname,prp->vel_tblA, prp->vel_tblN);
|
||||
printf("Loading:%s\n",prp->fname );
|
||||
|
||||
// load the requested performance
|
||||
if((rc = _do_load(app,prp->fname,prp->vel_tblA, prp->vel_tblN)) != kOkRC )
|
||||
if((rc = _do_load_perf_score(app,prp->fname,prp->vel_tblA, prp->vel_tblN)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(kSyntaxErrorRC,"The performance load failed.");
|
||||
goto errLabel;
|
||||
@ -2604,6 +2666,7 @@ namespace cw
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
rc_t _on_midi_load_btn_0(app_t* app)
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
@ -2612,13 +2675,13 @@ namespace cw
|
||||
object_t* cfg = nullptr;
|
||||
unsigned sfResetLoc;
|
||||
|
||||
if((rc = perf_score::create_from_midi_csv( app->scoreH, app->midiLoadFname )) != kOkRC )
|
||||
if((rc = perf_score::create_from_midi_csv( app->perfScoreH, app->midiLoadFname )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Piano score performance load failed on '%s'.",cwStringNullGuard(app->midiLoadFname));
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if((rc = _do_load(app,nullptr)) != kOkRC )
|
||||
if((rc = _do_load_perf_score(app,nullptr)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Performance load failed on '%s'.",cwStringNullGuard(app->midiLoadFname));
|
||||
goto errLabel;
|
||||
@ -2659,12 +2722,13 @@ namespace cw
|
||||
|
||||
return rc;
|
||||
}
|
||||
*/
|
||||
|
||||
rc_t _on_midi_load_btn(app_t* app)
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
if((rc = _do_load(app,app->midiLoadFname)) != kOkRC )
|
||||
if((rc = _do_load_perf_score(app,app->midiLoadFname)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Piano score performance load failed on '%s'.",cwStringNullGuard(app->midiLoadFname));
|
||||
goto errLabel;
|
||||
@ -2675,7 +2739,6 @@ namespace cw
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
rc_t _on_midi_load_fname(app_t* app, const char* fname)
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
@ -2725,9 +2788,11 @@ namespace cw
|
||||
if((rc = preset_sel::set_value( app->psH, kInvalidId, varId, kInvalidId, value )) != kOkRC )
|
||||
rc = cwLogError(rc,"Master value set failed on varId:%i %s.%s.",varId,cwStringNullGuard(inst_label),cwStringNullGuard(var_label));
|
||||
else
|
||||
if((rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, inst_label, var_label, flow::kAnyChIdx, (dsp::real_t)value )) != kOkRC )
|
||||
rc = cwLogError(rc,"Master value send failed on %s.%s.",cwStringNullGuard(inst_label),cwStringNullGuard(var_label));
|
||||
|
||||
{
|
||||
if( app->ioFlowH.isValid() )
|
||||
if((rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, inst_label, var_label, flow::kAnyChIdx, (dsp::real_t)value )) != kOkRC )
|
||||
rc = cwLogError(rc,"Master value send failed on %s.%s.",cwStringNullGuard(inst_label),cwStringNullGuard(var_label));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -2762,13 +2827,15 @@ namespace cw
|
||||
case kPvWndSmpCntId:
|
||||
var_label = "wndSmpN";
|
||||
app->pvWndSmpCnt = m.value->u.u;
|
||||
rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, "pva", var_label, flow::kAnyChIdx, m.value->u.u );
|
||||
if( app->ioFlowH.isValid() )
|
||||
rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, "pva", var_label, flow::kAnyChIdx, m.value->u.u );
|
||||
break;
|
||||
|
||||
case kSdBypassId:
|
||||
var_label = "bypass";
|
||||
app->sdBypassFl = m.value->u.b;
|
||||
rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, "sd", var_label, flow::kAnyChIdx, m.value->u.b );
|
||||
if( app->ioFlowH.isValid() )
|
||||
rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, "sd", var_label, flow::kAnyChIdx, m.value->u.b );
|
||||
break;
|
||||
|
||||
case kSdInGainId:
|
||||
@ -2809,14 +2876,15 @@ namespace cw
|
||||
case kCmpBypassId:
|
||||
var_label = "cmp-bypass";
|
||||
app->cmpBypassFl = m.value->u.b;
|
||||
rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, "cmp", "bypass", flow::kAnyChIdx, m.value->u.b );
|
||||
if( app->ioFlowH.isValid() )
|
||||
rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, "cmp", "bypass", flow::kAnyChIdx, m.value->u.b );
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if( m.value->tid == ui::kDoubleTId )
|
||||
if( m.value->tid == ui::kDoubleTId && app->ioFlowH.isValid() )
|
||||
rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, "sd", var_label, flow::kAnyChIdx, (dsp::real_t)m.value->u.d );
|
||||
|
||||
if(rc != kOkRC )
|
||||
@ -2825,7 +2893,7 @@ namespace cw
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc_t _on_live_midi_fl( app_t* app, bool useLiveMidiFl )
|
||||
rc_t _on_live_midi_checkbox( app_t* app, bool useLiveMidiFl )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
dsp::real_t value;
|
||||
@ -2847,8 +2915,9 @@ namespace cw
|
||||
io::uiSendValue( app->ioH, io::uiFindElementUuId( app->ioH, kSyncDelayMsId ), app->dfltSyncDelayMs );
|
||||
}
|
||||
|
||||
if((rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, "sync_delay", "delayMs", flow::kAnyChIdx, (dsp::real_t)value )) != kOkRC )
|
||||
rc = cwLogError(rc,"Error setting sync delay 'flow' value.");
|
||||
if( app->ioFlowH.isValid() )
|
||||
if((rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, "sync_delay", "delayMs", flow::kAnyChIdx, (dsp::real_t)value )) != kOkRC )
|
||||
rc = cwLogError(rc,"Error setting sync delay 'flow' value.");
|
||||
|
||||
|
||||
app->useLiveMidiFl = useLiveMidiFl;
|
||||
@ -2887,7 +2956,7 @@ namespace cw
|
||||
if((rc = _fragment_load_data(app)) != kOkRC )
|
||||
rc = cwLogError(rc,"Preset data restore failed.");
|
||||
|
||||
_on_live_midi_fl(app,app->useLiveMidiFl);
|
||||
_on_live_midi_checkbox(app,app->useLiveMidiFl);
|
||||
|
||||
return rc;
|
||||
}
|
||||
@ -2907,7 +2976,8 @@ namespace cw
|
||||
break;
|
||||
|
||||
case kNetPrintBtnId:
|
||||
io_flow::print_network(app->ioFlowH,flow_cross::kCurDestId);
|
||||
if( app->ioFlowH.isValid() )
|
||||
io_flow::print_network(app->ioFlowH,flow_cross::kCurDestId);
|
||||
break;
|
||||
|
||||
case kReportBtnId:
|
||||
@ -2926,6 +2996,11 @@ namespace cw
|
||||
preset_sel::report_presets(app->psH);
|
||||
break;
|
||||
|
||||
case kLatencyBtnId:
|
||||
latency_measure_report(app->ioH);
|
||||
latency_measure_setup(app->ioH);
|
||||
break;
|
||||
|
||||
case kSaveBtnId:
|
||||
_on_ui_save(app);
|
||||
break;
|
||||
@ -2938,6 +3013,18 @@ namespace cw
|
||||
_on_alt_select(app,m.value->u.u);
|
||||
break;
|
||||
|
||||
case kPriPresetProbCheckId:
|
||||
app->multiPresetFlags = cwEnaFlag(app->multiPresetFlags,flow::kPriPresetProbFl,m.value->u.b);
|
||||
break;
|
||||
|
||||
case kSecPresetProbCheckId:
|
||||
app->multiPresetFlags = cwEnaFlag(app->multiPresetFlags,flow::kSecPresetProbFl,m.value->u.b);
|
||||
break;
|
||||
|
||||
case kPresetInterpCheckId:
|
||||
app->multiPresetFlags = cwEnaFlag(app->multiPresetFlags,flow::kInterpPresetFl,m.value->u.b);
|
||||
break;
|
||||
|
||||
case kMidiThruCheckId:
|
||||
cwLogInfo("MIDI thru:%i",m.value->u.b);
|
||||
_set_midi_thru_state(app, m.value->u.b);
|
||||
@ -2952,8 +3039,7 @@ namespace cw
|
||||
break;
|
||||
|
||||
case kLiveCheckId:
|
||||
//app->useLiveMidiFl = m.value->u.b;
|
||||
_on_live_midi_fl(app, m.value->u.b );
|
||||
_on_live_midi_checkbox(app, m.value->u.b );
|
||||
break;
|
||||
|
||||
case kTrackMidiCheckId:
|
||||
@ -3175,6 +3261,18 @@ namespace cw
|
||||
_on_echo_midi_enable( app, m.uuId, midi_record_play::kSampler_MRP_DevIdx );
|
||||
break;
|
||||
|
||||
case kPriPresetProbCheckId:
|
||||
io::uiSendValue( app->ioH, m.uuId, preset_cfg_flags(app->ioFlowH) & flow::kPriPresetProbFl );
|
||||
break;
|
||||
|
||||
case kSecPresetProbCheckId:
|
||||
io::uiSendValue( app->ioH, m.uuId, preset_cfg_flags(app->ioFlowH) & flow::kSecPresetProbFl );
|
||||
break;
|
||||
|
||||
case kPresetInterpCheckId:
|
||||
io::uiSendValue( app->ioH, m.uuId, preset_cfg_flags(app->ioFlowH) & flow::kInterpPresetFl );
|
||||
break;
|
||||
|
||||
case kWetInGainId:
|
||||
_on_echo_master_value( app, preset_sel::kMasterWetInGainVarId, m.uuId );
|
||||
break;
|
||||
@ -3364,7 +3462,7 @@ namespace cw
|
||||
|
||||
case io::kMidiTId:
|
||||
if( app->useLiveMidiFl && app->mrpH.isValid() )
|
||||
_on_live_midi( app, *m );
|
||||
_on_live_midi_event( app, *m );
|
||||
break;
|
||||
|
||||
case io::kAudioTId:
|
||||
@ -3520,11 +3618,21 @@ cw::rc_t cw::preset_sel_app::main( const object_t* cfg, int argc, const char* ar
|
||||
}
|
||||
|
||||
// create the IO Flow controller
|
||||
if(app.flow_cfg==nullptr || app.flow_proc_dict==nullptr || (rc = io_flow::create(app.ioFlowH,app.ioH,sysSampleRate,app.crossFadeCnt,*app.flow_proc_dict,*app.flow_cfg)) != kOkRC )
|
||||
if( !audioIsEnabled(app.ioH) )
|
||||
{
|
||||
rc = cwLogError(rc,"The IO Flow controller create failed.");
|
||||
goto errLabel;
|
||||
cwLogInfo("Audio disabled.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if(app.flow_cfg==nullptr || app.flow_proc_dict==nullptr || (rc = io_flow::create(app.ioFlowH,app.ioH,sysSampleRate,app.crossFadeCnt,*app.flow_proc_dict,*app.flow_cfg)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"The IO Flow controller create failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
app.multiPresetFlags = preset_cfg_flags(app.ioFlowH);
|
||||
}
|
||||
|
||||
printf("ioFlow is %s valid.\n",app.ioFlowH.isValid() ? "" : "not");
|
||||
|
||||
// start the IO framework instance
|
||||
if((rc = io::start(app.ioH)) != kOkRC )
|
||||
@ -3533,11 +3641,24 @@ cw::rc_t cw::preset_sel_app::main( const object_t* cfg, int argc, const char* ar
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
//io::uiReport(app.ioH);
|
||||
|
||||
|
||||
// execute the io framework
|
||||
while( !io::isShuttingDown(app.ioH))
|
||||
{
|
||||
time::spec_t t0;
|
||||
time::get(t0);
|
||||
|
||||
// This call may block on the websocket handle.
|
||||
io::exec(app.ioH);
|
||||
sleepMs( app.psNextFrag != nullptr? 1 : 50 );
|
||||
|
||||
unsigned dMs = time::elapsedMs(t0);
|
||||
if( dMs < 50 && app.psNextFrag == nullptr )
|
||||
{
|
||||
sleepMs( 50-dMs );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// stop the io framework
|
||||
|
@ -1317,7 +1317,7 @@ cw::rc_t cw::net::mdns::test()
|
||||
}
|
||||
|
||||
// create the TCP listening thread
|
||||
if((rc = thread::create( app.tcpThreadH, tcpReceiveCallback, &app )) != kOkRC )
|
||||
if((rc = thread::create( app.tcpThreadH, tcpReceiveCallback, &app, "mdns" )) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
|
||||
|
@ -12,7 +12,7 @@ void* cw::mem::_alloc( void* p0, unsigned n, unsigned flags )
|
||||
|
||||
n += 2*sizeof(unsigned); // add space for the size of the block
|
||||
|
||||
// if there is no existing block
|
||||
// if there is an existing block
|
||||
if( p0 != nullptr )
|
||||
{
|
||||
// get a pointer to the base of the exsting block
|
||||
|
30
cwMidi.h
30
cwMidi.h
@ -88,26 +88,32 @@ namespace cw
|
||||
template< typename T> bool isStatus( T s ) { return (kNoteOffMdId <= removeCh(s) /*&& ((unsigned)(s)) <= kSysRtResetMdId*/ ); }
|
||||
template< typename T> bool isChStatus( T s ) { return (kNoteOffMdId <= removeCh(s) && removeCh(s) < kSysExMdId); }
|
||||
|
||||
template< typename T> bool isCtlStatus( T s ) { return removeCh(s) == kCtlMdId; }
|
||||
|
||||
template< typename T> bool isNoteOnStatus( T s ) { return ( kNoteOnMdId <= removeCh(s) && removeCh(s) <= (kNoteOnMdId + kMidiChCnt) ); }
|
||||
template< typename T> bool isNoteOn( T s, T d1 ) { return ( isNoteOnStatus(removeCh(s)) && (d1)!=0) ; }
|
||||
template< typename T> bool isNoteOff( T s, T d1 ) { return ( (isNoteOnStatus(removeCh(s)) && (d1)==0) || (kNoteOffMdId <= removeCh(s) && removeCh(s) <= (kNoteOffMdId + kMidiChCnt)) ); }
|
||||
template< typename T> bool isCtl( T s ) { return ( kCtlMdId <= removeCh(s) && removeCh(s) <= (kCtlMdId + kMidiChCnt) ); }
|
||||
|
||||
template< typename T> bool isSustainPedal( T s, T d0 ) { return ( kCtlMdId <= removeCh(s) && removeCh(s) <= (kCtlMdId + kMidiChCnt) && (d0)== kSustainCtlMdId ); }
|
||||
template< typename T> bool isSustainPedalDown( T s, T d0, T d1) { return ( isSustainPedal(s,d0) && (d1)>=64 ); }
|
||||
template< typename T> bool isSustainPedalUp( T s, T d0, T d1) { return ( isSustainPedal(s,d0) && (d1)<64 ); }
|
||||
template< typename T> bool isPedal( T s, T d0 ) { return isCtlStatus(s) && kSustainCtlMdId <= (d0) && (d0) <= kLegatoCtlMdId; }
|
||||
template< typename T> bool isPedalDown( T d1 ) { return ( (d1)>=64 ); }
|
||||
template< typename T> bool isPedalUp( T d1 ) { return ( !isPedalDown(d1) ); }
|
||||
template< typename T> bool isPedalDown( T s, T d0, T d1 ) { return ( isPedal(s,d0) && isPedalDown(d1) ); }
|
||||
template< typename T> bool isPedalUp( T s, T d0, T d1 ) { return ( isPedal(s,d0) && isPedalUp(d1) ); }
|
||||
|
||||
template< typename T> bool isSostenutoPedal( T s, T d0 ) { return ( kCtlMdId <= removeCh(s) && removeCh(s) <= (kCtlMdId + kMidiChCnt) && (d0)== kSostenutoCtlMdId ); }
|
||||
template< typename T> bool isSostenutoPedalDown( T s, T d0, T d1) { return ( isSostenutoPedal(s,d0) && (d1)>=64 ); }
|
||||
template< typename T> bool isSostenutoPedalUp( T s, T d0, T d1) { return ( isSostenutoPedal(s,d0) && (d1)<64 ); }
|
||||
template< typename T> bool isSustainPedal( T s, T d0 ) { return isCtlStatus(s) && (d0)==kSustainCtlMdId; }
|
||||
template< typename T> bool isSustainPedalDown( T s, T d0, T d1) { return ( isSustainPedal(s,d0) && isPedalDown(d1) ); }
|
||||
template< typename T> bool isSustainPedalUp( T s, T d0, T d1) { return ( isSustainPedal(s,d0) && isPedalUp(d1) ); }
|
||||
|
||||
template< typename T> bool isSostenutoPedal( T s, T d0 ) { return isCtlStatus(s) && (d0)==kSostenutoCtlMdId; }
|
||||
template< typename T> bool isSostenutoPedalDown( T s, T d0, T d1) { return ( isSostenutoPedal(s,d0) && isPedalDown(d1) ); }
|
||||
template< typename T> bool isSostenutoPedalUp( T s, T d0, T d1) { return ( isSostenutoPedal(s,d0) && isPedalUp(d1) ); }
|
||||
|
||||
template< typename T> bool isSoftPedal( T s, T d0 ) { return isCtlStatus(s) && (d0)==kSoftPedalCtlMdId; }
|
||||
template< typename T> bool isSoftPedalDown( T s, T d0, T d1) { return ( isSoftPedal(s,d0) && isPedalDown(d1)); }
|
||||
template< typename T> bool isSoftPedalUp( T s, T d0, T d1) { return ( isSoftPedal(s,d0) && isPedalUp(d1)); }
|
||||
|
||||
template< typename T> bool isSoftPedal( T s, T d0 ) { return ( kCtlMdId <= removeCh(s) && removeCh(s) <= (kCtlMdId + kMidiChCnt) && (d0)== kSoftPedalCtlMdId ); }
|
||||
template< typename T> bool isSoftPedalDown( T s, T d0, T d1) { return ( isSoftPedal(s,d0) && (d1)>=64 ); }
|
||||
template< typename T> bool isSoftPedalUp( T s, T d0, T d1) { return ( isSoftPedal(s,d0) && (d1)<64 ); }
|
||||
|
||||
template< typename T> bool isPedal( T s, T d0 ) { return ( kCtlMdId <= removeCh(s) && removeCh(s) <= (kCtlMdId + kMidiChCnt) && (d0)>=kSustainCtlMdId && (d0)<=kLegatoCtlMdId ); }
|
||||
template< typename T> bool isPedalDown( T s, T d0, T d1 ) { return ( isPedal(s,d0) && (d1)>=64 ); }
|
||||
template< typename T> bool isPedalUp( T s, T d0, T d1 ) { return ( isPedal(s,d0) && (d1)<64 ); }
|
||||
|
||||
typedef uint8_t byte_t;
|
||||
|
||||
|
969
cwMidiAlsa.cpp
969
cwMidiAlsa.cpp
File diff suppressed because it is too large
Load Diff
44
cwMidiAlsa.h
Normal file
44
cwMidiAlsa.h
Normal file
@ -0,0 +1,44 @@
|
||||
namespace cw
|
||||
{
|
||||
namespace midi
|
||||
{
|
||||
namespace device
|
||||
{
|
||||
namespace alsa
|
||||
{
|
||||
|
||||
typedef handle< struct alsa_device_str> handle_t;
|
||||
|
||||
rc_t create( handle_t& h, cbFunc_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt, const char* appNameStr );
|
||||
rc_t destroy( handle_t& h);
|
||||
bool isInitialized( handle_t h );
|
||||
|
||||
struct pollfd* pollFdArray( handle_t h, unsigned& arrayCntRef );
|
||||
rc_t handleInputMsg( handle_t h );
|
||||
|
||||
unsigned count( handle_t h );
|
||||
const char* name( handle_t h, unsigned devIdx );
|
||||
unsigned nameToIndex(handle_t h, const char* deviceName);
|
||||
unsigned portCount( handle_t h, unsigned devIdx, unsigned flags );
|
||||
const char* portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx );
|
||||
unsigned portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portName );
|
||||
rc_t portEnable( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx, bool enableFl );
|
||||
|
||||
rc_t send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t st, uint8_t d0, uint8_t d1 );
|
||||
rc_t sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt );
|
||||
|
||||
|
||||
// Latency measurment:
|
||||
// Record the time of the next incoming note-on msg
|
||||
// and the time of the next outgoing note-on msg
|
||||
|
||||
// Reset the latency measurement process.
|
||||
void latency_measure_reset(handle_t h);
|
||||
latency_meas_result_t latency_measure_result(handle_t h);
|
||||
|
||||
void report( handle_t h, textBuf::handle_t tbH);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -8,21 +8,25 @@ namespace cw
|
||||
typedef struct msg_str
|
||||
{
|
||||
time::spec_t timeStamp;
|
||||
uint8_t status; // midi status byte
|
||||
uint8_t d0; // midi data byte 0
|
||||
uint8_t d1; // midi data byte 1
|
||||
uint8_t pad;
|
||||
unsigned uid; // application specified id
|
||||
uint8_t ch; // midi channel
|
||||
uint8_t status; // midi status byte (channel has been removed)
|
||||
uint8_t d0; // midi data byte 0
|
||||
uint8_t d1; // midi data byte 1
|
||||
} msg_t;
|
||||
|
||||
typedef struct packet_str
|
||||
{
|
||||
void* cbDataPtr; // Application supplied reference value from mdParserCreate()
|
||||
void* cbArg; // Application supplied reference value
|
||||
unsigned devIdx; // The device the msg originated from
|
||||
unsigned portIdx; // The port index on the source device
|
||||
msg_t* msgArray; // Pointer to an array of 'msgCnt' mdMsg records or NULL if sysExMsg is non-NULL
|
||||
uint8_t* sysExMsg; // Pointer to a sys-ex msg or NULL if msgArray is non-NULL (see note below)
|
||||
unsigned msgCnt; // Count of mdMsg records or sys-ex bytes
|
||||
} packet_t;
|
||||
|
||||
typedef void (*cbFunc_t)( const packet_t* pktArray, unsigned pktCnt );
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
666
cwMidiDevice.cpp
Normal file
666
cwMidiDevice.cpp
Normal file
@ -0,0 +1,666 @@
|
||||
#include "cwCommon.h"
|
||||
#include "cwLog.h"
|
||||
#include "cwCommonImpl.h"
|
||||
#include "cwMem.h"
|
||||
#include "cwTime.h"
|
||||
#include "cwObject.h"
|
||||
#include "cwText.h"
|
||||
#include "cwTextBuf.h"
|
||||
#include "cwThread.h"
|
||||
|
||||
#include "cwMidi.h"
|
||||
#include "cwMidiDecls.h"
|
||||
#include "cwMidiFile.h"
|
||||
#include "cwMidiDevice.h"
|
||||
#include <poll.h>
|
||||
|
||||
#include "cwMidiAlsa.h"
|
||||
#include "cwMidiFileDev.h"
|
||||
|
||||
|
||||
namespace cw
|
||||
{
|
||||
namespace midi
|
||||
{
|
||||
namespace device
|
||||
{
|
||||
typedef enum {
|
||||
kStoppedStateId,
|
||||
kPausedStateId,
|
||||
kPlayingStateId
|
||||
} transportStateId_t;
|
||||
|
||||
typedef struct device_str
|
||||
{
|
||||
cbFunc_t cbFunc;
|
||||
void* cbArg;
|
||||
|
||||
alsa::handle_t alsaDevH;
|
||||
unsigned alsaPollfdN;
|
||||
struct pollfd* alsaPollfdA;
|
||||
unsigned alsa_dev_cnt;
|
||||
|
||||
file_dev::handle_t fileDevH;
|
||||
unsigned file_dev_cnt;
|
||||
|
||||
unsigned total_dev_cnt;
|
||||
|
||||
|
||||
unsigned thread_timeout_microsecs;
|
||||
thread::handle_t threadH;
|
||||
|
||||
transportStateId_t fileDevStateId;
|
||||
|
||||
unsigned long long offset_micros;
|
||||
unsigned long long last_posn_micros;
|
||||
time::spec_t start_time;
|
||||
|
||||
} device_t;
|
||||
|
||||
device_t* _handleToPtr( handle_t h )
|
||||
{ return handleToPtr<handle_t,device_t>(h); }
|
||||
|
||||
rc_t _validate_dev_index( device_t* p, unsigned devIdx )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
if( devIdx >= p->total_dev_cnt )
|
||||
rc = cwLogError(kInvalidArgRC,"Invalid MIDI device index (%i >= %i).",devIdx,p->total_dev_cnt);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
unsigned _devIdxToAlsaDevIdx( device_t* p, unsigned devIdx )
|
||||
{
|
||||
return devIdx >= p->alsa_dev_cnt ? kInvalidIdx : devIdx;
|
||||
}
|
||||
|
||||
unsigned _devIdxToFileDevIdx( device_t* p, unsigned devIdx )
|
||||
{
|
||||
return devIdx==kInvalidIdx || devIdx < p->alsa_dev_cnt ? kInvalidIdx : devIdx - p->alsa_dev_cnt;
|
||||
}
|
||||
|
||||
unsigned _alsaDevIdxToDevIdx( device_t* p, unsigned alsaDevIdx )
|
||||
{ return alsaDevIdx; }
|
||||
|
||||
unsigned _fileDevIdxToDevIdx( device_t* p, unsigned fileDevIdx )
|
||||
{ return fileDevIdx == kInvalidIdx ? kInvalidIdx : p->alsa_dev_cnt + fileDevIdx; }
|
||||
|
||||
bool _isAlsaDevIdx( device_t* p, unsigned devIdx )
|
||||
{ return devIdx==kInvalidIdx ? false : devIdx < p->alsa_dev_cnt; }
|
||||
|
||||
bool _isFileDevIdx( device_t* p, unsigned devIdx )
|
||||
{ return devIdx==kInvalidIdx ? false : (p->alsa_dev_cnt <= devIdx && devIdx < p->total_dev_cnt); }
|
||||
|
||||
|
||||
rc_t _destroy( device_t* p )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
if((rc = destroy(p->threadH)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"MIDI port thread destroy failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
destroy(p->alsaDevH);
|
||||
destroy(p->fileDevH);
|
||||
mem::release(p);
|
||||
|
||||
errLabel:
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool _thread_func( void* arg )
|
||||
{
|
||||
device_t* p = (device_t*)arg;
|
||||
|
||||
unsigned max_sleep_micros = p->thread_timeout_microsecs/2;
|
||||
unsigned sleep_millis = max_sleep_micros/1000;
|
||||
|
||||
if( p->fileDevStateId == kPlayingStateId )
|
||||
{
|
||||
time::spec_t cur_time = time::current_time();
|
||||
unsigned elapsed_micros = time::elapsedMicros(p->start_time,cur_time);
|
||||
|
||||
unsigned long long file_posn_micros = p->offset_micros + elapsed_micros;
|
||||
|
||||
// Send any messages whose time has expired and get the
|
||||
// wait time for the next message.
|
||||
file_dev::exec_result_t r = exec(p->fileDevH,file_posn_micros);
|
||||
|
||||
// If the file dev has no more messages to play then sleep for the maximum time.
|
||||
unsigned file_dev_sleep_micros = r.eof_fl ? max_sleep_micros : r.next_msg_wait_micros;
|
||||
|
||||
// Prevent the wait time from being longer than the thread state change timeout.
|
||||
unsigned sleep_micros = std::min( max_sleep_micros, file_dev_sleep_micros );
|
||||
|
||||
p->last_posn_micros = file_posn_micros + sleep_micros;
|
||||
|
||||
// If the wait time is less than one millisecond then make it one millisecond.
|
||||
// (remember that we allowed the file device to go 3 milliseconds ahead and
|
||||
// and so it is safe, and better for preventing many very short timeout's,
|
||||
// to wait at least 1 millisecond)
|
||||
sleep_millis = std::max(1U, sleep_micros/1000 );
|
||||
}
|
||||
|
||||
|
||||
// Block here waiting for ALSA events or timeout when the next file msg should be sent
|
||||
int sysRC = poll( p->alsaPollfdA, p->alsaPollfdN, sleep_millis );
|
||||
|
||||
if(sysRC == 0 )
|
||||
{
|
||||
// time-out
|
||||
}
|
||||
else
|
||||
{
|
||||
if( sysRC > 0 )
|
||||
{
|
||||
rc_t rc;
|
||||
if((rc = handleInputMsg(p->alsaDevH)) != kOkRC )
|
||||
{
|
||||
cwLogError(rc,"ALSA MIDI dev. input failed");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cwLogSysError(kOpFailRC,sysRC,"MIDI device poll failed.");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // device
|
||||
} // midi
|
||||
} // cw
|
||||
|
||||
|
||||
|
||||
cw::rc_t cw::midi::device::create( handle_t& hRef,
|
||||
cbFunc_t cbFunc,
|
||||
void* cbArg,
|
||||
const char* filePortLabelA[],
|
||||
unsigned max_file_cnt,
|
||||
const char* appNameStr,
|
||||
const char* fileDevName,
|
||||
unsigned fileDevReadAheadMicros,
|
||||
unsigned parserBufByteCnt )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
rc_t rc1 = kOkRC;
|
||||
|
||||
if((rc = destroy(hRef)) != kOkRC )
|
||||
return rc;
|
||||
|
||||
device_t* p = mem::allocZ<device_t>();
|
||||
|
||||
if((rc = create( p->alsaDevH, cbFunc, cbArg, parserBufByteCnt, appNameStr )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"ALSA MIDI device create failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
p->alsa_dev_cnt = count(p->alsaDevH);
|
||||
|
||||
if((rc = create( p->fileDevH, cbFunc, cbArg, p->alsa_dev_cnt, filePortLabelA, max_file_cnt, fileDevName, fileDevReadAheadMicros )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"MIDI file device create failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
p->file_dev_cnt = count(p->fileDevH);
|
||||
p->total_dev_cnt = p->alsa_dev_cnt + p->file_dev_cnt;
|
||||
p->alsaPollfdA = pollFdArray(p->alsaDevH,p->alsaPollfdN);
|
||||
p->fileDevStateId = kStoppedStateId;
|
||||
|
||||
if((rc = thread::create(p->threadH,
|
||||
_thread_func,
|
||||
p,
|
||||
"midi_dev")) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"The MIDI file device thread create failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
p->thread_timeout_microsecs = stateTimeOutMicros(p->threadH);
|
||||
|
||||
hRef.set(p);
|
||||
|
||||
if((rc = unpause(p->threadH)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Initial thread un-pause failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
|
||||
errLabel:
|
||||
if(rc != kOkRC )
|
||||
rc1 = _destroy(p);
|
||||
|
||||
if((rc = rcSelect(rc,rc1)) != kOkRC )
|
||||
rc = cwLogError(rc,"MIDI device mgr. create failed.");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::create( handle_t& h,
|
||||
cbFunc_t cbFunc,
|
||||
void* cbArg,
|
||||
const object_t* args )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
const char* appNameStr = nullptr;
|
||||
const char* fileDevName = "file_dev";
|
||||
unsigned fileDevReadAheadMicros = 3000;
|
||||
unsigned parseBufByteCnt = 1024;
|
||||
const object_t* file_ports = nullptr;
|
||||
const object_t* port = nullptr;
|
||||
|
||||
if((rc = args->getv("appNameStr",appNameStr,
|
||||
"fileDevName",fileDevName,
|
||||
"fileDevReadAheadMicros",fileDevReadAheadMicros,
|
||||
"parseBufByteCnt",parseBufByteCnt,
|
||||
"file_ports",file_ports)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"MIDI port parse args. failed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned fpi = 0;
|
||||
unsigned filePortArgCnt = file_ports->child_count();
|
||||
const char* labelArray[ filePortArgCnt ];
|
||||
memset(labelArray,0,sizeof(labelArray));
|
||||
|
||||
for(unsigned i=0; i<filePortArgCnt; ++i)
|
||||
{
|
||||
if((port = file_ports->child_ele(i)) != nullptr )
|
||||
{
|
||||
if((rc = port->getv("label",labelArray[fpi])) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"MIDI file dev. port arg parse failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
fpi += 1;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
rc = create(h,cbFunc,cbArg,labelArray,fpi,appNameStr,fileDevName,fileDevReadAheadMicros,parseBufByteCnt);
|
||||
|
||||
}
|
||||
|
||||
|
||||
errLabel:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::midi::device::destroy( handle_t& hRef)
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
if( !hRef.isValid() )
|
||||
return rc;
|
||||
|
||||
device_t* p = _handleToPtr(hRef);
|
||||
|
||||
if((rc = _destroy(p)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"MIDI device mgr. destroy failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
hRef.clear();
|
||||
errLabel:
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool cw::midi::device::isInitialized( handle_t h )
|
||||
{ return h.isValid(); }
|
||||
|
||||
unsigned cw::midi::device::count( handle_t h )
|
||||
{
|
||||
device_t* p = _handleToPtr(h);
|
||||
return p->total_dev_cnt;
|
||||
}
|
||||
|
||||
const char* cw::midi::device::name( handle_t h, unsigned devIdx )
|
||||
{
|
||||
device_t* p = _handleToPtr(h);
|
||||
const char* ret_name = nullptr;
|
||||
unsigned alsaDevIdx = kInvalidIdx;
|
||||
unsigned fileDevIdx = kInvalidIdx;
|
||||
|
||||
if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
|
||||
ret_name = name(p->alsaDevH,alsaDevIdx);
|
||||
else
|
||||
{
|
||||
if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx)
|
||||
ret_name = name(p->fileDevH,fileDevIdx);
|
||||
else
|
||||
cwLogError(kInvalidArgRC,"%i is an invalid device index.",devIdx);
|
||||
}
|
||||
|
||||
if( ret_name == nullptr )
|
||||
cwLogError(kOpFailRC,"The name of device index %i could not be found.",devIdx);
|
||||
|
||||
return ret_name;
|
||||
}
|
||||
|
||||
unsigned cw::midi::device::nameToIndex(handle_t h, const char* deviceName)
|
||||
{
|
||||
device_t* p = _handleToPtr(h);
|
||||
unsigned devIdx = kInvalidIdx;
|
||||
|
||||
if((devIdx = nameToIndex(p->alsaDevH,deviceName)) != kInvalidIdx )
|
||||
devIdx = _alsaDevIdxToDevIdx(p,devIdx);
|
||||
else
|
||||
{
|
||||
if((devIdx = nameToIndex(p->fileDevH,deviceName)) != kInvalidIdx )
|
||||
devIdx = _fileDevIdxToDevIdx(p,devIdx);
|
||||
}
|
||||
|
||||
if( devIdx == kInvalidIdx )
|
||||
cwLogError(kOpFailRC,"MIDI device name to index failed on '%s'.",cwStringNullGuard(deviceName));
|
||||
|
||||
return devIdx;
|
||||
}
|
||||
|
||||
unsigned cw::midi::device::portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portNameStr )
|
||||
{
|
||||
device_t* p = _handleToPtr(h);
|
||||
unsigned alsaDevIdx = kInvalidIdx;
|
||||
unsigned fileDevIdx = kInvalidIdx;
|
||||
unsigned portIdx = kInvalidIdx;
|
||||
|
||||
if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
|
||||
portIdx = portNameToIndex(p->alsaDevH,alsaDevIdx,flags,portNameStr);
|
||||
else
|
||||
if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx )
|
||||
portIdx = portNameToIndex(p->fileDevH,fileDevIdx,flags,portNameStr);
|
||||
|
||||
if( portIdx == kInvalidIdx )
|
||||
cwLogError(kInvalidArgRC,"The MIDI port name '%s' could not be found.",cwStringNullGuard(portNameStr));
|
||||
|
||||
return portIdx;
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::portEnable( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx, bool enableFl )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
device_t* p = _handleToPtr(h);
|
||||
unsigned alsaDevIdx = kInvalidIdx;
|
||||
unsigned fileDevIdx = kInvalidIdx;
|
||||
|
||||
if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
|
||||
rc = portEnable(p->alsaDevH,alsaDevIdx,flags,portIdx,enableFl);
|
||||
else
|
||||
if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx )
|
||||
rc = portEnable(p->fileDevH,fileDevIdx,flags,portIdx,enableFl);
|
||||
|
||||
if( rc != kOkRC )
|
||||
rc = cwLogError(rc,"The MIDI port %s failed on dev '%s' port '%s'.",enableFl ? "enable" : "disable", cwStringNullGuard(name(h,devIdx)), cwStringNullGuard(portName(h,devIdx,flags,portIdx)));
|
||||
|
||||
return rc;
|
||||
|
||||
}
|
||||
|
||||
unsigned cw::midi::device::portCount( handle_t h, unsigned devIdx, unsigned flags )
|
||||
{
|
||||
device_t* p = _handleToPtr(h);
|
||||
unsigned alsaDevIdx = kInvalidIdx;
|
||||
unsigned fileDevIdx = kInvalidIdx;
|
||||
unsigned portCnt = 0;
|
||||
|
||||
if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
|
||||
portCnt = portCount(p->alsaDevH,alsaDevIdx,flags);
|
||||
else
|
||||
{
|
||||
if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx )
|
||||
portCnt = portCount(p->fileDevH,fileDevIdx,flags);
|
||||
else
|
||||
cwLogError(kInvalidArgRC,"The device index %i is not valid. Port count access failed.",devIdx);
|
||||
}
|
||||
|
||||
return portCnt;
|
||||
}
|
||||
|
||||
const char* cw::midi::device::portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx )
|
||||
{
|
||||
device_t* p = _handleToPtr(h);
|
||||
unsigned alsaDevIdx = kInvalidIdx;
|
||||
unsigned fileDevIdx = kInvalidIdx;
|
||||
const char* name = nullptr;
|
||||
|
||||
if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
|
||||
name = portName(p->alsaDevH,alsaDevIdx,flags,portIdx);
|
||||
else
|
||||
if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx )
|
||||
name = portName(p->fileDevH,fileDevIdx,flags,portIdx);
|
||||
else
|
||||
cwLogError(kInvalidArgRC,"The device index %i is not valid.");
|
||||
|
||||
if( name == nullptr )
|
||||
cwLogError(kOpFailRC,"The access to port name on device index %i port index %i failed.",devIdx,portIdx);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::midi::device::send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t st, uint8_t d0, uint8_t d1 )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
device_t* p = _handleToPtr(h);
|
||||
unsigned alsaDevIdx = kInvalidIdx;
|
||||
unsigned fileDevIdx = kInvalidIdx;
|
||||
|
||||
if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
|
||||
rc = send(p->alsaDevH,alsaDevIdx,portIdx,st,d0,d1);
|
||||
else
|
||||
{
|
||||
if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx )
|
||||
rc = send(p->fileDevH,fileDevIdx,portIdx,st,d0,d1);
|
||||
else
|
||||
rc = cwLogError(kInvalidArgRC,"The device %i is not valid.",devIdx);
|
||||
}
|
||||
|
||||
if( rc != kOkRC )
|
||||
rc = cwLogError(rc,"The MIDI msg (0x%x %i %i) transmit failed.",st,d0,d1);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
device_t* p = _handleToPtr(h);
|
||||
unsigned alsaDevIdx = kInvalidIdx;
|
||||
unsigned fileDevIdx = kInvalidIdx;
|
||||
|
||||
if((alsaDevIdx = _devIdxToAlsaDevIdx(p,devIdx)) != kInvalidIdx )
|
||||
rc = sendData(p->alsaDevH,alsaDevIdx,portIdx,dataPtr,byteCnt);
|
||||
else
|
||||
{
|
||||
if((fileDevIdx = _devIdxToFileDevIdx(p,devIdx)) != kInvalidIdx )
|
||||
rc = sendData(p->fileDevH,fileDevIdx,portIdx,dataPtr,byteCnt);
|
||||
else
|
||||
rc = cwLogError(kInvalidArgRC,"The device %i is not valid.",devIdx);
|
||||
}
|
||||
|
||||
if( rc != kOkRC )
|
||||
rc = cwLogError(rc,"The MIDI msg transmit data failed.");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::openMidiFile( handle_t h, unsigned devIdx, unsigned portIdx, const char* fname )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
device_t* p = _handleToPtr(h);
|
||||
|
||||
if( _devIdxToFileDevIdx(p,devIdx) == kInvalidIdx )
|
||||
{
|
||||
cwLogError(kInvalidArgRC,"The device index %i does not identify a valid file device.",devIdx);
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if((rc = open_midi_file( p->fileDevH, portIdx, fname)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
|
||||
errLabel:
|
||||
return rc;
|
||||
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::seekToMsg( handle_t h, unsigned devIdx, unsigned portIdx, unsigned msgIdx )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
device_t* p = _handleToPtr(h);
|
||||
|
||||
if(_devIdxToFileDevIdx(p,devIdx) == kInvalidIdx )
|
||||
{
|
||||
cwLogError(kInvalidArgRC,"The device index %i does not identify a valid file device.",devIdx);
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if((rc = seek_to_msg_index( p->fileDevH, portIdx, msgIdx)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
|
||||
errLabel:
|
||||
return rc;
|
||||
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::setEndMsg( handle_t h, unsigned devIdx, unsigned portIdx, unsigned msgIdx )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
device_t* p = _handleToPtr(h);
|
||||
|
||||
if(_devIdxToFileDevIdx(p,devIdx) == kInvalidIdx )
|
||||
{
|
||||
cwLogError(kInvalidArgRC,"The device index %i does not identify a valid file device.",devIdx);
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if((rc = set_end_msg_index( p->fileDevH, portIdx, msgIdx)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
|
||||
errLabel:
|
||||
return rc;
|
||||
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::midi::device::start( handle_t h )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
device_t* p = _handleToPtr(h);
|
||||
|
||||
if( p->fileDevStateId != kPlayingStateId )
|
||||
{
|
||||
|
||||
if((rc = rewind(p->fileDevH)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Rewind failed on MIDI file device.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
p->start_time = time::current_time();
|
||||
p->offset_micros = 0;
|
||||
p->last_posn_micros = 0;
|
||||
p->fileDevStateId = kPlayingStateId;
|
||||
}
|
||||
errLabel:
|
||||
|
||||
if( rc != kOkRC )
|
||||
rc = cwLogError(rc,"MIDI port start failed.");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::stop( handle_t h )
|
||||
{
|
||||
device_t* p = _handleToPtr(h);
|
||||
|
||||
p->fileDevStateId = kStoppedStateId;
|
||||
|
||||
return kOkRC;
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::pause( handle_t h, bool pause_fl )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
device_t* p = _handleToPtr(h);
|
||||
|
||||
switch( p->fileDevStateId )
|
||||
{
|
||||
case kStoppedStateId:
|
||||
// unpausing does nothing from a 'stopped' state
|
||||
break;
|
||||
|
||||
case kPausedStateId:
|
||||
if( !pause_fl )
|
||||
{
|
||||
p->start_time = time::current_time();
|
||||
p->fileDevStateId = kPlayingStateId;
|
||||
}
|
||||
break;
|
||||
|
||||
case kPlayingStateId:
|
||||
if( pause_fl )
|
||||
{
|
||||
p->offset_micros = p->last_posn_micros;
|
||||
p->fileDevStateId = kPausedStateId;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::midi::device::report( handle_t h )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
textBuf::handle_t tbH;
|
||||
|
||||
if((rc = textBuf::create(tbH)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
report(h,tbH);
|
||||
|
||||
errLabel:
|
||||
destroy(tbH);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void cw::midi::device::report( handle_t h, textBuf::handle_t tbH)
|
||||
{
|
||||
device_t* p = _handleToPtr(h);
|
||||
report(p->alsaDevH,tbH);
|
||||
report(p->fileDevH,tbH);
|
||||
}
|
||||
|
||||
|
||||
void cw::midi::device::latency_measure_reset(handle_t h)
|
||||
{
|
||||
device_t* p = _handleToPtr(h);
|
||||
latency_measure_reset(p->alsaDevH);
|
||||
latency_measure_reset(p->fileDevH);
|
||||
}
|
||||
|
||||
cw::midi::device::latency_meas_combined_result_t cw::midi::device::latency_measure_result(handle_t h)
|
||||
{
|
||||
device_t* p = _handleToPtr(h);
|
||||
latency_meas_combined_result_t r;
|
||||
r.alsa_dev = latency_measure_result(p->alsaDevH);
|
||||
r.file_dev = latency_measure_result(p->fileDevH);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
|
89
cwMidiDevice.h
Normal file
89
cwMidiDevice.h
Normal file
@ -0,0 +1,89 @@
|
||||
#ifndef cwMidiPort_H
|
||||
#define cwMidiPort_H
|
||||
|
||||
|
||||
namespace cw
|
||||
{
|
||||
namespace midi
|
||||
{
|
||||
|
||||
// Flags used to identify input and output ports on MIDI devices
|
||||
enum
|
||||
{
|
||||
kInMpFl = 0x01,
|
||||
kOutMpFl = 0x02
|
||||
};
|
||||
|
||||
|
||||
|
||||
namespace device
|
||||
{
|
||||
typedef handle< struct device_str> handle_t;
|
||||
|
||||
|
||||
|
||||
rc_t create( handle_t& h,
|
||||
cbFunc_t cbFunc,
|
||||
void* cbArg,
|
||||
const char* filePortLabelA[], // filePortLabelA[ maxFileCnt ]
|
||||
unsigned maxFileCnt, // count of file dev ports
|
||||
const char* appNameStr,
|
||||
const char* fileDevName = "file_dev",
|
||||
unsigned fileDevReadAheadMicros = 3000,
|
||||
unsigned parserBufByteCnt = 1024 );
|
||||
|
||||
rc_t create( handle_t& h,
|
||||
cbFunc_t cbFunc,
|
||||
void* cbArg,
|
||||
const object_t* args );
|
||||
|
||||
rc_t destroy( handle_t& h);
|
||||
bool isInitialized( handle_t h );
|
||||
|
||||
unsigned count( handle_t h );
|
||||
const char* name( handle_t h, unsigned devIdx );
|
||||
unsigned nameToIndex(handle_t h, const char* deviceName);
|
||||
unsigned portCount( handle_t h, unsigned devIdx, unsigned flags );
|
||||
const char* portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx );
|
||||
unsigned portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portName );
|
||||
rc_t portEnable( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx, bool enableFl );
|
||||
|
||||
rc_t send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t st, uint8_t d0, uint8_t d1 );
|
||||
rc_t sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt );
|
||||
|
||||
rc_t openMidiFile( handle_t h, unsigned devIdx, unsigned portIdx, const char* fname );
|
||||
rc_t seekToMsg( handle_t h, unsigned devIdx, unsigned portIdx, unsigned msgIdx );
|
||||
rc_t setEndMsg( handle_t h, unsigned devIdx, unsigned portidx, unsigned msgIdx );
|
||||
|
||||
rc_t start( handle_t h );
|
||||
rc_t stop( handle_t h );
|
||||
rc_t pause( handle_t h, bool pause_fl );
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
time::spec_t note_on_input_ts;
|
||||
time::spec_t note_on_output_ts;
|
||||
} latency_meas_result_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
latency_meas_result_t alsa_dev;
|
||||
latency_meas_result_t file_dev;
|
||||
} latency_meas_combined_result_t;
|
||||
|
||||
// Reset the latency measurement process. Record the time of the first
|
||||
// incoming note-on msg and the first outgoing note-on msg.
|
||||
void latency_measure_reset(handle_t h);
|
||||
latency_meas_combined_result_t latency_measure_result(handle_t h);
|
||||
|
||||
rc_t report( handle_t h );
|
||||
void report( handle_t h, textBuf::handle_t tbH);
|
||||
|
||||
rc_t testReport();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
389
cwMidiDeviceTest.cpp
Normal file
389
cwMidiDeviceTest.cpp
Normal file
@ -0,0 +1,389 @@
|
||||
#include "cwCommon.h"
|
||||
#include "cwLog.h"
|
||||
#include "cwCommonImpl.h"
|
||||
#include "cwMem.h"
|
||||
#include "cwTime.h"
|
||||
#include "cwObject.h"
|
||||
#include "cwText.h"
|
||||
#include "cwTextBuf.h"
|
||||
#include "cwThread.h"
|
||||
|
||||
#include "cwMidi.h"
|
||||
#include "cwMidiDecls.h"
|
||||
#include "cwMidiFile.h"
|
||||
#include "cwMidiDevice.h"
|
||||
#include "cwMidiDeviceTest.h"
|
||||
|
||||
namespace cw
|
||||
{
|
||||
namespace midi
|
||||
{
|
||||
namespace device
|
||||
{
|
||||
typedef struct test_msg_str
|
||||
{
|
||||
msg_t msg;
|
||||
time::spec_t t;
|
||||
} test_msg_t;
|
||||
|
||||
typedef struct test_str
|
||||
{
|
||||
test_msg_t* msgA;
|
||||
unsigned msgN;
|
||||
unsigned msg_idx;
|
||||
unsigned file_dev_idx;
|
||||
unsigned port_idx;
|
||||
} test_t;
|
||||
|
||||
rc_t _test_create( test_t*& pRef )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
test_t* p = nullptr;
|
||||
|
||||
p = mem::allocZ<test_t>();
|
||||
|
||||
p->msgN = 0;
|
||||
p->msgA = nullptr;
|
||||
p->file_dev_idx = kInvalidIdx;
|
||||
p->port_idx = kInvalidIdx;
|
||||
pRef = p;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc_t _test_open( test_t* p, unsigned fileDevIdx, unsigned portIdx, const char* fname )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
if( p->file_dev_idx == kInvalidIdx )
|
||||
{
|
||||
file::handle_t mfH;
|
||||
|
||||
if((rc = open(mfH,fname)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
p->msgN = msgCount(mfH);
|
||||
p->msgA = mem::allocZ<test_msg_t>(p->msgN);
|
||||
p->file_dev_idx = fileDevIdx;
|
||||
p->port_idx = portIdx;
|
||||
|
||||
close(mfH);
|
||||
|
||||
}
|
||||
|
||||
errLabel:
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc_t _test_destroy( test_t* p )
|
||||
{
|
||||
if( p != nullptr )
|
||||
{
|
||||
mem::release(p->msgA);
|
||||
mem::release(p);
|
||||
}
|
||||
|
||||
return kOkRC;
|
||||
}
|
||||
|
||||
void _test_callback( const packet_t* pktArray, unsigned pktCnt )
|
||||
{
|
||||
unsigned i,j;
|
||||
time::spec_t cur_time = time::current_time();
|
||||
|
||||
for(i=0; i<pktCnt; ++i)
|
||||
{
|
||||
const packet_t* p = pktArray + i;
|
||||
|
||||
test_t* t = (test_t*)p->cbArg;
|
||||
|
||||
for(j=0; j<p->msgCnt; ++j)
|
||||
if( p->msgArray != NULL )
|
||||
{
|
||||
if( t->msg_idx < t->msgN && p->devIdx == t->file_dev_idx && p->portIdx == t->port_idx )
|
||||
{
|
||||
t->msgA[t->msg_idx].msg = p->msgArray[j];
|
||||
t->msgA[t->msg_idx].t = cur_time;
|
||||
t->msg_idx += 1;
|
||||
}
|
||||
|
||||
if( isNoteOn(p->msgArray[j].status,p->msgArray[j].d1) )
|
||||
printf("%ld %ld %i 0x%x %i %i\n", p->msgArray[j].timeStamp.tv_sec, p->msgArray[j].timeStamp.tv_nsec, p->msgArray[j].ch, p->msgArray[j].status,p->msgArray[j].d0, p->msgArray[j].d1);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("0x%x ",p->sysExMsg[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _test_is_not_equal( const file::trackMsg_t* tmsg, const test_msg_t& m )
|
||||
{ return tmsg->status != m.msg.status || tmsg->u.chMsgPtr->d0 != m.msg.d0 || tmsg->u.chMsgPtr->d1 != m.msg.d1; }
|
||||
|
||||
bool _test_is_equal( const file::trackMsg_t* tmsg, const test_msg_t& m )
|
||||
{ return !_test_is_not_equal(tmsg,m); }
|
||||
|
||||
void _test_print( const file::trackMsg_t* tmsg, const test_msg_t& m )
|
||||
{
|
||||
const char* eql_mark = _test_is_equal(tmsg,m) ? "" : "*";
|
||||
printf("%2i 0x%2x %3i %3i : %2i 0x%2x %3i %3i : %s\n",tmsg->u.chMsgPtr->ch, tmsg->status, tmsg->u.chMsgPtr->d0, tmsg->u.chMsgPtr->d1, m.msg.ch, m.msg.status, m.msg.d0, m.msg.d1, eql_mark);
|
||||
}
|
||||
|
||||
void _test_print( unsigned long long t0, const file::trackMsg_t* tmsg, unsigned long long t1, const test_msg_t& m, unsigned dt )
|
||||
{
|
||||
const char* eql_mark = _test_is_equal(tmsg,m) ? "" : "*";
|
||||
printf("%6llu %2i 0x%2x %3i %3i : %6llu %2i 0x%2x %3i %3i : %6i : %s\n",t0, tmsg->u.chMsgPtr->ch, tmsg->status, tmsg->u.chMsgPtr->d0, tmsg->u.chMsgPtr->d1, t1, m.msg.ch, m.msg.status, m.msg.d0, m.msg.d1, dt, eql_mark);
|
||||
}
|
||||
|
||||
|
||||
|
||||
rc_t _test_analyze( test_t* p, const char* fname )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
file::handle_t mfH;
|
||||
const file::trackMsg_t** tmsgA;
|
||||
unsigned tmsgN;
|
||||
unsigned max_diff_micros = 0;
|
||||
unsigned sum_micros = 0;
|
||||
unsigned sum_cnt = 0;
|
||||
unsigned i0 = kInvalidIdx;
|
||||
unsigned j0 = kInvalidIdx;
|
||||
|
||||
// open the MIDI file under test
|
||||
if((rc = open(mfH,fname)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
tmsgA = msgArray(mfH);
|
||||
tmsgN = msgCount(mfH);
|
||||
|
||||
printf("file:%i test:%i\n",tmsgN,p->msg_idx);
|
||||
|
||||
// for file trk msg and recorded msg
|
||||
for(unsigned i=0,j=0; i<tmsgN && j<p->msg_idx; ++i)
|
||||
{
|
||||
// skip non-channel messages
|
||||
if( isChStatus(tmsgA[i]->status))
|
||||
{
|
||||
|
||||
unsigned long long d0 = 0;
|
||||
unsigned long long d1 = 0;
|
||||
unsigned dt = 0;
|
||||
|
||||
// if there is a previous file msg
|
||||
if( i0 != kInvalidIdx )
|
||||
{
|
||||
// get the elapsed time between the cur and prev file msg
|
||||
d0 = tmsgA[i]->amicro - tmsgA[i0]->amicro;
|
||||
|
||||
// if there is a previous recorded msg
|
||||
if( j0 != kInvalidIdx )
|
||||
{
|
||||
// get the time elapsed between the cur and prev recorded msg
|
||||
d1 = time::elapsedMicros(p->msgA[j0].t,p->msgA[j].t);
|
||||
|
||||
dt = (unsigned)(d0>d1 ? d0-d1 : d1-d0);
|
||||
|
||||
sum_micros += dt;
|
||||
sum_cnt += 1;
|
||||
|
||||
if( dt > max_diff_micros )
|
||||
max_diff_micros = dt;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
_test_print(d0, tmsgA[i], d1, p->msgA[j], dt );
|
||||
|
||||
i0 = i;
|
||||
j0 = j;
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
|
||||
printf("max diff:%i avg diff:%i micros\n",max_diff_micros,sum_cnt==0 ? 0 : sum_micros/sum_cnt);
|
||||
|
||||
errLabel:
|
||||
close(mfH);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::test( const object_t* cfg )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
const char* testMidiFname = nullptr;
|
||||
const char* fileDevName = nullptr;
|
||||
const char* testFileLabel = nullptr;
|
||||
bool testFileEnableFl = false;
|
||||
const object_t* file_ports = nullptr;
|
||||
test_t* t = nullptr;
|
||||
bool quit_fl = false;
|
||||
char ch;
|
||||
handle_t h;
|
||||
|
||||
|
||||
if((rc = _test_create( t )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Test create failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if((rc = create(h,_test_callback,t,cfg)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"MIDI dev create failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
report(h);
|
||||
|
||||
if((rc = cfg->getv("fileDevName", fileDevName,
|
||||
"testFileLabel", testFileLabel,
|
||||
"testFileEnableFl", testFileEnableFl,
|
||||
"file_ports", file_ports)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Parse 'file_ports' failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
|
||||
// for each file dev port
|
||||
for(unsigned i=0; i<file_ports->child_count(); ++i)
|
||||
{
|
||||
const char* fname = nullptr;
|
||||
const char* label = nullptr;
|
||||
bool enable_fl = false;
|
||||
unsigned fileDevIdx = kInvalidIdx;
|
||||
unsigned portIdx = kInvalidIdx;
|
||||
|
||||
// parse the file/label pair
|
||||
if((rc = file_ports->child_ele(i)->getv("file",fname,
|
||||
"enable_fl", enable_fl,
|
||||
"label",label)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Parse failed on 'file_port' index %i.",i);
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
// get the file device name
|
||||
if((fileDevIdx = nameToIndex(h,fileDevName)) == kInvalidIdx )
|
||||
{
|
||||
rc = cwLogError(kInvalidArgRC,"Unable to locate the MIDI file device '%s'.",cwStringNullGuard(fileDevName));
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
// get the file/label port index
|
||||
if((portIdx = portNameToIndex(h,fileDevIdx,kInMpFl,label)) == kInvalidIdx )
|
||||
{
|
||||
rc = cwLogError(kInvalidArgRC,"Unable to locate the port '%s' on device '%s'.",cwStringNullGuard(label),cwStringNullGuard(fileDevName));
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
// open the MIDI file on this port
|
||||
if((rc = openMidiFile(h,fileDevIdx,portIdx,fname)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"MIDI file open failed on '%s'.",fname);
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if((rc = portEnable(h,fileDevIdx,kInMpFl,portIdx,enable_fl)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"MIDI file enable failed on '%s'.",fname);
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
// if this is the test port
|
||||
if( testFileEnableFl && testFileLabel != nullptr && textIsEqual(label,testFileLabel) )
|
||||
{
|
||||
testMidiFname = fname;
|
||||
|
||||
cwLogInfo("Test label:%s device:%i fname:%s",testFileLabel,fileDevIdx,fname);
|
||||
|
||||
if((rc = _test_open(t,fileDevIdx,portIdx,fname)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Test create failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cwLogInfo("menu: (q)uit (b)egin (s)top (p)ause (u)npause (n)ote-on\n");
|
||||
|
||||
while( !quit_fl)
|
||||
{
|
||||
ch = getchar();
|
||||
|
||||
switch(ch)
|
||||
{
|
||||
case 'q':
|
||||
quit_fl = true;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
printf("starting ...\n");
|
||||
start(h);
|
||||
break;
|
||||
|
||||
case 's':
|
||||
printf("stopping ...\n");
|
||||
stop(h);
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
printf("pausing ...\n");
|
||||
pause(h,true);
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
printf("unpausing ...\n");
|
||||
pause(h,false);
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
printf("sending ...\n");
|
||||
send(h,2,0,0x90,60,60);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
errLabel:
|
||||
|
||||
if( testMidiFname != nullptr )
|
||||
_test_analyze(t,testMidiFname);
|
||||
|
||||
destroy(h);
|
||||
_test_destroy(t);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::midi::device::testReport()
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
textBuf::handle_t tbH;
|
||||
handle_t h;
|
||||
|
||||
if((rc = create(h,nullptr,nullptr,nullptr,0,"test_report")) != kOkRC )
|
||||
return rc;
|
||||
|
||||
// create a text buffer to hold the MIDI system report text
|
||||
if((rc = textBuf::create(tbH)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
// generate and print the MIDI system report
|
||||
report(h,tbH);
|
||||
cwLogInfo("%s",textBuf::text(tbH));
|
||||
|
||||
errLabel:
|
||||
textBuf::destroy(tbH);
|
||||
destroy(h);
|
||||
return rc;
|
||||
|
||||
}
|
10
cwMidiDeviceTest.h
Normal file
10
cwMidiDeviceTest.h
Normal file
@ -0,0 +1,10 @@
|
||||
namespace cw
|
||||
{
|
||||
namespace midi
|
||||
{
|
||||
namespace device
|
||||
{
|
||||
rc_t test( const object_t* cfg );
|
||||
}
|
||||
}
|
||||
}
|
@ -152,7 +152,7 @@ namespace cw
|
||||
const trackMsg_t* trackMsg( handle_t h, unsigned trackIdx );
|
||||
|
||||
// Returns the total count of records in the midi file and the
|
||||
// number in the array returned by cmMidiFileMsgArray().
|
||||
// number in the array returned by msgArray().
|
||||
// Return kInvalidCnt if 'h' is invalid.
|
||||
unsigned msgCount( handle_t h );
|
||||
|
||||
@ -221,7 +221,7 @@ namespace cw
|
||||
|
||||
rc_t genCsvFile( const char* midiFn, const char* csvFn, bool printWarningsFl=true );
|
||||
|
||||
// Generate a text file reportusing cmMIdiFilePrintMsgs()
|
||||
// Generate a text file report using printMsgs()
|
||||
rc_t report( const char* midiFn, log::handle_t logH );
|
||||
|
||||
rc_t report_begin_end( const char* midiFn, unsigned msg_cnt=10, bool pitch_only_fl=true, log::handle_t logH=log::handle_t() );
|
||||
|
922
cwMidiFileDev.cpp
Normal file
922
cwMidiFileDev.cpp
Normal file
@ -0,0 +1,922 @@
|
||||
#include "cwCommon.h"
|
||||
#include "cwLog.h"
|
||||
#include "cwCommonImpl.h"
|
||||
#include "cwMem.h"
|
||||
#include "cwTime.h"
|
||||
#include "cwFile.h"
|
||||
#include "cwObject.h"
|
||||
#include "cwFileSys.h"
|
||||
#include "cwText.h"
|
||||
#include "cwTextBuf.h"
|
||||
#include "cwMidi.h"
|
||||
#include "cwMidiDecls.h"
|
||||
#include "cwMidiFile.h"
|
||||
#include "cwMidiDevice.h"
|
||||
#include <poll.h>
|
||||
#include "cwMidiFileDev.h"
|
||||
|
||||
namespace cw
|
||||
{
|
||||
namespace midi
|
||||
{
|
||||
namespace device
|
||||
{
|
||||
namespace file_dev
|
||||
{
|
||||
|
||||
typedef struct file_msg_str
|
||||
{
|
||||
unsigned long long amicro; //
|
||||
msg_t* msg; // msg_t as declared in cwMidiDecls.h
|
||||
unsigned file_idx;
|
||||
unsigned msg_idx;
|
||||
} file_msg_t;
|
||||
|
||||
|
||||
typedef struct file_str
|
||||
{
|
||||
char* label;
|
||||
char* fname;
|
||||
|
||||
msg_t* msgA;
|
||||
unsigned msgN;
|
||||
|
||||
bool enable_fl;
|
||||
} file_t;
|
||||
|
||||
typedef struct file_dev_str
|
||||
{
|
||||
cbFunc_t cbFunc;
|
||||
void* cbArg;
|
||||
|
||||
file_t* fileA; // fileA[ fileN ]
|
||||
unsigned fileN; //
|
||||
|
||||
file_msg_t* msgA; // msgA[ msgN ]
|
||||
unsigned msgAllocN; //
|
||||
unsigned msgN;
|
||||
|
||||
unsigned devCnt; // always 1
|
||||
unsigned base_dev_idx;
|
||||
char* dev_name;
|
||||
|
||||
bool is_activeFl;
|
||||
|
||||
// The following indexes are all int p->msgA[ p->msgN ]
|
||||
unsigned beg_msg_idx; // beg_msg_idx is the first msg to transmit
|
||||
unsigned end_msg_idx; // end_msg_idx indicates the last msg to transmit, end_msg_idx+1 will not be transmitted
|
||||
unsigned next_wr_msg_idx; // next_wr_msg_idx is the next msg to transmit
|
||||
unsigned next_rd_msg_idx; // next_rd_msg_idx is the last msg transmitted
|
||||
|
||||
unsigned long long start_delay_micros;
|
||||
|
||||
unsigned long long read_ahead_micros;
|
||||
|
||||
bool latency_meas_enable_in_fl;
|
||||
bool latency_meas_enable_out_fl;
|
||||
latency_meas_result_t latency_meas_result;
|
||||
|
||||
} file_dev_t;
|
||||
|
||||
file_dev_t * _handleToPtr(handle_t h)
|
||||
{ return handleToPtr<handle_t,file_dev_t>(h); }
|
||||
|
||||
rc_t _validate_file_index(file_dev_t* p, unsigned file_idx)
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
if( file_idx >= p->fileN )
|
||||
rc = cwLogError(kInvalidArgRC,"The MIDI device file/port index %i is invalid.",file_idx);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool _file_exists( const file_t& r )
|
||||
{ return r.msgN > 0; }
|
||||
|
||||
rc_t _validate_file_existence(file_dev_t* p, unsigned file_idx )
|
||||
{
|
||||
rc_t rc;
|
||||
if((rc = _validate_file_index(p,file_idx)) == kOkRC )
|
||||
if( !_file_exists(p->fileA[file_idx]) )
|
||||
rc = cwLogError(kInvalidArgRC,"The MIDI device file at file/port index %i does not exist.",file_idx);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc_t _validate_dev_index(file_dev_t* p, unsigned dev_idx )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
if( dev_idx >= p->devCnt )
|
||||
rc = cwLogError(kInvalidArgRC,"The MIDI file device index %i is invalid.",dev_idx );
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc_t _validate_port_index(file_dev_t* p, unsigned port_idx )
|
||||
{ return _validate_file_index(p,port_idx); }
|
||||
|
||||
void _reset_indexes( file_dev_t* p )
|
||||
{
|
||||
p->beg_msg_idx = kInvalidIdx;
|
||||
p->end_msg_idx = kInvalidIdx;
|
||||
p->next_wr_msg_idx = kInvalidIdx;
|
||||
p->next_rd_msg_idx = kInvalidIdx;
|
||||
}
|
||||
|
||||
rc_t _close_file( file_dev_t* p, unsigned file_idx )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
|
||||
if((rc = _validate_file_index(p,file_idx)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
if( _file_exists(p->fileA[file_idx] ) )
|
||||
{
|
||||
// if the beg/end msg index refers to the file being closed then invalidate the beg/end msg idx
|
||||
if( p->beg_msg_idx != kInvalidIdx && p->beg_msg_idx < p->msgN && p->msgA[ p->beg_msg_idx ].file_idx == file_idx )
|
||||
p->beg_msg_idx = kInvalidIdx;
|
||||
|
||||
if( p->end_msg_idx != kInvalidIdx && p->end_msg_idx < p->msgN && p->msgA[ p->end_msg_idx ].file_idx == file_idx )
|
||||
p->end_msg_idx = kInvalidIdx;
|
||||
|
||||
mem::release(p->fileA[file_idx].fname);
|
||||
p->fileA[file_idx].msgN = 0;
|
||||
}
|
||||
|
||||
|
||||
errLabel:
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc_t _destroy( file_dev_t* p )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
for(unsigned i=0; i<p->fileN; ++i)
|
||||
{
|
||||
mem::release(p->fileA[i].msgA);
|
||||
mem::release(p->fileA[i].label);
|
||||
mem::release(p->fileA[i].fname);
|
||||
}
|
||||
|
||||
mem::release(p->fileA);
|
||||
mem::release(p->msgA);
|
||||
mem::release(p->dev_name);
|
||||
mem::release(p);
|
||||
//errLabel:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
rc_t _open_midi_file( file_dev_t* p, unsigned file_idx, const char* fname )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
midi::file::handle_t mfH;
|
||||
|
||||
if((rc = open(mfH, fname)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"MIDI file open failed on '%s'.",cwStringNullGuard(fname));
|
||||
goto errLabel;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned msg_idx = 0;
|
||||
unsigned msgN = msgCount(mfH);;
|
||||
const file::trackMsg_t** fileMsgPtrA = msgArray(mfH);
|
||||
|
||||
p->fileA[file_idx].msgA = mem::resizeZ<msg_t>(p->fileA[file_idx].msgA,msgN);
|
||||
|
||||
for(unsigned j=0; j<msgN; ++j)
|
||||
if( isChStatus(fileMsgPtrA[j]->status) )
|
||||
{
|
||||
msg_t* m = p->fileA[file_idx].msgA + msg_idx;
|
||||
|
||||
m->uid = j;
|
||||
m->ch = fileMsgPtrA[j]->u.chMsgPtr->ch;
|
||||
m->status = fileMsgPtrA[j]->status;
|
||||
m->d0 = fileMsgPtrA[j]->u.chMsgPtr->d0;
|
||||
m->d1 = fileMsgPtrA[j]->u.chMsgPtr->d1;
|
||||
m->timeStamp = time::microsecondsToSpec(fileMsgPtrA[j]->amicro);
|
||||
|
||||
msg_idx += 1;
|
||||
}
|
||||
|
||||
p->fileA[file_idx].msgN = msg_idx;
|
||||
p->fileA[file_idx].fname = mem::duplStr(fname);
|
||||
close( mfH );
|
||||
|
||||
}
|
||||
|
||||
|
||||
errLabel:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
unsigned _calc_msg_count( file_dev_t* p )
|
||||
{
|
||||
unsigned msgAllocN = 0;
|
||||
for(unsigned i=0; i<p->fileN; ++i)
|
||||
if( p->fileA[i].msgA != nullptr )
|
||||
msgAllocN += p->fileA[i].msgN;
|
||||
|
||||
return msgAllocN;
|
||||
}
|
||||
|
||||
// Set msg_idx to kInvalidIdx to seek to the current value of p->beg_msg_idx
|
||||
// or 0 if p->beg_msg_idx was never set.
|
||||
// If msg_idx is a valid msg index then it will be assigned to p->beg_msg_idx
|
||||
rc_t _seek_to_msg_index( file_dev_t* p, unsigned file_idx, unsigned msg_idx )
|
||||
{
|
||||
rc_t rc;
|
||||
unsigned i = 0;
|
||||
unsigned beg_msg_idx = p->beg_msg_idx;
|
||||
|
||||
if((rc = _validate_file_existence(p,file_idx)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
// if no target msg was given ...
|
||||
if( msg_idx == kInvalidIdx )
|
||||
{
|
||||
// ... then use the previous target msg or 0
|
||||
beg_msg_idx = p->beg_msg_idx == kInvalidIdx ? 0 : p->beg_msg_idx;
|
||||
}
|
||||
else // if a target msg was given ..
|
||||
{
|
||||
// locate the msg in p->msgA[]
|
||||
for(i=0; i<p->msgN; ++i)
|
||||
if( p->msgA[i].file_idx == file_idx && p->msgA[i].msg_idx == msg_idx )
|
||||
{
|
||||
p->beg_msg_idx = i;
|
||||
beg_msg_idx = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if( i == p->msgN )
|
||||
{
|
||||
rc = cwLogError(kEleNotFoundRC,"The 'begin' MIDI file event at index %i in %s was not found.",msg_idx,cwStringNullGuard(p->fileA[file_idx].label));
|
||||
goto errLabel;
|
||||
}
|
||||
}
|
||||
|
||||
p->next_wr_msg_idx = beg_msg_idx;
|
||||
p->next_rd_msg_idx = beg_msg_idx;
|
||||
|
||||
errLabel:
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Set file_idx and msg_idx to kInvalidIdx to make the p->msgN the end index.
|
||||
// Set msg_idx to kInvalidIdx to make the last p->fileA[file_idx].msgN the end index.
|
||||
rc_t _set_end_msg_index( file_dev_t* p, unsigned file_idx, unsigned msg_idx )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
unsigned i = 0;
|
||||
|
||||
if( file_idx == kInvalidIdx )
|
||||
p->end_msg_idx = p->msgN - 1;
|
||||
else
|
||||
{
|
||||
if((rc = _validate_file_existence(p,file_idx)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
if( msg_idx == kInvalidIdx )
|
||||
msg_idx = p->fileA[ file_idx ].msgN - 1;
|
||||
|
||||
// locate the msg in p->msgA[]
|
||||
for(i=0; i<p->msgN; ++i)
|
||||
if( p->msgA[i].file_idx == file_idx && p->msgA[i].msg_idx == msg_idx )
|
||||
{
|
||||
p->end_msg_idx = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if( i == p->msgN )
|
||||
{
|
||||
rc = cwLogError(kEleNotFoundRC,"The 'end' MIDI file event at index %i in %s was not found.",msg_idx,cwStringNullGuard(p->fileA[file_idx].label));
|
||||
goto errLabel;
|
||||
}
|
||||
}
|
||||
|
||||
errLabel:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
unsigned _fill_msg_array_from_msg( file_dev_t* p, unsigned file_idx, msg_t* msgA, unsigned msgN, unsigned msg_idx )
|
||||
{
|
||||
unsigned k = msg_idx;
|
||||
|
||||
for(unsigned j=0; j<msgN; ++j)
|
||||
if( isChStatus(msgA[j].status) )
|
||||
{
|
||||
p->msgA[k].msg_idx = j;
|
||||
p->msgA[k].amicro = time::specToMicroseconds(msgA[j].timeStamp);
|
||||
p->msgA[k].msg = msgA + j;
|
||||
p->msgA[k].file_idx = file_idx;
|
||||
++k;
|
||||
}
|
||||
|
||||
return k;
|
||||
|
||||
}
|
||||
|
||||
void _fill_msg_array( file_dev_t* p )
|
||||
{
|
||||
unsigned msg_idx = 0;
|
||||
for(unsigned i=0; i<p->fileN; ++i)
|
||||
if( p->fileA[i].msgA != nullptr )
|
||||
msg_idx = _fill_msg_array_from_msg(p,i,p->fileA[i].msgA,p->fileA[i].msgN,msg_idx);
|
||||
|
||||
p->msgN = msg_idx;
|
||||
|
||||
}
|
||||
|
||||
// Combine all the file msg's into a single array and sort them on file_msg_t.amicro.
|
||||
rc_t _prepare_msg_array( file_dev_t* p )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
// save the current starting message
|
||||
unsigned beg_file_idx = p->beg_msg_idx==kInvalidIdx ? kInvalidIdx : p->msgA[ p->beg_msg_idx ].file_idx;
|
||||
unsigned beg_msg_idx = p->beg_msg_idx==kInvalidIdx ? kInvalidIdx : p->msgA[ p->beg_msg_idx ].msg_idx;
|
||||
|
||||
// if the 'beg' file does not exist
|
||||
if( beg_file_idx != kInvalidIdx && _file_exists(p->fileA[ beg_file_idx ])==false )
|
||||
{
|
||||
beg_file_idx = kInvalidIdx;
|
||||
beg_msg_idx = kInvalidIdx;
|
||||
}
|
||||
|
||||
// save the current ending message
|
||||
unsigned end_file_idx = p->end_msg_idx==kInvalidIdx ? kInvalidIdx : p->msgA[ p->end_msg_idx ].file_idx;
|
||||
unsigned end_msg_idx = p->end_msg_idx==kInvalidIdx ? kInvalidIdx : p->msgA[ p->end_msg_idx ].msg_idx;
|
||||
|
||||
// if the 'end' file does not exist
|
||||
if( end_file_idx != kInvalidIdx && _file_exists(p->fileA[ end_file_idx ])==false )
|
||||
{
|
||||
end_file_idx = kInvalidIdx;
|
||||
end_msg_idx = kInvalidIdx;
|
||||
}
|
||||
|
||||
|
||||
// calc the count of message in all the files
|
||||
p->msgAllocN = _calc_msg_count(p);
|
||||
|
||||
// allocate a single array to hold the messages from all the files
|
||||
p->msgA = mem::resize<file_msg_t>(p->msgA,p->msgAllocN);
|
||||
|
||||
// fill p->msgA[] from each of the files
|
||||
_fill_msg_array(p);
|
||||
|
||||
// sort p->msgA[] on msgA[].amicro
|
||||
auto f = [](const file_msg_t& a0,const file_msg_t& a1) -> bool { return a0.amicro < a1.amicro; };
|
||||
std::sort(p->msgA,p->msgA+p->msgN,f);
|
||||
|
||||
// by default we rewind to the first msg
|
||||
p->next_wr_msg_idx = 0;
|
||||
p->next_rd_msg_idx = 0;
|
||||
|
||||
// if a valid seek position exists then reset it
|
||||
if( beg_file_idx != kInvalidIdx && beg_msg_idx != kInvalidIdx )
|
||||
if((rc = _seek_to_msg_index( p, beg_file_idx, beg_msg_idx )) != kOkRC )
|
||||
rc = cwLogError(rc,"The MIDI file device starting output message could not be restored.");
|
||||
|
||||
if( end_file_idx != kInvalidIdx && end_msg_idx != kInvalidIdx )
|
||||
if((rc = _set_end_msg_index(p, end_file_idx, end_msg_idx )) != kOkRC )
|
||||
rc = cwLogError(rc,"The MIDI file device ending output message could not be restored.");
|
||||
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void _update_active_flag( file_dev_t* p )
|
||||
{
|
||||
unsigned i=0;
|
||||
for(; i<p->fileN; ++i)
|
||||
if( _file_exists(p->fileA[i]) && p->fileA[i].enable_fl )
|
||||
break;
|
||||
|
||||
p->is_activeFl = i < p->fileN;
|
||||
}
|
||||
|
||||
rc_t _enable_file( handle_t h, unsigned file_idx, bool enable_fl )
|
||||
{
|
||||
rc_t rc;
|
||||
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
|
||||
if((rc = _validate_file_existence(p, file_idx)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
p->fileA[ file_idx ].enable_fl = enable_fl;
|
||||
|
||||
_update_active_flag(p);
|
||||
|
||||
errLabel:
|
||||
|
||||
if(rc != kOkRC )
|
||||
rc = cwLogError(rc,"MIDI file device %s failed on file index %i.", enable_fl ? "enable" : "disable", file_idx );
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void _callback( file_dev_t* p, unsigned file_idx, msg_t* msgA, unsigned msgN )
|
||||
{
|
||||
if( p->cbFunc != nullptr )
|
||||
{
|
||||
packet_t pkt = {
|
||||
.cbArg = p->cbArg,
|
||||
.devIdx = p->base_dev_idx,
|
||||
.portIdx = file_idx,
|
||||
.msgArray = msgA,
|
||||
.msgCnt = msgN
|
||||
};
|
||||
|
||||
p->cbFunc( &pkt, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
void _packetize_and_transmit_msgs( file_dev_t* p, unsigned xmt_msg_cnt )
|
||||
{
|
||||
msg_t msgA[ xmt_msg_cnt ];
|
||||
|
||||
unsigned pkt_msg_idx = 0;
|
||||
unsigned file_idx_0 = kInvalidIdx;
|
||||
|
||||
assert( p->next_rd_msg_idx != kInvalidIdx && p->next_wr_msg_idx != kInvalidIdx );
|
||||
|
||||
for(; p->next_rd_msg_idx < p->next_wr_msg_idx && pkt_msg_idx < xmt_msg_cnt; ++p->next_rd_msg_idx)
|
||||
{
|
||||
const file_msg_t& m = p->msgA[ p->next_rd_msg_idx ];
|
||||
|
||||
if( p->fileA[ m.file_idx ].enable_fl && m.msg != nullptr )
|
||||
{
|
||||
//
|
||||
if( p->latency_meas_enable_in_fl && isNoteOn(m.msg->status,m.msg->d1) )
|
||||
{
|
||||
p->latency_meas_enable_in_fl = false;
|
||||
time::get(p->latency_meas_result.note_on_input_ts);
|
||||
}
|
||||
|
||||
// if the file_idx is not the same as the previous messages then
|
||||
// send the currently stored messages from msgA[] - because packet
|
||||
// messages must all belong to the same port
|
||||
if( file_idx_0 != kInvalidIdx && m.file_idx != file_idx_0 )
|
||||
{
|
||||
_callback(p,file_idx_0,msgA,pkt_msg_idx);
|
||||
pkt_msg_idx = 0;
|
||||
}
|
||||
|
||||
msgA[pkt_msg_idx] = *m.msg;
|
||||
file_idx_0 = m.file_idx;
|
||||
pkt_msg_idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if( pkt_msg_idx > 0 )
|
||||
_callback(p,file_idx_0,msgA,pkt_msg_idx);
|
||||
}
|
||||
|
||||
rc_t _load_messages( handle_t h, unsigned file_idx, const char* fname, const msg_t* msgA, unsigned msgN )
|
||||
{
|
||||
rc_t rc;
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
|
||||
if((rc = _validate_file_index(p,file_idx)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
if((rc = _close_file(p,file_idx)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
if( fname != nullptr )
|
||||
{
|
||||
if((rc = _open_midi_file(p,file_idx,fname)) != kOkRC )
|
||||
goto errLabel;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( msgA != nullptr )
|
||||
{
|
||||
p->fileA[ file_idx ].msgA = mem::allocZ<msg_t>(msgN);
|
||||
p->fileA[ file_idx ].msgN = msgN;
|
||||
memcpy(p->fileA[ file_idx ].msgA, msgA, msgN*sizeof(msg_t));
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
if((rc = _prepare_msg_array(p)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
p->fileA[ file_idx ].enable_fl = true;
|
||||
|
||||
errLabel:
|
||||
|
||||
_update_active_flag(p);
|
||||
|
||||
if( rc != kOkRC )
|
||||
rc = cwLogError(rc,"MIDI file device msg. load port failed %i.",file_idx);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::create( handle_t& hRef,
|
||||
cbFunc_t cbFunc,
|
||||
void* cbArg,
|
||||
unsigned baseDevIdx,
|
||||
const char* labelA[],
|
||||
unsigned max_file_cnt,
|
||||
const char* dev_name,
|
||||
unsigned read_ahead_micros)
|
||||
{
|
||||
rc_t rc;
|
||||
|
||||
if((rc = destroy(hRef)) != kOkRC )
|
||||
return rc;
|
||||
|
||||
file_dev_t* p = mem::allocZ<file_dev_t>();
|
||||
|
||||
p->cbFunc = cbFunc;
|
||||
p->cbArg = cbArg;
|
||||
p->fileN = max_file_cnt;
|
||||
p->fileA = mem::allocZ<file_t>(p->fileN);
|
||||
p->devCnt = 1;
|
||||
p->is_activeFl = false;
|
||||
p->read_ahead_micros = read_ahead_micros;
|
||||
p->base_dev_idx = baseDevIdx;
|
||||
p->dev_name = mem::duplStr(dev_name);
|
||||
|
||||
_reset_indexes(p);
|
||||
|
||||
for(unsigned i=0; i<p->fileN; ++i)
|
||||
{
|
||||
if( labelA[i] != nullptr )
|
||||
{
|
||||
p->fileA[i].label = mem::duplStr(labelA[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = cwLogError(kInvalidArgRC,"Count of MIDI file device labels must match the max file count.");
|
||||
goto errLabel;
|
||||
}
|
||||
}
|
||||
|
||||
hRef.set(p);
|
||||
|
||||
errLabel:
|
||||
if(rc != kOkRC )
|
||||
_destroy(p);
|
||||
return rc;
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::destroy( handle_t& hRef )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
if(!hRef.isValid() )
|
||||
return rc;
|
||||
|
||||
file_dev_t* p = _handleToPtr(hRef);
|
||||
|
||||
if((rc = _destroy(p)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"MIDI file device destroy failed.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
hRef.clear();
|
||||
|
||||
errLabel:
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool cw::midi::device::file_dev::is_active( handle_t h )
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
return p->is_activeFl;
|
||||
}
|
||||
|
||||
unsigned cw::midi::device::file_dev::file_count( handle_t h )
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
return p->fileN;
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::open_midi_file( handle_t h, unsigned file_idx, const char* fname )
|
||||
{
|
||||
return _load_messages(h,file_idx,fname,nullptr,0);
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::load_messages( handle_t h, unsigned file_idx, const msg_t* msgA, unsigned msgN )
|
||||
{
|
||||
return _load_messages(h,file_idx,nullptr,msgA,msgN);
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::enable_file( handle_t h, unsigned file_idx, bool enableFl )
|
||||
{ return _enable_file(h,file_idx,enableFl); }
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::enable_file( handle_t h, unsigned file_idx )
|
||||
{ return _enable_file(h,file_idx,true); }
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::disable_file( handle_t h,unsigned file_idx )
|
||||
{ return _enable_file(h,file_idx,false); }
|
||||
|
||||
|
||||
unsigned cw::midi::device::file_dev::count( handle_t h )
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
return p->devCnt;
|
||||
}
|
||||
|
||||
const char* cw::midi::device::file_dev::name( handle_t h, unsigned devIdx )
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
|
||||
return _validate_dev_index(p,devIdx)==kOkRC ? p->dev_name : nullptr;
|
||||
}
|
||||
|
||||
unsigned cw::midi::device::file_dev::nameToIndex(handle_t h, const char* deviceName)
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
return textIsEqual(deviceName,p->dev_name) ? 0 : kInvalidIdx;
|
||||
}
|
||||
|
||||
unsigned cw::midi::device::file_dev::portCount( handle_t h, unsigned devIdx, unsigned flags )
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
|
||||
if(_validate_dev_index(p,devIdx) != kOkRC )
|
||||
return 0;
|
||||
|
||||
return flags & kInMpFl ? p->fileN : 0;
|
||||
}
|
||||
|
||||
const char* cw::midi::device::file_dev::portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx )
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
|
||||
if( _validate_dev_index(p,devIdx) != kOkRC )
|
||||
return nullptr;
|
||||
|
||||
if( _validate_port_index(p,portIdx) != kOkRC )
|
||||
return nullptr;
|
||||
|
||||
return p->fileA[portIdx].label;
|
||||
}
|
||||
|
||||
unsigned cw::midi::device::file_dev::portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portName )
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
|
||||
if( _validate_dev_index(p,devIdx) != kOkRC )
|
||||
return kInvalidIdx;
|
||||
|
||||
if( flags & kInMpFl )
|
||||
{
|
||||
for(unsigned i=0; i<p->fileN; ++i)
|
||||
if( textIsEqual(p->fileA[i].label,portName) )
|
||||
return i;
|
||||
}
|
||||
return kInvalidIdx;
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::portEnable( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx, bool enableFl )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
|
||||
if((rc = _validate_dev_index(p,devIdx)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
if( flags & kInMpFl )
|
||||
{
|
||||
rc = enable_file(h,portIdx,enableFl);
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = cwLogError(kNotImplementedRC,"MIDI file dev output enable/disable has not been implemented.");
|
||||
}
|
||||
|
||||
|
||||
errLabel:
|
||||
if(rc != kOkRC )
|
||||
rc = cwLogError(rc,"MIDI file dev port enable/disable failed.");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::seek_to_msg_index( handle_t h, unsigned file_idx, unsigned msg_idx )
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
|
||||
return _seek_to_msg_index(p,file_idx,msg_idx);
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::set_end_msg_index( handle_t h, unsigned file_idx, unsigned msg_idx )
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
return _set_end_msg_index(p, file_idx, msg_idx );
|
||||
}
|
||||
|
||||
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::rewind( handle_t h )
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
|
||||
if( p->beg_msg_idx == kInvalidIdx )
|
||||
{
|
||||
p->next_wr_msg_idx = 0;
|
||||
p->next_rd_msg_idx = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
p->next_wr_msg_idx = p->beg_msg_idx;
|
||||
p->next_rd_msg_idx = p->beg_msg_idx;
|
||||
}
|
||||
|
||||
return kOkRC;
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::set_start_delay( handle_t h, unsigned start_delay_micros )
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
|
||||
p->start_delay_micros = start_delay_micros;
|
||||
|
||||
return kOkRC;
|
||||
|
||||
}
|
||||
|
||||
cw::midi::device::file_dev::exec_result_t cw::midi::device::file_dev::exec( handle_t h, unsigned long long cur_time_us )
|
||||
{
|
||||
exec_result_t r;
|
||||
|
||||
file_dev_t*p = _handleToPtr(h);
|
||||
|
||||
// p->end_msg_idx indicates the last msg to transmit, but we wait until the msg following p->end_msg_idx to
|
||||
// actually stop transmitting and set r.eof_fl. This will facilitate providing a natural time gap to loop to the beginning.
|
||||
unsigned end_msg_idx = p->end_msg_idx == kInvalidIdx ? p->msgN-1 : p->end_msg_idx;
|
||||
|
||||
|
||||
// if there are no messages left to send
|
||||
if( p->next_wr_msg_idx==kInvalidIdx || p->next_wr_msg_idx >= p->msgN || p->next_wr_msg_idx > p->end_msg_idx )
|
||||
{
|
||||
r.next_msg_wait_micros = 0;
|
||||
r.xmit_cnt = 0;
|
||||
r.eof_fl = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if( cur_time_us < p->start_delay_micros )
|
||||
{
|
||||
r.xmit_cnt = 0;
|
||||
r.eof_fl = false;
|
||||
r.next_msg_wait_micros = p->start_delay_micros - cur_time_us;
|
||||
}
|
||||
else
|
||||
{
|
||||
cur_time_us -= p->start_delay_micros;
|
||||
|
||||
unsigned base_msg_idx = p->beg_msg_idx == kInvalidIdx ? 0 : p->beg_msg_idx;
|
||||
|
||||
assert( base_msg_idx <= p->next_wr_msg_idx );
|
||||
|
||||
unsigned long long msg_time_us = p->msgA[ p->next_wr_msg_idx ].amicro - p->msgA[ base_msg_idx ].amicro;
|
||||
unsigned xmit_cnt = 0;
|
||||
unsigned long long end_time_us = cur_time_us + p->read_ahead_micros;
|
||||
|
||||
// for all msgs <= current time + read_ahead_micros
|
||||
while( msg_time_us <= end_time_us )
|
||||
{
|
||||
//
|
||||
// consume msg here
|
||||
//
|
||||
|
||||
xmit_cnt += 1;
|
||||
|
||||
// advance to next msg
|
||||
p->next_wr_msg_idx += 1;
|
||||
|
||||
// check for EOF
|
||||
if( p->next_wr_msg_idx >= p->msgN)
|
||||
break;
|
||||
|
||||
// time of next msg
|
||||
msg_time_us = p->msgA[ p->next_wr_msg_idx ].amicro - p->msgA[ base_msg_idx ].amicro;
|
||||
|
||||
// if we went past the end msg then stop
|
||||
if( p->next_wr_msg_idx > end_msg_idx )
|
||||
break;
|
||||
}
|
||||
|
||||
assert( p->next_wr_msg_idx >= p->msgN || msg_time_us > cur_time_us );
|
||||
|
||||
r.xmit_cnt = xmit_cnt;
|
||||
r.eof_fl = p->next_wr_msg_idx >= p->msgN;
|
||||
r.next_msg_wait_micros = r.eof_fl ? 0 : msg_time_us - cur_time_us;
|
||||
|
||||
// callback with output msg's
|
||||
if( xmit_cnt )
|
||||
_packetize_and_transmit_msgs( p, xmit_cnt );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return r;
|
||||
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t st, uint8_t d0, uint8_t d1 )
|
||||
{
|
||||
return cwLogError(kNotImplementedRC,"MIDI file dev send() not implemented.");
|
||||
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
|
||||
if( p->latency_meas_enable_out_fl && isNoteOn(st,d1) )
|
||||
{
|
||||
p->latency_meas_enable_out_fl = false;
|
||||
time::get(p->latency_meas_result.note_on_output_ts);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt )
|
||||
{
|
||||
return cwLogError(kNotImplementedRC,"MIDI file dev send() not implemented.");
|
||||
}
|
||||
|
||||
void cw::midi::device::file_dev::latency_measure_reset(handle_t h)
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
|
||||
p->latency_meas_result.note_on_input_ts = {};
|
||||
p->latency_meas_result.note_on_output_ts = {};
|
||||
p->latency_meas_enable_in_fl = true;
|
||||
p->latency_meas_enable_out_fl = true;
|
||||
}
|
||||
|
||||
cw::midi::device::latency_meas_result_t cw::midi::device::file_dev::latency_measure_result(handle_t h)
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
return p->latency_meas_result;
|
||||
}
|
||||
|
||||
|
||||
void cw::midi::device::file_dev::report( handle_t h, textBuf::handle_t tbH)
|
||||
{
|
||||
file_dev_t* p = _handleToPtr(h);
|
||||
|
||||
print(tbH,"%i : Device: '%s'\n",p->base_dev_idx,p->dev_name);
|
||||
|
||||
if( p->fileN )
|
||||
print(tbH," Input:\n");
|
||||
|
||||
|
||||
for(unsigned i=0; i<p->fileN; ++i)
|
||||
{
|
||||
const char* fname = "<none>";
|
||||
|
||||
if( p->fileA[i].fname != nullptr )
|
||||
{
|
||||
fname = p->fileA[i].fname;
|
||||
}
|
||||
else
|
||||
if( p->fileA[i].msgA != nullptr )
|
||||
{
|
||||
fname = "msg array";
|
||||
}
|
||||
|
||||
print(tbH," port:%i '%s' ena:%i msg count:%i %s\n",
|
||||
i,
|
||||
cwStringNullGuard(p->fileA[i].label),
|
||||
p->fileA[i].enable_fl,
|
||||
p->fileA[i].msgN,
|
||||
fname );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::midi::device::file_dev::test( const object_t* cfg )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
|
||||
return rc;
|
||||
|
||||
}
|
100
cwMidiFileDev.h
Normal file
100
cwMidiFileDev.h
Normal file
@ -0,0 +1,100 @@
|
||||
namespace cw
|
||||
{
|
||||
namespace midi
|
||||
{
|
||||
namespace device
|
||||
{
|
||||
namespace file_dev
|
||||
{
|
||||
|
||||
typedef handle<struct file_dev_str> handle_t;
|
||||
|
||||
//
|
||||
// Count of labels determines the count of ports.
|
||||
// Note that port indexes and file indexes are synonomous.
|
||||
rc_t create( handle_t& hRef,
|
||||
cbFunc_t cbFunc,
|
||||
void* cbArg,
|
||||
unsigned baseDevIdx, // device index assigned to packet_t.devIndex in callbacks
|
||||
const char* labelA[], // labelA[ max_file_cnt ] assigns a textual label to each port.
|
||||
unsigned max_file_cnt,
|
||||
const char* dev_name = "file_dev", // name of the file device
|
||||
unsigned read_ahead_micros = 3000); // exec() will xmit events up to 'read_ahead_micros' after the current time
|
||||
rc_t destroy( handle_t& hRef );
|
||||
|
||||
// Return true if at least one enabled file exists, otherwise return false.
|
||||
bool is_active( handle_t h );
|
||||
|
||||
// Returns create(...,max_file_cnt...)
|
||||
unsigned file_count( handle_t h );
|
||||
|
||||
// Assign a MIDI file to an input port.
|
||||
rc_t open_midi_file( handle_t h, unsigned file_idx, const char* fname );
|
||||
rc_t load_messages( handle_t h, unsigned file_idx, const msg_t* msgA, unsigned msgN );
|
||||
|
||||
// Enable and disble the output of the specified file/port.
|
||||
rc_t enable_file( handle_t h, unsigned file_idx, bool enableFl );
|
||||
rc_t enable_file( handle_t h, unsigned file_idx );
|
||||
rc_t disable_file( handle_t h,unsigned file_idx );
|
||||
|
||||
|
||||
// Device count: Always 1.
|
||||
unsigned count( handle_t h );
|
||||
|
||||
// Device name as set in create()
|
||||
const char* name( handle_t h, unsigned devIdx );
|
||||
|
||||
// Returns 0 if deviceName == name() else kInvalidIdx
|
||||
unsigned nameToIndex(handle_t h, const char* deviceName);
|
||||
|
||||
// The count of ports is determined by count of labels in create(...,labelA[],...).
|
||||
unsigned portCount( handle_t h, unsigned devIdx, unsigned flags );
|
||||
|
||||
// Port name are provided by create(...,labelA[],...)
|
||||
// Port indexes and file indexes are the same.
|
||||
const char* portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx );
|
||||
unsigned portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portName );
|
||||
rc_t portEnable( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx, bool enableFl );
|
||||
|
||||
|
||||
typedef struct exec_result_str
|
||||
{
|
||||
unsigned next_msg_wait_micros; // microseconds before the next file msg must be transmitted
|
||||
unsigned xmit_cnt; // count of msg's sent to callback during this exec
|
||||
bool eof_fl; // true if there are no more file msg's to transmit
|
||||
|
||||
} exec_result_t;
|
||||
|
||||
|
||||
// Set the next msg to be returned.
|
||||
rc_t seek_to_msg_index( handle_t h, unsigned file_idx, unsigned msg_idx );
|
||||
rc_t set_end_msg_index( handle_t h, unsigned file_idx, unsigned msg_idx );
|
||||
|
||||
// Seek to the start of the file or to the last msg_idx set by seek_to_event().
|
||||
rc_t rewind( handle_t h );
|
||||
|
||||
|
||||
// Delay the first MIDI msg by 'start_delay_micros'.
|
||||
rc_t set_start_delay( handle_t h, unsigned start_delay_micros );
|
||||
|
||||
|
||||
|
||||
// Callback create(...,cbFunc,...) with msg's whose time has expired and return the
|
||||
// time delay prior to the next message.
|
||||
exec_result_t exec( handle_t h, unsigned long long elapsed_micros );
|
||||
|
||||
rc_t send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t st, uint8_t d0, uint8_t d1 );
|
||||
rc_t sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt );
|
||||
|
||||
|
||||
// Reset the latency measurement process.
|
||||
void latency_measure_reset(handle_t h);
|
||||
latency_meas_result_t latency_measure_result(handle_t h);
|
||||
|
||||
void report( handle_t h, textBuf::handle_t tbH );
|
||||
|
||||
rc_t test( const object_t* cfg );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,13 +4,8 @@
|
||||
#include "cwMem.h"
|
||||
#include "cwTime.h"
|
||||
#include "cwMidi.h"
|
||||
#include "cwTextBuf.h"
|
||||
|
||||
#include "cwMidiPort.h"
|
||||
|
||||
//===================================================================================================
|
||||
//
|
||||
//
|
||||
#include "cwMidiDecls.h"
|
||||
#include "cwMidiParser.h"
|
||||
|
||||
namespace cw
|
||||
{
|
||||
@ -63,7 +58,7 @@ namespace cw
|
||||
cbRecd_t* c = p->cbChain;
|
||||
for(; c!=NULL; c=c->linkPtr)
|
||||
{
|
||||
pkt->cbDataPtr = c->cbDataPtr;
|
||||
pkt->cbArg = c->cbDataPtr;
|
||||
c->cbFunc( pkt, pktCnt );
|
||||
}
|
||||
}
|
||||
@ -89,7 +84,6 @@ namespace cw
|
||||
p->pkt.msgArray = NULL;
|
||||
p->pkt.sysExMsg = p->buf;
|
||||
p->pkt.msgCnt = p->bufIdx;
|
||||
//p->cbFunc( &p->pkt, 1 );
|
||||
_cmMpParserCb(p,&p->pkt,1);
|
||||
p->bufIdx = 0;
|
||||
|
||||
@ -110,7 +104,9 @@ namespace cw
|
||||
|
||||
// fill the buffer msg
|
||||
msgPtr->timeStamp = *timeStamp;
|
||||
msgPtr->status = p->status;
|
||||
msgPtr->status = p->status & 0xf0;
|
||||
msgPtr->ch = p->status & 0x0f;
|
||||
msgPtr->uid = kInvalidId;
|
||||
|
||||
switch( p->dataCnt )
|
||||
{
|
||||
@ -171,10 +167,6 @@ cw::rc_t cw::midi::parser::create( handle_t& hRef, unsigned devIdx, unsigned por
|
||||
p->pkt.devIdx = devIdx;
|
||||
p->pkt.portIdx = portIdx;
|
||||
|
||||
//p->cbChain = cmMemAllocZ( cbRecd_t, 1 );
|
||||
//p->cbChain->cbFunc = cbFunc;
|
||||
//p->cbChain->cbDataPtr = cbDataPtr;
|
||||
//p->cbChain->linkPtr = NULL;
|
||||
p->cbChain = NULL;
|
||||
p->buf = mem::allocZ<uint8_t>( bufByteCnt );
|
||||
p->bufByteCnt = bufByteCnt;
|
||||
@ -406,6 +398,7 @@ cw::rc_t cw::midi::parser::transmit( handle_t h )
|
||||
return kOkRC;
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::midi::parser::installCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr )
|
||||
{
|
||||
parser_t* p = _handleToPtr(h);
|
||||
@ -474,95 +467,3 @@ bool cw::midi::parser::hasCallback( handle_t h, cbFunc_t cbFunc, void* cbArg )
|
||||
return false;
|
||||
}
|
||||
|
||||
//====================================================================================================
|
||||
//
|
||||
//
|
||||
|
||||
unsigned cw::midi::device::nameToIndex(handle_t h, const char* deviceName)
|
||||
{
|
||||
assert(deviceName!=NULL);
|
||||
unsigned i;
|
||||
unsigned n = count(h);
|
||||
for(i=0; i<n; ++i)
|
||||
if( strcmp(name(h,i),deviceName)==0)
|
||||
return i;
|
||||
return kInvalidIdx;
|
||||
}
|
||||
|
||||
unsigned cw::midi::device::portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portNameStr )
|
||||
{
|
||||
unsigned i;
|
||||
unsigned n = portCount(h,devIdx,flags);
|
||||
for(i=0; i<n; ++i)
|
||||
if( strcmp(portName(h,devIdx,flags,i),portNameStr)==0)
|
||||
return i;
|
||||
|
||||
return kInvalidIdx;
|
||||
}
|
||||
|
||||
//====================================================================================================
|
||||
//
|
||||
//
|
||||
|
||||
namespace cw
|
||||
{
|
||||
namespace midi
|
||||
{
|
||||
namespace device
|
||||
{
|
||||
|
||||
void testCallback( const packet_t* pktArray, unsigned pktCnt )
|
||||
{
|
||||
unsigned i,j;
|
||||
for(i=0; i<pktCnt; ++i)
|
||||
{
|
||||
const packet_t* p = pktArray + i;
|
||||
|
||||
for(j=0; j<p->msgCnt; ++j)
|
||||
if( p->msgArray != NULL )
|
||||
{
|
||||
if( ((p->msgArray[j].status & 0xf0) == kNoteOnMdId) && (p->msgArray[j].d1>0))
|
||||
printf("%ld %ld 0x%x %i %i\n", p->msgArray[j].timeStamp.tv_sec, p->msgArray[j].timeStamp.tv_nsec, p->msgArray[j].status,p->msgArray[j].d0, p->msgArray[j].d1);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("0x%x ",p->sysExMsg[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // device
|
||||
} // midi
|
||||
} // cw
|
||||
|
||||
cw::rc_t cw::midi::device::test()
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
char ch;
|
||||
unsigned parserBufByteCnt = 1024;
|
||||
textBuf::handle_t tbH;
|
||||
handle_t h;
|
||||
|
||||
// initialie the MIDI system
|
||||
if((rc = create(h,testCallback,NULL,parserBufByteCnt,"app")) != kOkRC )
|
||||
return rc;
|
||||
|
||||
// create a text buffer to hold the MIDI system report text
|
||||
if((rc = textBuf::create(tbH)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
// generate and print the MIDI system report
|
||||
report(h,tbH);
|
||||
cwLogInfo("%s",textBuf::text(tbH));
|
||||
|
||||
cwLogInfo("any key to send note-on (<q>=quit)\n");
|
||||
|
||||
while((ch = getchar()) != 'q')
|
||||
{
|
||||
send(h,2,0,0x90,60,60);
|
||||
}
|
||||
|
||||
errLabel:
|
||||
textBuf::destroy(tbH);
|
||||
destroy(h);
|
||||
return rc;
|
||||
}
|
36
cwMidiParser.h
Normal file
36
cwMidiParser.h
Normal file
@ -0,0 +1,36 @@
|
||||
|
||||
namespace cw
|
||||
{
|
||||
namespace midi
|
||||
{
|
||||
namespace parser
|
||||
{
|
||||
typedef handle<struct parser_str> handle_t;
|
||||
|
||||
// 'cbFunc' and 'cbArg' are optional. If 'cbFunc' is not supplied in the call to
|
||||
// create() it may be supplied later by installCallback().
|
||||
// 'bufByteCnt' define is the largest complete system-exclusive message the parser will
|
||||
// by able to transmit. System-exclusive messages larger than this will be broken into
|
||||
// multiple sequential callbacks.
|
||||
rc_t create( handle_t& hRef, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbArg, unsigned bufByteCnt );
|
||||
rc_t destroy( handle_t& hRef );
|
||||
unsigned errorCount( handle_t h );
|
||||
void parseMidiData( handle_t h, const time::spec_t* timestamp, const uint8_t* buf, unsigned bufByteCnt );
|
||||
|
||||
// The following two functions are intended to be used togetther.
|
||||
// Use midiTriple() to insert pre-parsed msg's to the output buffer,
|
||||
// and then use transmit() to send the buffer via the parsers callback function.
|
||||
// Set the data bytes to 0xff if they are not used by the message.
|
||||
rc_t midiTriple( handle_t h, const time::spec_t* timestamp, uint8_t status, uint8_t d0, uint8_t d1 );
|
||||
rc_t transmit( handle_t h );
|
||||
|
||||
// Install/Remove additional callbacks.
|
||||
rc_t installCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr );
|
||||
rc_t removeCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr );
|
||||
|
||||
// Returns true if the parser uses the given callback.
|
||||
bool hasCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
101
cwMidiPort.h
101
cwMidiPort.h
@ -1,101 +0,0 @@
|
||||
#ifndef cwMidiPort_H
|
||||
#define cwMidiPort_H
|
||||
|
||||
#include "cwMidiDecls.h"
|
||||
|
||||
namespace cw
|
||||
{
|
||||
namespace midi
|
||||
{
|
||||
|
||||
//( { file_desc:"Device independent MIDI port related code." kw:[midi]}
|
||||
|
||||
// Flags used to identify input and output ports on MIDI devices
|
||||
enum
|
||||
{
|
||||
kInMpFl = 0x01,
|
||||
kOutMpFl = 0x02
|
||||
};
|
||||
|
||||
typedef void (*cbFunc_t)( const packet_t* pktArray, unsigned pktCnt );
|
||||
|
||||
//)
|
||||
//( { label:parser file_desc:"MIDI event parser converts raw MIDI events into packet_t messages." kw:[midi]}
|
||||
|
||||
//===============================================================================================
|
||||
// MIDI Parser
|
||||
//
|
||||
|
||||
namespace parser
|
||||
{
|
||||
typedef handle<struct parser_str> handle_t;
|
||||
|
||||
// 'cbFunc' and 'cbDataPtr' are optional. If 'cbFunc' is not supplied in the call to
|
||||
// create() it may be supplied later by installCallback().
|
||||
// 'bufByteCnt' defines is the largest complete system-exclusive message the parser will
|
||||
// by able to transmit. System-exclusive messages larger than this will be broken into
|
||||
// multiple sequential callbacks.
|
||||
rc_t create( handle_t& hRef, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbArg, unsigned bufByteCnt );
|
||||
rc_t destroy( handle_t& hRef );
|
||||
unsigned errorCount( handle_t h );
|
||||
void parseMidiData( handle_t h, const time::spec_t* timestamp, const uint8_t* buf, unsigned bufByteCnt );
|
||||
|
||||
// The following two functions are intended to be used togetther.
|
||||
// Use midiTriple() to insert pre-parsed msg's to the output buffer,
|
||||
// and then use transmit() to send the buffer via the parsers callback function.
|
||||
// Set the data bytes to 0xff if they are not used by the message.
|
||||
rc_t midiTriple( handle_t h, const time::spec_t* timestamp, uint8_t status, uint8_t d0, uint8_t d1 );
|
||||
rc_t transmit( handle_t h );
|
||||
|
||||
// Install/Remove additional callbacks.
|
||||
rc_t installCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr );
|
||||
rc_t removeCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr );
|
||||
|
||||
// Returns true if the parser uses the given callback.
|
||||
bool hasCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr );
|
||||
|
||||
}
|
||||
|
||||
//)
|
||||
//( { label:cmMidiPort file_desc:"Device independent MIDI port." kw:[midi]}
|
||||
|
||||
//===============================================================================================
|
||||
// MIDI Device Interface
|
||||
//
|
||||
|
||||
|
||||
namespace device
|
||||
{
|
||||
typedef handle< struct device_str> handle_t;
|
||||
|
||||
// 'cbFunc' and 'cbDataPtr' are optional (they may be set to NULL). In this case
|
||||
// 'cbFunc' and 'cbDataPtr' may be set in a later call to cmMpInstallCallback().
|
||||
rc_t create( handle_t& h, cbFunc_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt, const char* appNameStr );
|
||||
rc_t destroy( handle_t& h);
|
||||
bool isInitialized( handle_t h );
|
||||
|
||||
unsigned count( handle_t h );
|
||||
const char* name( handle_t h, unsigned devIdx );
|
||||
unsigned nameToIndex(handle_t h, const char* deviceName);
|
||||
unsigned portCount( handle_t h, unsigned devIdx, unsigned flags );
|
||||
const char* portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx );
|
||||
unsigned portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portName );
|
||||
rc_t send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t st, uint8_t d0, uint8_t d1 );
|
||||
rc_t sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt );
|
||||
|
||||
// Set devIdx to -1 to assign the callback to all devices.
|
||||
// Set portIdx to -1 to assign the callback to all ports on the specified devices.
|
||||
//
|
||||
rc_t installCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr );
|
||||
rc_t removeCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr );
|
||||
bool usesCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr );
|
||||
|
||||
void report( handle_t h, textBuf::handle_t tbH);
|
||||
|
||||
rc_t test();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
25
cwMutex.cpp
25
cwMutex.cpp
@ -36,6 +36,15 @@ namespace cw
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
time::spec_t _future_ms(unsigned timeout_milliseconds)
|
||||
{
|
||||
time::spec_t ts;
|
||||
clock_gettime(CLOCK_REALTIME,&ts);
|
||||
time::advanceMs(ts,timeout_milliseconds);
|
||||
return ts;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,10 +111,15 @@ cw::rc_t cw::mutex::lock( handle_t h, unsigned timeout_milliseconds )
|
||||
rc_t rc = kOkRC;
|
||||
mutex_t* p = _handleToPtr(h);
|
||||
int sysRc;
|
||||
time::spec_t ts;
|
||||
time::spec_t ts = _future_ms(timeout_milliseconds);
|
||||
|
||||
// Apparently timedlock depends on using CLOCK_REALTIME vs CLOCK_MONOTONIC
|
||||
// so we can't call to time::current_time() which uses CLOCK_MONOTONIC.
|
||||
// TODO: See this: https://stackoverflow.com/questions/14248033/clock-monotonic-and-pthread-mutex-timedlock-pthread-cond-timedwait
|
||||
// and consider changing this to use CLOCK_MONOTONIC.
|
||||
//clock_gettime(CLOCK_REALTIME,&ts);
|
||||
//time::advanceMs(ts,timeout_milliseconds);
|
||||
|
||||
time::get(ts);
|
||||
time::advanceMs(ts,timeout_milliseconds);
|
||||
|
||||
|
||||
switch(sysRc = pthread_mutex_timedlock(&p->mutex,&ts) )
|
||||
@ -170,9 +184,10 @@ cw::rc_t cw::mutex::waitOnCondVar( handle_t h, bool lockThenWaitFl, unsigned tim
|
||||
else // ... otherwise use the cond. var. wait with timeout API
|
||||
{
|
||||
|
||||
struct timespec ts;
|
||||
struct timespec ts = _future_ms(timeOutMs);
|
||||
|
||||
if((rc = time::futureMs(ts,timeOutMs)) == kOkRC )
|
||||
|
||||
//if((rc = time::futureMs(ts,timeOutMs)) == kOkRC )
|
||||
if((sysRC = pthread_cond_timedwait(&p->cvar,&p->mutex,&ts)) != 0 )
|
||||
{
|
||||
if( sysRC == ETIMEDOUT )
|
||||
|
@ -365,7 +365,7 @@ cw::rc_t cw::nbmem::test_multi_threaded()
|
||||
ctx.threadA[i].varA = mem::allocZ<void*>(threadVarN);
|
||||
ctx.threadA[i].varN = threadVarN;
|
||||
|
||||
if((rc = thread::create(ctx.threadA[i].threadH, _test_thread_func, ctx.threadA + i )) != kOkRC )
|
||||
if((rc = thread::create(ctx.threadA[i].threadH, _test_thread_func, ctx.threadA + i, "nb_mem" )) != kOkRC )
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -211,7 +211,7 @@ namespace cw
|
||||
if( cwIsNotFlag(flags, kOptionalFl) )
|
||||
return cwLogError(kInvalidIdRC,"The pair label '%s' could not be found.",cwStringNullGuard(label));
|
||||
|
||||
return kLabelNotFoundRC;
|
||||
return kEleNotFoundRC;
|
||||
|
||||
}
|
||||
return o->value(v);
|
||||
@ -226,7 +226,7 @@ namespace cw
|
||||
rc_t rc = get(label,valRef,flags);
|
||||
|
||||
// if no error occurred ....
|
||||
if( rc == kOkRC || (rc == kLabelNotFoundRC && cwIsFlag(flags,kOptionalFl)))
|
||||
if( rc == kOkRC || (rc == kEleNotFoundRC && cwIsFlag(flags,kOptionalFl)))
|
||||
rc = _getv(flags, std::forward<ARGS>(args)...); // ... recurse to find next label/value pair
|
||||
else
|
||||
rc = cwLogError(rc,"Object parse failed for the pair label:'%s'.",cwStringNullGuard(label));
|
||||
|
434
cwPianoScore.cpp
434
cwPianoScore.cpp
@ -4,45 +4,28 @@
|
||||
#include "cwMem.h"
|
||||
#include "cwText.h"
|
||||
#include "cwObject.h"
|
||||
#include "cwPianoScore.h"
|
||||
|
||||
|
||||
|
||||
#include "cwMidi.h"
|
||||
#include "cwTime.h"
|
||||
#include "cwFile.h"
|
||||
#include "cwCsv.h"
|
||||
#include "cwVectOps.h"
|
||||
|
||||
#include "cwDynRefTbl.h"
|
||||
#include "cwScoreParse.h"
|
||||
#include "cwSfScore.h"
|
||||
#include "cwSfTrack.h"
|
||||
#include "cwPerfMeas.h"
|
||||
#include "cwPianoScore.h"
|
||||
|
||||
#define INVALID_PERF_MEAS (-1)
|
||||
|
||||
namespace cw
|
||||
{
|
||||
namespace perf_score
|
||||
{
|
||||
/*
|
||||
enum {
|
||||
kMeasColIdx = 0,
|
||||
kLocColIdx,
|
||||
kSecColIdx,
|
||||
kSciPitchColIdx,
|
||||
kStatusColIdx,
|
||||
kD0ColIdx,
|
||||
kD1ColIdx,
|
||||
kBarColIdx,
|
||||
kSectionColIdx,
|
||||
kEvenColIdx,
|
||||
kDynColIdx,
|
||||
kTempoColIdx,
|
||||
kCostColIdx,
|
||||
kColCnt
|
||||
};
|
||||
|
||||
typedef struct col_map_str
|
||||
{
|
||||
unsigned colId;
|
||||
const char* label;
|
||||
bool enableFl;
|
||||
} col_map_t;
|
||||
*/
|
||||
|
||||
typedef struct score_str
|
||||
{
|
||||
event_t* base;
|
||||
@ -57,24 +40,6 @@ namespace cw
|
||||
|
||||
} score_t;
|
||||
|
||||
/*
|
||||
col_map_t col_map_array[] = {
|
||||
{ kMeasColIdx, "meas", true },
|
||||
{ kLocColIdx, "loc", true },
|
||||
{ kLocColIdx, "oloc", false },
|
||||
{ kSecColIdx, "sec", true },
|
||||
{ kSciPitchColIdx, "sci_pitch", true },
|
||||
{ kStatusColIdx, "status", true },
|
||||
{ kD0ColIdx, "d0", true },
|
||||
{ kD1ColIdx, "d1", true },
|
||||
{ kBarColIdx, "bar", true },
|
||||
{ kSectionColIdx, "section", true },
|
||||
{ kEvenColIdx, "even", true },
|
||||
{ kDynColIdx, "dyn", true },
|
||||
{ kTempoColIdx, "tempo", true },
|
||||
{ kCostColIdx, "cost", true },
|
||||
};
|
||||
*/
|
||||
score_t* _handleToPtr(handle_t h)
|
||||
{
|
||||
return handleToPtr<handle_t,score_t>(h);
|
||||
@ -116,6 +81,67 @@ namespace cw
|
||||
}
|
||||
}
|
||||
|
||||
void _setup_feat_vectors( score_t* p )
|
||||
{
|
||||
for(event_t* e=p->base; e!=nullptr; e=e->link)
|
||||
if( e->valid_stats_fl )
|
||||
{
|
||||
for(unsigned i=0; i<perf_meas::kValCnt; ++i)
|
||||
{
|
||||
unsigned stat_idx = e->statsA[i].id;
|
||||
|
||||
switch( e->statsA[i].id )
|
||||
{
|
||||
case perf_meas::kEvenValIdx: e->featV[ stat_idx ] = e->even; break;
|
||||
case perf_meas::kDynValIdx: e->featV[ stat_idx ] = e->dyn; break;
|
||||
case perf_meas::kTempoValIdx: e->featV[ stat_idx ] = e->tempo; break;
|
||||
case perf_meas::kMatchCostValIdx: e->featV[ stat_idx ] = e->cost; break;
|
||||
}
|
||||
|
||||
e->featMinV[ stat_idx ] = e->statsA[i].min;
|
||||
e->featMaxV[ stat_idx ] = e->statsA[i].max;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
rc_t _read_meas_stats( score_t* p, csv::handle_t csvH, event_t* e )
|
||||
{
|
||||
rc_t rc;
|
||||
|
||||
if((rc = getv(csvH,
|
||||
"even_min", e->statsA[ perf_meas::kEvenValIdx ].min,
|
||||
"even_max", e->statsA[ perf_meas::kEvenValIdx ].max,
|
||||
"even_mean", e->statsA[ perf_meas::kEvenValIdx ].mean,
|
||||
"even_std", e->statsA[ perf_meas::kEvenValIdx ].std,
|
||||
"dyn_min", e->statsA[ perf_meas::kDynValIdx ].min,
|
||||
"dyn_max", e->statsA[ perf_meas::kDynValIdx ].max,
|
||||
"dyn_mean", e->statsA[ perf_meas::kDynValIdx ].mean,
|
||||
"dyn_std", e->statsA[ perf_meas::kDynValIdx ].std,
|
||||
"tempo_min", e->statsA[ perf_meas::kTempoValIdx ].min,
|
||||
"tempo_max", e->statsA[ perf_meas::kTempoValIdx ].max,
|
||||
"tempo_mean",e->statsA[ perf_meas::kTempoValIdx ].mean,
|
||||
"tempo_std", e->statsA[ perf_meas::kTempoValIdx ].std,
|
||||
"cost_min", e->statsA[ perf_meas::kMatchCostValIdx ].min,
|
||||
"cost_max", e->statsA[ perf_meas::kMatchCostValIdx ].max,
|
||||
"cost_mean", e->statsA[ perf_meas::kMatchCostValIdx ].mean,
|
||||
"cost_std", e->statsA[ perf_meas::kMatchCostValIdx ].std )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Error parsing CSV meas. stats field.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
e->statsA[ perf_meas::kEvenValIdx ].id = perf_meas::kEvenValIdx;
|
||||
e->statsA[ perf_meas::kDynValIdx ].id = perf_meas::kDynValIdx;
|
||||
e->statsA[ perf_meas::kTempoValIdx ].id = perf_meas::kTempoValIdx;
|
||||
e->statsA[ perf_meas::kMatchCostValIdx ].id = perf_meas::kMatchCostValIdx;
|
||||
|
||||
errLabel:
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc_t _read_csv_line( score_t* p, bool score_fl, csv::handle_t csvH )
|
||||
{
|
||||
@ -123,6 +149,7 @@ namespace cw
|
||||
event_t* e = mem::allocZ<event_t>();
|
||||
const char* sci_pitch;
|
||||
unsigned sci_pitch_char_cnt;
|
||||
int has_stats_fl = 0;
|
||||
|
||||
if((rc = getv(csvH,
|
||||
"meas",e->meas,
|
||||
@ -133,12 +160,21 @@ namespace cw
|
||||
"d0", e->d0,
|
||||
"d1", e->d1,
|
||||
"bar", e->bar,
|
||||
"section", e->section )) != kOkRC )
|
||||
"section", e->section)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Error parsing CSV.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if( has_field(csvH,"has_stats_fl") )
|
||||
if((rc = getv(csvH,"has_stats_fl",has_stats_fl)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Error parsing optional fields.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
e->valid_stats_fl = has_stats_fl;
|
||||
|
||||
if( score_fl )
|
||||
{
|
||||
if((rc = getv(csvH,"oloc",e->loc )) != kOkRC )
|
||||
@ -147,18 +183,27 @@ namespace cw
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if( e->section > 0 && has_stats_fl )
|
||||
if((rc = _read_meas_stats(p,csvH,e)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if((rc = getv(csvH,
|
||||
"even", e->even,
|
||||
"dyn", e->dyn,
|
||||
"tempo", e->tempo,
|
||||
"cost", e->cost )) != kOkRC )
|
||||
"even", e->even,
|
||||
"dyn", e->dyn,
|
||||
"tempo", e->tempo,
|
||||
"cost", e->cost)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Error parsing CSV.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if( has_stats_fl )
|
||||
if((rc = _read_meas_stats(p,csvH,e)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
}
|
||||
|
||||
if((rc = field_char_count( csvH, title_col_index(csvH,"sci_pitch"), sci_pitch_char_cnt )) != kOkRC )
|
||||
@ -203,16 +248,6 @@ namespace cw
|
||||
csv::handle_t csvH;
|
||||
rc_t rc = kOkRC;
|
||||
bool score_fl = false;
|
||||
/*
|
||||
//unsigned titleN = sizeof(col_map_array)/sizeof(col_map_array[0]);
|
||||
//const char* titleA[ titleN ];
|
||||
|
||||
for(unsigned i=0; i<titleN; ++i)
|
||||
{
|
||||
titleA[i] = col_map_array[i].label;
|
||||
assert( col_map_array[i].colId == i );
|
||||
}
|
||||
*/
|
||||
|
||||
if((rc = csv::create(csvH,csvFname)) != kOkRC )
|
||||
{
|
||||
@ -220,21 +255,12 @@ namespace cw
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
// Distinguish between the score file which has
|
||||
// an 'oloc' field and the recorded performance
|
||||
// files which do not.
|
||||
if( title_col_index(csvH,"oloc") != kInvalidIdx )
|
||||
score_fl = true;
|
||||
|
||||
/*
|
||||
for(unsigned i=0; i<titleN; ++i)
|
||||
if( col_map_array[i].enableFl )
|
||||
{
|
||||
if((col_map_array[i].colIdx = title_col_index(csvH,col_map_array[i].label)) == kInvalidIdx )
|
||||
{
|
||||
rc = cwLogError(rc,"The performance score column '%s' was not found in the score file:'%s'.",col_map_array[i].label,cwStringNullGuard(csvFname));
|
||||
goto errLabel;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
for(unsigned i=0; (rc = next_line(csvH)) == kOkRC; ++i )
|
||||
if((rc = _read_csv_line(p,score_fl,csvH)) != kOkRC )
|
||||
{
|
||||
@ -273,255 +299,6 @@ namespace cw
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
unsigned _scan_to_end_of_field( const char* lineBuf, unsigned buf_idx, unsigned bufCharCnt )
|
||||
{
|
||||
for(; buf_idx < bufCharCnt; ++buf_idx )
|
||||
{
|
||||
if( lineBuf[buf_idx] == '"' )
|
||||
{
|
||||
for(++buf_idx; buf_idx < bufCharCnt; ++buf_idx)
|
||||
if( lineBuf[buf_idx] == '"' )
|
||||
break;
|
||||
}
|
||||
|
||||
if( lineBuf[buf_idx] == ',')
|
||||
break;
|
||||
}
|
||||
|
||||
return buf_idx;
|
||||
}
|
||||
|
||||
rc_t _parse_csv_string( const char* lineBuf, unsigned bfi, unsigned efi, char* val, unsigned valCharN )
|
||||
{
|
||||
unsigned n = std::min(efi-bfi,valCharN);
|
||||
strncpy(val,lineBuf+bfi,n);
|
||||
val[std::min(n,valCharN-1)] = 0;
|
||||
return kOkRC;
|
||||
}
|
||||
|
||||
rc_t _parse_csv_double( const char* lineBuf, unsigned bfi, unsigned efi, double &valueRef )
|
||||
{
|
||||
errno = 0;
|
||||
valueRef = strtod(lineBuf+bfi,nullptr);
|
||||
if( errno != 0 )
|
||||
return cwLogError(kOpFailRC,"CSV String to number conversion failed.");
|
||||
|
||||
return kOkRC;
|
||||
}
|
||||
|
||||
rc_t _parse_csv_unsigned( const char* lineBuf, unsigned bfi, unsigned efi, unsigned &valueRef )
|
||||
{
|
||||
rc_t rc;
|
||||
double v;
|
||||
if((rc = _parse_csv_double(lineBuf,bfi,efi,v)) == kOkRC )
|
||||
valueRef = (unsigned)v;
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc_t _parse_csv_line( score_t* p, event_t* e, char* line_buf, unsigned lineBufCharCnt )
|
||||
{
|
||||
enum
|
||||
{
|
||||
kMeas_FIdx,
|
||||
kIndex_FIdx,
|
||||
kVoice_FIdx,
|
||||
kLoc_FIdx,
|
||||
kTick_FIdx,
|
||||
kSec_FIdx,
|
||||
kDur_FIdx,
|
||||
kRval_FIdx,
|
||||
kDots_FIdx,
|
||||
kSPitch_FIdx,
|
||||
kDMark_FIdx,
|
||||
kDLevel_FIdx,
|
||||
kStatus_FIdx,
|
||||
kD0_FIdx,
|
||||
kD1_FIdx,
|
||||
kBar_FIdx,
|
||||
kSection_FIdx,
|
||||
kBpm_FIdx,
|
||||
kGrace_FIdx,
|
||||
kPedal_FIdx,
|
||||
kEven_FIdx,
|
||||
kDyn_FIdx,
|
||||
kTempo_FIdx,
|
||||
kCost_FIdx,
|
||||
kMax_FIdx
|
||||
};
|
||||
|
||||
rc_t rc = kOkRC;
|
||||
unsigned bfi = 0;
|
||||
unsigned efi = 0;
|
||||
unsigned field_idx = 0;
|
||||
|
||||
|
||||
for(field_idx=0; field_idx != kMax_FIdx; ++field_idx)
|
||||
{
|
||||
if((efi = _scan_to_end_of_field(line_buf,efi,lineBufCharCnt)) == kInvalidIdx )
|
||||
{
|
||||
rc = cwLogError( rc, "End of field scan failed");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if( bfi != efi )
|
||||
{
|
||||
switch( field_idx )
|
||||
{
|
||||
case kMeas_FIdx:
|
||||
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->meas );
|
||||
break;
|
||||
|
||||
case kLoc_FIdx:
|
||||
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->loc );
|
||||
break;
|
||||
|
||||
case kSec_FIdx:
|
||||
rc = _parse_csv_double( line_buf, bfi, efi, e->sec );
|
||||
break;
|
||||
|
||||
case kStatus_FIdx:
|
||||
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->status );
|
||||
break;
|
||||
|
||||
case kD0_FIdx:
|
||||
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->d0 );
|
||||
break;
|
||||
|
||||
case kD1_FIdx:
|
||||
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->d1 );
|
||||
break;
|
||||
|
||||
case kBar_FIdx:
|
||||
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->bar );
|
||||
break;
|
||||
|
||||
case kSection_FIdx:
|
||||
rc = _parse_csv_unsigned( line_buf, bfi, efi, e->section );
|
||||
break;
|
||||
|
||||
case kSPitch_FIdx:
|
||||
rc = _parse_csv_string( line_buf, bfi, efi, e->sci_pitch, sizeof(e->sci_pitch) );
|
||||
break;
|
||||
|
||||
case kEven_FIdx:
|
||||
e->even = INVALID_PERF_MEAS;
|
||||
if( efi > bfi+1 )
|
||||
rc = _parse_csv_double( line_buf, bfi, efi, e->even );
|
||||
break;
|
||||
|
||||
case kDyn_FIdx:
|
||||
e->dyn = INVALID_PERF_MEAS;
|
||||
if( efi > bfi+1 )
|
||||
rc = _parse_csv_double( line_buf, bfi, efi, e->dyn );
|
||||
break;
|
||||
|
||||
case kTempo_FIdx:
|
||||
e->tempo = INVALID_PERF_MEAS;
|
||||
if( efi > bfi+1 )
|
||||
rc = _parse_csv_double( line_buf, bfi, efi, e->tempo );
|
||||
break;
|
||||
|
||||
case kCost_FIdx:
|
||||
e->cost = INVALID_PERF_MEAS;
|
||||
if( efi > bfi+1 )
|
||||
rc = _parse_csv_double( line_buf, bfi, efi, e->cost );
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bfi = efi + 1;
|
||||
efi = efi + 1;
|
||||
|
||||
}
|
||||
|
||||
errLabel:
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
rc_t _parse_csv( score_t* p, const char* fn )
|
||||
{
|
||||
rc_t rc;
|
||||
file::handle_t fH;
|
||||
unsigned line_count = 0;
|
||||
char* lineBufPtr = nullptr;
|
||||
unsigned lineBufCharCnt = 0;
|
||||
event_t* e = nullptr;
|
||||
|
||||
if((rc = file::open( fH, fn, file::kReadFl )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError( rc, "Piano score file open failed on '%s'.",cwStringNullGuard(fn));
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if((rc = file::lineCount(fH,&line_count)) != kOkRC )
|
||||
{
|
||||
rc = cwLogError( rc, "Line count query failed on '%s'.",cwStringNullGuard(fn));
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
p->min_uid = kInvalidId;
|
||||
p->uid_mapN = 0;
|
||||
|
||||
for(unsigned line=0; line<line_count; ++line)
|
||||
{
|
||||
if((rc = getLineAuto( fH, &lineBufPtr, &lineBufCharCnt )) != kOkRC )
|
||||
{
|
||||
if( rc != kEofRC )
|
||||
rc = cwLogError( rc, "Line read failed on '%s' line number '%i'.",cwStringNullGuard(fn),line+1);
|
||||
else
|
||||
rc = kOkRC;
|
||||
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if( line > 0 ) // skip column title line
|
||||
{
|
||||
|
||||
e = mem::allocZ<event_t>();
|
||||
|
||||
if((rc = _parse_csv_line( p, e, lineBufPtr, lineBufCharCnt )) != kOkRC )
|
||||
{
|
||||
mem::release(e);
|
||||
rc = cwLogError( rc, "Line parse failed on '%s' line number '%i'.",cwStringNullGuard(fn),line+1);
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
// assign the UID
|
||||
e->uid = line;
|
||||
|
||||
// link the event into the event list
|
||||
if( p->end != nullptr )
|
||||
p->end->link = e;
|
||||
else
|
||||
p->base = e;
|
||||
|
||||
p->end = e;
|
||||
|
||||
// track the max 'loc' id
|
||||
if( e->loc > p->maxLocId )
|
||||
p->maxLocId = e->loc;
|
||||
|
||||
if( p->min_uid == kInvalidId || e->uid < p->min_uid )
|
||||
p->min_uid = e->uid;
|
||||
|
||||
p->uid_mapN += 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
errLabel:
|
||||
mem::release(lineBufPtr);
|
||||
file::close(fH);
|
||||
|
||||
return rc;
|
||||
}
|
||||
*/
|
||||
rc_t _parse_event_list( score_t* p, const object_t* cfg )
|
||||
{
|
||||
rc_t rc;
|
||||
@ -574,16 +351,6 @@ namespace cw
|
||||
textCopy( e->sci_pitch,sizeof(e->sci_pitch),sci_pitch);
|
||||
textCopy( e->dmark,sizeof(e->dmark),dmark);
|
||||
textCopy( e->grace_mark, sizeof(e->grace_mark),grace_mark);
|
||||
/*
|
||||
if( sci_pitch != nullptr )
|
||||
strncpy(e->sci_pitch,sci_pitch,sizeof(e->sci_pitch)-1);
|
||||
|
||||
if( dmark != nullptr )
|
||||
strncpy(e->dmark,dmark,sizeof(e->dmark)-1);
|
||||
|
||||
if( grace_mark != nullptr )
|
||||
strncpy(e->grace_mark,grace_mark,sizeof(e->grace_mark)-1);
|
||||
*/
|
||||
|
||||
// assign the UID
|
||||
e->uid = i;
|
||||
@ -699,7 +466,6 @@ cw::rc_t cw::perf_score::create( handle_t& hRef, const char* fn )
|
||||
|
||||
if( _does_file_have_loc_info(fn) )
|
||||
{
|
||||
// if((rc = _parse_csv(p,fn)) != kOkRC )
|
||||
if((rc = _read_csv( p, fn )) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
@ -715,6 +481,8 @@ cw::rc_t cw::perf_score::create( handle_t& hRef, const char* fn )
|
||||
p->has_locs_fl = false;
|
||||
}
|
||||
|
||||
_setup_feat_vectors(p);
|
||||
|
||||
hRef.set(p);
|
||||
|
||||
errLabel:
|
||||
@ -874,6 +642,8 @@ cw::rc_t cw::perf_score::test( const object_t* cfg )
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
cwLogInfo("Creating score from '%s'.",cwStringNullGuard(fname));
|
||||
|
||||
if((rc = create( h, fname )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Score create failed.");
|
||||
|
@ -7,6 +7,15 @@ namespace cw
|
||||
{
|
||||
typedef handle<struct score_str> handle_t;
|
||||
|
||||
typedef struct stats_str
|
||||
{
|
||||
unsigned id; // see: perf_meas::k???VarIdx
|
||||
double min;
|
||||
double max;
|
||||
double mean;
|
||||
double std;
|
||||
} stats_t;
|
||||
|
||||
typedef struct event_str
|
||||
{
|
||||
unsigned uid; // unique id for this event
|
||||
@ -28,11 +37,18 @@ namespace cw
|
||||
unsigned barPitchIdx; // bar pitch index or 0
|
||||
unsigned section; // section number or 0
|
||||
|
||||
bool valid_stats_fl; // is statsA valid in this record[]
|
||||
stats_t statsA[ perf_meas::kValCnt ];
|
||||
|
||||
double even;
|
||||
double dyn;
|
||||
double tempo;
|
||||
double cost;
|
||||
|
||||
double featV[ perf_meas::kValCnt ];
|
||||
double featMinV[ perf_meas::kValCnt ];
|
||||
double featMaxV[ perf_meas::kValCnt ];
|
||||
|
||||
struct event_str* link; // list link
|
||||
} event_t;
|
||||
|
||||
|
@ -6,13 +6,18 @@
|
||||
#include "cwObject.h"
|
||||
#include "cwTime.h"
|
||||
#include "cwVectOps.h"
|
||||
#include "cwFlowDecl.h"
|
||||
#include "cwPresetSel.h"
|
||||
#include "cwFile.h"
|
||||
#include "cwPianoScore.h"
|
||||
|
||||
#include "cwMidi.h"
|
||||
#include "cwDynRefTbl.h"
|
||||
#include "cwScoreParse.h"
|
||||
#include "cwSfScore.h"
|
||||
#include "cwPerfMeas.h"
|
||||
|
||||
#include "cwPianoScore.h"
|
||||
|
||||
|
||||
namespace cw
|
||||
{
|
||||
@ -872,7 +877,7 @@ cw::rc_t cw::preset_sel::create( handle_t& hRef, const object_t* cfg )
|
||||
}
|
||||
|
||||
|
||||
// allocate the label array
|
||||
// allocate the alt label array
|
||||
p->altLabelN = alt_labelL->child_count() + 1;
|
||||
p->altLabelA = mem::allocZ<alt_label_t>(p->altLabelN);
|
||||
|
||||
@ -953,6 +958,25 @@ const char* cw::preset_sel::alt_label( handle_t h, unsigned alt_idx )
|
||||
return _alt_index_to_label(p,alt_idx);
|
||||
}
|
||||
|
||||
void cw::preset_sel::get_loc_range( handle_t h, unsigned& minLocRef, unsigned& maxLocRef )
|
||||
{
|
||||
preset_sel_t* p = _handleToPtr(h);
|
||||
|
||||
if( p->fragL == nullptr )
|
||||
{
|
||||
minLocRef = score_parse::kInvalidLocId;
|
||||
maxLocRef = score_parse::kInvalidLocId;
|
||||
}
|
||||
else
|
||||
{
|
||||
minLocRef = 1;
|
||||
|
||||
for(const frag_t* f = p->fragL; f!=nullptr; f=f->link)
|
||||
maxLocRef = f->endLoc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unsigned cw::preset_sel::fragment_count( handle_t h )
|
||||
{
|
||||
preset_sel_t* p = _handleToPtr(h);
|
||||
@ -1041,7 +1065,6 @@ cw::rc_t cw::preset_sel::create_fragment( handle_t h, unsigned end_loc, time::sp
|
||||
// set the return value
|
||||
fragIdRef = f->fragId;
|
||||
|
||||
|
||||
// intiialize the preset array elements
|
||||
for(unsigned i=0; i<p->presetLabelN; ++i)
|
||||
{
|
||||
@ -1127,6 +1150,7 @@ cw::rc_t cw::preset_sel::delete_fragment( handle_t h, unsigned fragId )
|
||||
|
||||
// release the fragment
|
||||
mem::release(f->presetA);
|
||||
mem::release(f->multiPresetA);
|
||||
mem::release(f);
|
||||
|
||||
return kOkRC;
|
||||
@ -1477,7 +1501,6 @@ cw::rc_t cw::preset_sel::write( handle_t h, const char* fn )
|
||||
}
|
||||
|
||||
newPairObject("fragL", fragL_obj, root);
|
||||
newPairObject("fragN", fragN, root);
|
||||
newPairObject("masterWetInGain", p->master_wet_in_gain, root );
|
||||
newPairObject("masterWetOutGain", p->master_wet_out_gain, root );
|
||||
newPairObject("masterDryGain", p->master_dry_gain, root );
|
||||
@ -1515,7 +1538,6 @@ cw::rc_t cw::preset_sel::read( handle_t h, const char* fn )
|
||||
rc_t rc = kOkRC;
|
||||
preset_sel_t* p = _handleToPtr(h);
|
||||
object_t* root = nullptr;
|
||||
unsigned fragN = 0;
|
||||
const object_t* fragL_obj = nullptr;
|
||||
|
||||
// parse the preset file
|
||||
@ -1529,8 +1551,7 @@ cw::rc_t cw::preset_sel::read( handle_t h, const char* fn )
|
||||
_destroy_all_frags(p);
|
||||
|
||||
// parse the root level
|
||||
if((rc = root->getv( "fragN", fragN,
|
||||
"fragL", fragL_obj,
|
||||
if((rc = root->getv( "fragL", fragL_obj,
|
||||
"masterWetInGain", p->master_wet_in_gain,
|
||||
"masterWetOutGain", p->master_wet_out_gain,
|
||||
"masterDryGain", p->master_dry_gain,
|
||||
@ -1540,13 +1561,14 @@ cw::rc_t cw::preset_sel::read( handle_t h, const char* fn )
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
|
||||
// for each fragment
|
||||
for(unsigned i=0; i<fragN; ++i)
|
||||
for(unsigned i=0; i<fragL_obj->child_count(); ++i)
|
||||
{
|
||||
frag_t* f = nullptr;
|
||||
const object_t* r = fragL_obj->child_ele(i);
|
||||
|
||||
unsigned fragId=kInvalidId,endLoc=0,presetN=0,begPlayLoc=0,endPlayLoc=0;
|
||||
unsigned fragId=kInvalidId,endLoc=0,presetN=0,multiPresetN=0,begPlayLoc=0,endPlayLoc=0;
|
||||
double igain=0,ogain=0,wetDryGain=0,fadeOutMs=0;
|
||||
const char* note = nullptr;
|
||||
const object_t* presetL_obj = nullptr;
|
||||
@ -1627,6 +1649,9 @@ cw::rc_t cw::preset_sel::read( handle_t h, const char* fn )
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if( order > 0 || playFl )
|
||||
multiPresetN += 1;
|
||||
|
||||
f->presetA[ preset_idx ].order = order;
|
||||
f->presetA[ preset_idx ].alt_str = mem::duplStr(alt_str);
|
||||
f->presetA[ preset_idx ].playFl = playFl;
|
||||
@ -1638,6 +1663,32 @@ cw::rc_t cw::preset_sel::read( handle_t h, const char* fn )
|
||||
|
||||
}
|
||||
|
||||
// create the multiPresetA[]
|
||||
if( multiPresetN>0 )
|
||||
{
|
||||
f->multiPresetA = mem::allocZ<flow::preset_order_t>(multiPresetN);
|
||||
f->multiPresetN = multiPresetN;
|
||||
|
||||
for(unsigned i=0,j=1; i<presetN; ++i)
|
||||
if( f->presetA[i].order > 0 || f->presetA[i].playFl )
|
||||
{
|
||||
unsigned out_idx = f->presetA[i].playFl ? 0 : j++;
|
||||
|
||||
assert( out_idx < multiPresetN );
|
||||
|
||||
f->multiPresetA[out_idx].preset_label = _preset_label( p, f->presetA[i].preset_idx );
|
||||
f->multiPresetA[out_idx].order = f->presetA[i].order;
|
||||
}
|
||||
|
||||
// sort
|
||||
if( multiPresetN > 1 )
|
||||
{
|
||||
std::sort(f->multiPresetA+1,
|
||||
f->multiPresetA+f->multiPresetN-1,
|
||||
[](const flow::preset_order_t& a,const flow::preset_order_t& b){ return a.order<b.order; } );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -1691,7 +1742,14 @@ cw::rc_t cw::preset_sel::report_presets( handle_t h )
|
||||
return rc;
|
||||
}
|
||||
|
||||
cw::rc_t cw::preset_sel::translate_frags( const object_t* cfg )
|
||||
{
|
||||
return cwLogError(kNotImplementedRC,"translate_frags() is not implemented.");
|
||||
}
|
||||
|
||||
|
||||
#undef NOT_DEF
|
||||
#ifdef NOT_DEF
|
||||
cw::rc_t cw::preset_sel::translate_frags( const object_t* cfg )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
@ -1918,4 +1976,4 @@ cw::rc_t cw::preset_sel::translate_frags( const object_t* cfg )
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -13,8 +13,8 @@ namespace cw
|
||||
bool playFl; // play this preset
|
||||
bool seqFl; // play this preset during sequencing.
|
||||
unsigned preset_idx; // preset index into preset_labelA[].
|
||||
unsigned order; //
|
||||
char* alt_str;
|
||||
unsigned order; // selection label
|
||||
char* alt_str; // 'alt' label
|
||||
} preset_t;
|
||||
|
||||
typedef struct frag_str
|
||||
@ -35,6 +35,9 @@ namespace cw
|
||||
preset_t* presetA; // presetA[ presetN ] - status of each preset
|
||||
unsigned presetN;
|
||||
|
||||
flow::preset_order_t* multiPresetA; // array of active presets in this frag. sequenced by ascending 'order'.
|
||||
unsigned multiPresetN;
|
||||
|
||||
unsigned* altPresetIdxA; // altPresetIdxA[ alt_count() ] selected preset idx for each alt.
|
||||
|
||||
bool uiSelectFl;
|
||||
@ -81,6 +84,8 @@ namespace cw
|
||||
unsigned alt_count( handle_t h );
|
||||
const char* alt_label( handle_t h, unsigned alt_idx );
|
||||
|
||||
void get_loc_range( handle_t h, unsigned& minLocRef, unsigned& maxLocRef );
|
||||
|
||||
unsigned fragment_count( handle_t h );
|
||||
const frag_t* get_fragment_base( handle_t h );
|
||||
const frag_t* get_fragment( handle_t h, unsigned fragId );
|
||||
|
@ -290,7 +290,7 @@ namespace cw
|
||||
rc_t _gen_synced_perf_files( test_t* p,
|
||||
sfscore::handle_t scoreH,
|
||||
score_follower::handle_t sfH,
|
||||
perf_meas::handle_t perfMeasH )
|
||||
perf_meas::handle_t perfMeasH)
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
const object_t* jobL = nullptr;
|
||||
@ -345,6 +345,9 @@ namespace cw
|
||||
unsigned end_loc = kInvalidId;
|
||||
bool skip_score_follow_fl = false;
|
||||
|
||||
|
||||
cwLogInfo("\nProcessing:%s",cwStringNullGuard(dirEntryArray[i].name));
|
||||
|
||||
// read the meta object
|
||||
if((rc = objectFromFile( meta_fname, meta_obj)) != kOkRC )
|
||||
rc = cwLogError(rc,"An object could not be formed from the meta data file '%s'.",cwStringNullGuard(meta_fname));
|
||||
@ -365,7 +368,7 @@ namespace cw
|
||||
p->srate,
|
||||
p->print_rt_events_fl,
|
||||
false,
|
||||
sync_perf_fname)) != kOkRC )
|
||||
sync_perf_fname )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"The score follower failed on '%s'. Consider setting the 'skip_score_follow_fl' in '%s'.",cwStringNullGuard(midi_fname),cwStringNullGuard(meta_fname));
|
||||
}
|
||||
|
@ -121,6 +121,21 @@ namespace cw
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool _processPedal(const char* pedalLabel, unsigned barNumb, bool curPedalDownStateFl, uint8_t d1)
|
||||
{
|
||||
bool newStateFl = midi::isPedalDown(d1);
|
||||
/*
|
||||
if( newStateFl == curPedalDownStateFl )
|
||||
{
|
||||
const char* upDownLabel = newStateFl ? "down" : "up";
|
||||
cwLogWarning("Double %s pedal %s in bar number:%i.",pedalLabel,upDownLabel,barNumb);
|
||||
}
|
||||
*/
|
||||
|
||||
return newStateFl;
|
||||
}
|
||||
|
||||
|
||||
rc_t _destroy( score_follower_t* p)
|
||||
{
|
||||
destroy(p->dynRefH);
|
||||
@ -576,10 +591,14 @@ cw::rc_t cw::score_follower::midi_state_rt_report( handle_t h, const char* out_f
|
||||
|
||||
cw::rc_t cw::score_follower::write_sync_perf_csv( handle_t h, const char* out_fname, const midi::file::trackMsg_t** msgA, unsigned msgN )
|
||||
{
|
||||
score_follower_t* p = _handleToPtr(h);
|
||||
rc_t rc = kOkRC;
|
||||
unsigned resultN = result_count(p->trackH);
|
||||
auto resultA = result_base(p->trackH);
|
||||
score_follower_t* p = _handleToPtr(h);
|
||||
rc_t rc = kOkRC;
|
||||
unsigned resultN = result_count(p->trackH);
|
||||
auto resultA = result_base(p->trackH);
|
||||
bool dampPedalDownFl = false;
|
||||
bool sostPedalDownFl = false;
|
||||
bool softPedalDownFl = false;
|
||||
unsigned curBarNumb = 1;
|
||||
file::handle_t fH;
|
||||
|
||||
if( msgN == 0 )
|
||||
@ -602,7 +621,7 @@ cw::rc_t cw::score_follower::write_sync_perf_csv( handle_t h, const char* out_fn
|
||||
}
|
||||
|
||||
// write the header line
|
||||
file::printf(fH,"meas,index,voice,loc,tick,sec,dur,rval,dots,sci_pitch,dmark,dlevel,status,d0,d1,bar,section,bpm,grace,pedal\n");
|
||||
file::printf(fH,"meas,index,voice,loc,tick,sec,dur,rval,dots,sci_pitch,dmark,dlevel,status,d0,d1,bar,section,bpm,grace,damp_down_fl,soft_down_fl,sost_down_fl\n");
|
||||
|
||||
for(unsigned i=0; i<msgN; ++i)
|
||||
{
|
||||
@ -616,35 +635,73 @@ cw::rc_t cw::score_follower::write_sync_perf_csv( handle_t h, const char* out_fn
|
||||
uint8_t d0 = m->u.chMsgPtr->d0;
|
||||
uint8_t d1 = m->u.chMsgPtr->d1;
|
||||
|
||||
|
||||
if( midi::isNoteOn(m->status,d1) )
|
||||
{
|
||||
const unsigned INVALID_LOC = 0;
|
||||
|
||||
unsigned bar = 0;
|
||||
const char* sectionLabel = "";
|
||||
unsigned loc = score_parse::kInvalidLocId;
|
||||
unsigned dlevel = -1;
|
||||
char sciPitch[ midi::kMidiSciPitchCharCnt + 1 ];
|
||||
unsigned bar = 0;
|
||||
midi::midiToSciPitch( d0, sciPitch, midi::kMidiSciPitchCharCnt );
|
||||
unsigned loc = INVALID_LOC;
|
||||
|
||||
midi::midiToSciPitch( d0, sciPitch, midi::kMidiSciPitchCharCnt );
|
||||
|
||||
// locate score matching record for this performed note
|
||||
for(unsigned i=0; i<resultN; ++i)
|
||||
{
|
||||
const sfscore::event_t* e;
|
||||
// FIX THIS:
|
||||
// THE perfA[] INDEX IS STORED IN resultA[i].muid
|
||||
// this isn't right.
|
||||
assert( resultA[i].muid != kInvalidIdx && resultA[i].muid < p->perfN );
|
||||
|
||||
if( p->perfA[resultA[i].muid].muid == m->uid )
|
||||
if( p->perfA[resultA[i].muid].muid == m->uid && resultA[i].scEvtIdx != kInvalidIdx)
|
||||
{
|
||||
assert( resultA[i].pitch == d0 );
|
||||
loc = resultA[i].oLocId == kInvalidId ? INVALID_LOC : resultA[i].oLocId;
|
||||
|
||||
if((e = event( p->scoreH, resultA[i].scEvtIdx )) == nullptr )
|
||||
{
|
||||
cwLogError(kInvalidStateRC,"The performed, and matched, note with muid %i does not have a valid score event index.",m->uid);
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
bar = e->barNumb;
|
||||
sectionLabel = e->section != nullptr ? e->section->label : "";
|
||||
curBarNumb = std::max(bar,curBarNumb);
|
||||
dlevel = e->dynLevel;
|
||||
loc = resultA[i].oLocId == kInvalidId ? score_parse::kInvalidLocId : resultA[i].oLocId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc = file::printf(fH, "%i,%i,%i,%i,0,%f,0.0,0.0,0,%s,,,%i,%i,%i,,,,,\n",
|
||||
bar,i,1,loc,secs,sciPitch,m->status,d0,d1);
|
||||
|
||||
|
||||
rc = file::printf(fH, "%i,%i,%i,%i,0,%f,0.0,0.0,0,%s,,%i,%i,%i,%i,,%s,,,%i,%i,%i\n",
|
||||
bar,i,1,loc,secs,sciPitch,dlevel,m->status,d0,d1,sectionLabel,dampPedalDownFl,softPedalDownFl,sostPedalDownFl);
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = file::printf(fH, ",%i,,,%i,%f,,,,,,,%i,%i,%i,,,,,\n",i,0,secs,m->status,d0,d1);
|
||||
|
||||
if( midi::isPedal(m->status,d0) )
|
||||
{
|
||||
switch( d0 )
|
||||
{
|
||||
case midi::kSustainCtlMdId:
|
||||
dampPedalDownFl = _processPedal("damper",curBarNumb,dampPedalDownFl,d1);
|
||||
break;
|
||||
|
||||
case midi::kSostenutoCtlMdId:
|
||||
sostPedalDownFl = _processPedal("sostenuto",curBarNumb,sostPedalDownFl,d1);
|
||||
break;
|
||||
|
||||
case midi::kSoftPedalCtlMdId:
|
||||
softPedalDownFl = _processPedal("soft",curBarNumb,softPedalDownFl,d1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rc = file::printf(fH, ",%i,,,%i,%f,,,,,,,%i,%i,%i,,,,,%i,%i,%i\n",i,0,secs,m->status,d0,d1,dampPedalDownFl,softPedalDownFl,sostPedalDownFl);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,6 +163,38 @@ namespace cw
|
||||
return s;
|
||||
}
|
||||
|
||||
rc_t _parse_section_stats( score_parse_t* p, csv::handle_t csvH, event_t* e )
|
||||
{
|
||||
rc_t rc;
|
||||
|
||||
if((rc = getv(csvH,
|
||||
"even_min", e->section->statsA[ kEvenStatIdx ].min,
|
||||
"even_max", e->section->statsA[ kEvenStatIdx ].max,
|
||||
"even_mean", e->section->statsA[ kEvenStatIdx ].mean,
|
||||
"even_std", e->section->statsA[ kEvenStatIdx ].std,
|
||||
"dyn_min", e->section->statsA[ kDynStatIdx ].min,
|
||||
"dyn_max", e->section->statsA[ kDynStatIdx ].max,
|
||||
"dyn_mean", e->section->statsA[ kDynStatIdx ].mean,
|
||||
"dyn_std", e->section->statsA[ kDynStatIdx ].std,
|
||||
"tempo_min", e->section->statsA[ kTempoStatIdx ].min,
|
||||
"tempo_max", e->section->statsA[ kTempoStatIdx ].max,
|
||||
"tempo_mean",e->section->statsA[ kTempoStatIdx ].mean,
|
||||
"tempo_std", e->section->statsA[ kTempoStatIdx ].std,
|
||||
"cost_min", e->section->statsA[ kCostStatIdx ].min,
|
||||
"cost_max", e->section->statsA[ kCostStatIdx ].max,
|
||||
"cost_mean", e->section->statsA[ kCostStatIdx ].mean,
|
||||
"cost_std", e->section->statsA[ kCostStatIdx ].std )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Error parsing CSV meas. stats field.");
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
errLabel:
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
rc_t _parse_section_row( score_parse_t* p, csv::handle_t csvH, event_t* e )
|
||||
{
|
||||
rc_t rc;
|
||||
@ -180,6 +212,9 @@ namespace cw
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
//if((rc = _parse_section_stats(p,csvH,e)) != kOkRC )
|
||||
// goto errLabel;
|
||||
|
||||
e->section->csvRowNumb = e->csvRowNumb;
|
||||
|
||||
errLabel:
|
||||
|
@ -2,6 +2,11 @@ namespace cw
|
||||
{
|
||||
namespace score_parse
|
||||
{
|
||||
|
||||
enum {
|
||||
kInvalidLocId = 0
|
||||
};
|
||||
|
||||
enum {
|
||||
kInvalidTId,
|
||||
kBarTId,
|
||||
@ -43,8 +48,26 @@ namespace cw
|
||||
kVarCnt
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
kDynStatIdx,
|
||||
kEvenStatIdx,
|
||||
kTempoStatIdx,
|
||||
kCostStatIdx,
|
||||
kStatCnt
|
||||
} stats_idx_t;
|
||||
|
||||
struct set_str;
|
||||
struct event_str;
|
||||
|
||||
typedef struct stats_str
|
||||
{
|
||||
stats_idx_t id;
|
||||
double min;
|
||||
double max;
|
||||
double mean;
|
||||
double std;
|
||||
} stats_t;
|
||||
|
||||
typedef struct section_str
|
||||
{
|
||||
char* label; // This sections label
|
||||
@ -55,6 +78,9 @@ namespace cw
|
||||
struct event_str* endEvent; // last event in this section
|
||||
struct event_str* begSetEvent; // first set event in this section
|
||||
struct event_str* endSetEvent; // last set event in this section
|
||||
|
||||
stats_t statsA[ kStatCnt ];
|
||||
|
||||
struct section_str* link; // p->sectionL links
|
||||
} section_t;
|
||||
|
||||
|
@ -92,7 +92,7 @@ cw::rc_t cw::serialPortSrv::create( handle_t& h, unsigned pollPeriodMs, unsigned
|
||||
if((rc = serialPort::create( p->mgrH, recvBufByteN)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
if((rc = thread::create( p->threadH, threadCallback, p)) != kOkRC )
|
||||
if((rc = thread::create( p->threadH, threadCallback, p, "serial_srv")) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
p->pollPeriodMs = pollPeriodMs;
|
||||
|
@ -243,6 +243,20 @@ namespace cw
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc_t _assign_section_to_events( sfscore_t* p )
|
||||
{
|
||||
for(unsigned si=0,ei=0; si<p->sectionN ; ++si)
|
||||
{
|
||||
// the last event in a section is the event just prior to the first event in the next section
|
||||
unsigned end_evt_idx = si>=p->sectionN-1 ? p->eventN : p->sectionA[si+1].begEvtIndex;
|
||||
|
||||
for(; ei<end_evt_idx; ++ei)
|
||||
p->eventA[ ei ].section = p->sectionA + si;
|
||||
|
||||
}
|
||||
return kOkRC;
|
||||
}
|
||||
|
||||
rc_t _create_section_array( sfscore_t* p )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
@ -785,6 +799,9 @@ namespace cw
|
||||
if((rc = _create_section_array( p )) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
if((rc = _assign_section_to_events(p)) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
if((rc = _create_set_array( p )) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
|
@ -9,6 +9,8 @@ namespace cw
|
||||
struct loc_str;
|
||||
struct set_str;
|
||||
|
||||
typedef score_parse::stats_t stats_t;
|
||||
|
||||
// The score can be divided into arbitrary non-overlapping sections.
|
||||
typedef struct section_str
|
||||
{
|
||||
@ -20,8 +22,7 @@ namespace cw
|
||||
unsigned endEvtIndex; // last element in this section
|
||||
unsigned setCnt; // Count of elements in setArray[]
|
||||
struct set_str** setArray; // Ptrs to sets which are applied to this section.
|
||||
|
||||
//double vars[ score_parse::kVarCnt ]; // Set to DBL_MAX by default.
|
||||
//stats_t statsA[ score_parse::kStatCnt ];
|
||||
} section_t;
|
||||
|
||||
typedef struct var_str
|
||||
@ -43,6 +44,7 @@ namespace cw
|
||||
unsigned flags; // Attribute flags for this event
|
||||
unsigned dynLevel; // Dynamcis value pppp to ffff (1 to 11) for this note.
|
||||
double frac; // Note's time value for tempo and non-grace evenness notes.
|
||||
section_t* section; // The section to which this event belongs
|
||||
unsigned barNumb; // Bar id of the measure containing this event.
|
||||
unsigned barNoteIdx; // Index of this note in this bar
|
||||
unsigned csvRowNumb; // File row number (not index) from which this record originated
|
||||
|
@ -23,24 +23,24 @@ namespace cw
|
||||
callback_func_t cbFunc;
|
||||
void* cbArg;
|
||||
sfmatch::handle_t matchH;
|
||||
unsigned mn; // size of midiBuf[]
|
||||
sfmatch::midi_t* midiBuf; // midiBuf[mn]
|
||||
unsigned mn; // length of midiBuf[]
|
||||
sfmatch::midi_t* midiBuf; // midiBuf[mn] MIDI event window
|
||||
|
||||
result_t* res; // res[rn]
|
||||
result_t* res; // res[rn] result buffer
|
||||
unsigned rn; // length of res[] (set to 2*score event count)
|
||||
unsigned ri; // next avail res[] recd.
|
||||
|
||||
double s_opt; //
|
||||
unsigned missCnt; // current count of consecutive trailing non-matches
|
||||
unsigned ili; // index into loc[] to start scan following reset
|
||||
unsigned eli; // index into loc[] of the last positive match.
|
||||
unsigned mni; // current count of MIDI events since the last call to cmScMatcherReset()
|
||||
unsigned ili; // index into sfmatch_t.loc[] to start scan following reset
|
||||
unsigned eli; // index into sfmatch_t.loc[] of the last positive match.
|
||||
unsigned mni; // current count of MIDI events since the last call to reset()
|
||||
unsigned mbi; // index of oldest MIDI event in midiBuf[]; stays at 0 when the buffer is full.
|
||||
unsigned begSyncLocIdx; // start of score window, in mp->loc[], of best match in previous scan
|
||||
unsigned initHopCnt; // max window hops during the initial (when the MIDI buffer fills for first time) sync scan
|
||||
unsigned stepCnt; // count of forward/backward score loc's to examine for a match during cmScMatcherStep().
|
||||
unsigned maxMissCnt; // max. number of consecutive non-matches during step prior to executing a scan.
|
||||
unsigned scanCnt; // current count of times a resync-scan was executed during cmScMatcherStep()
|
||||
unsigned stepCnt; // count of forward/backward score loc's to examine for a match during _step().
|
||||
unsigned maxMissCnt; // max. number of consecutive non-matches during step prior to executing a _scan().
|
||||
unsigned scanCnt; // current count of times a resync-scan was executed during _step()
|
||||
|
||||
unsigned flags;
|
||||
} sftrack_t;
|
||||
|
10
cwSfTrack.h
10
cwSfTrack.h
@ -50,10 +50,12 @@ namespace cw
|
||||
// Notes:
|
||||
// The cwSfTrack maintains an internal cwSfMatch object which is used to attempt to find the
|
||||
// best match between the current MIDI active note buffer and the current score search area.
|
||||
// 'scWndN' is used to set the cwSfMatch 'locN' argument.
|
||||
// 'midiWndN' sets the length of the MIDI FIFO which is used to match to the score with
|
||||
// each recceived MIDI note.
|
||||
// 'midiWndN' must be <= 'scWndN'.
|
||||
//
|
||||
// 'scWndN' is used to set the cwSfMatch 'locN' argument. It defines the length of the
|
||||
// score window over which the MIDI event window will slide, while searching for the best match.
|
||||
//
|
||||
// 'midiWndN' sets the length of the MIDI FIFO winodw which is used to match to the score with
|
||||
// each recceived MIDI note. 'midiWndN' must be <= 'scWndN'.
|
||||
|
||||
rc_t destroy( handle_t& hRef );
|
||||
|
||||
|
@ -1211,7 +1211,7 @@ cw::rc_t cw::socksrv::createMgrSrv( handle_t& hRef, unsigned timeOutMs, unsigne
|
||||
goto errLabel;
|
||||
|
||||
// create the thread
|
||||
if((rc = thread::create( p->thH, _threadFunc, p)) != kOkRC )
|
||||
if((rc = thread::create( p->thH, _threadFunc, p, "sock")) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
p->timeOutMs = timeOutMs;
|
||||
|
@ -7,6 +7,12 @@
|
||||
#include "cwTime.h"
|
||||
#include "cwMidi.h"
|
||||
#include "cwMidiFile.h"
|
||||
|
||||
#include "cwDynRefTbl.h"
|
||||
#include "cwScoreParse.h"
|
||||
#include "cwSfScore.h"
|
||||
#include "cwPerfMeas.h"
|
||||
|
||||
#include "cwPianoScore.h"
|
||||
#include "cwSvg.h"
|
||||
#include "cwMidiState.h"
|
||||
|
@ -7,13 +7,14 @@
|
||||
#include "cwTime.h"
|
||||
#include "cwMidi.h"
|
||||
#include "cwMidiFile.h"
|
||||
#include "cwPianoScore.h"
|
||||
#include "cwSvg.h"
|
||||
#include "cwDynRefTbl.h"
|
||||
#include "cwScoreParse.h"
|
||||
#include "cwSfScore.h"
|
||||
#include "cwSfMatch.h"
|
||||
#include "cwSfTrack.h"
|
||||
#include "cwPerfMeas.h"
|
||||
#include "cwPianoScore.h"
|
||||
|
||||
#include "cwScoreFollowerPerf.h"
|
||||
#include "cwScoreFollower.h"
|
||||
|
@ -110,7 +110,7 @@ cw::rc_t cw::net::srv::create(
|
||||
if((rc = socket::create( p->sockH, port, flags, timeOutMs, remoteAddr, remotePort, localAddr )) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
if((rc = thread::create( p->threadH, _threadFunc, p )) != kOkRC )
|
||||
if((rc = thread::create( p->threadH, _threadFunc, p, "tcp_sock_srv" )) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
p->flags = srvFlags;
|
||||
|
@ -120,7 +120,7 @@ cw::rc_t cw::net::socket::test( portNumber_t localPort, const char* remoteAddr,
|
||||
if((rc = create(app.sockH,localPort, kBlockingFl,timeOutMs, NULL, kInvalidPortNumber )) != kOkRC )
|
||||
return rc;
|
||||
|
||||
if((rc = thread::create( app.threadH, _dgramThreadFunc, &app )) != kOkRC )
|
||||
if((rc = thread::create( app.threadH, _dgramThreadFunc, &app, "tcp_sock_test_tcp" )) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
if((rc = thread::unpause( app.threadH )) != kOkRC )
|
||||
@ -176,7 +176,7 @@ cw::rc_t cw::net::socket::test_tcp( portNumber_t localPort, const char* remoteAd
|
||||
return rc;
|
||||
|
||||
// create the listening thread (which is really only used by the server)
|
||||
if((rc = thread::create( app.threadH, streamFl ? _tcpStreamThreadFunc : _dgramThreadFunc, &app )) != kOkRC )
|
||||
if((rc = thread::create( app.threadH, streamFl ? _tcpStreamThreadFunc : _dgramThreadFunc, &app, "tcp_sock_test" )) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
// if this is a streaming client then connect to the server (which must have already been started)
|
||||
|
48
cwThread.cpp
48
cwThread.cpp
@ -31,13 +31,14 @@ namespace cw
|
||||
unsigned pauseMicros;
|
||||
unsigned sleepMicros;
|
||||
pthread_attr_t attr;
|
||||
char* label;
|
||||
|
||||
} thread_t;
|
||||
|
||||
inline thread_t* _handleToPtr(handle_t h) { return handleToPtr<handle_t,thread_t>(h); }
|
||||
|
||||
// Called from client thread to wait for the internal thread to transition to a specified state.
|
||||
rc_t _waitForState( thread_t* p, unsigned stateId )
|
||||
rc_t _waitForState( thread_t* p, stateId_t stateId )
|
||||
{
|
||||
unsigned waitTimeMicroSecs = 0;
|
||||
stateId_t curStateId;
|
||||
@ -120,7 +121,7 @@ namespace cw
|
||||
}
|
||||
|
||||
|
||||
cw::rc_t cw::thread::create( handle_t& hRef, cbFunc_t func, void* funcArg, int stateMicros, int pauseMicros )
|
||||
cw::rc_t cw::thread::create( handle_t& hRef, cbFunc_t func, void* funcArg, const char* label, int stateMicros, int pauseMicros )
|
||||
{
|
||||
rc_t rc;
|
||||
int sysRC;
|
||||
@ -136,6 +137,7 @@ cw::rc_t cw::thread::create( handle_t& hRef, cbFunc_t func, void* funcArg, int s
|
||||
p->pauseMicros = pauseMicros;
|
||||
p->stateId = kPausedThId;
|
||||
p->sleepMicros = 15000;
|
||||
p->label = mem::duplStr(label);
|
||||
|
||||
if((sysRC = pthread_attr_init(&p->attr)) != 0)
|
||||
{
|
||||
@ -143,6 +145,7 @@ cw::rc_t cw::thread::create( handle_t& hRef, cbFunc_t func, void* funcArg, int s
|
||||
rc = cwLogSysError(kOpFailRC,sysRC,"Thread attribute init failed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
|
||||
// Creating the thread in a detached state should prevent it from leaking memory when
|
||||
@ -160,8 +163,17 @@ cw::rc_t cw::thread::create( handle_t& hRef, cbFunc_t func, void* funcArg, int s
|
||||
p->stateId = kNotInitThId;
|
||||
rc = cwLogSysError(kOpFailRC,sysRC,"Thread create failed.");
|
||||
}
|
||||
}
|
||||
|
||||
if( label != nullptr )
|
||||
pthread_setname_np(p->pThreadH, label);
|
||||
|
||||
|
||||
hRef.set(p);
|
||||
|
||||
|
||||
cwLogInfo("Thread %s id:%p created.",cwStringNullGuard(label), p->pThreadH);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -180,16 +192,16 @@ cw::rc_t cw::thread::destroy( handle_t& hRef )
|
||||
|
||||
// wait for the thread to exit and then deallocate the thread object
|
||||
if((rc = _waitForState(p,kExitedThId)) != kOkRC )
|
||||
return cwLogError(rc,"Thread timed out waiting for destroy.");
|
||||
return cwLogError(rc,"Thread '%s' timed out waiting for destroy.",p->label);
|
||||
|
||||
// Block until the thread is actually fully cleaned up
|
||||
if((sysRC = pthread_join(p->pThreadH,NULL)) != 0)
|
||||
rc = cwLogSysError(kOpFailRC,sysRC,"Thread join failed.");
|
||||
rc = cwLogSysError(kOpFailRC,sysRC,"Thread '%s' join failed.",p->label);
|
||||
|
||||
//if( pthread_attr_destroy(&p->attr) != 0 )
|
||||
// rc = cwLogError(kOpFailRC,"Thread attribute destroy failed.");
|
||||
|
||||
|
||||
mem::release(p->label);
|
||||
mem::release(p);
|
||||
hRef.clear();
|
||||
|
||||
@ -205,7 +217,7 @@ cw::rc_t cw::thread::pause( handle_t h, unsigned cmdFlags )
|
||||
thread_t* p = _handleToPtr(h);
|
||||
stateId_t curStateId = p->stateId.load(std::memory_order_acquire);
|
||||
bool isPausedFl = curStateId == kPausedThId;
|
||||
unsigned waitId;
|
||||
stateId_t waitId;
|
||||
|
||||
if( isPausedFl == pauseFl )
|
||||
return kOkRC;
|
||||
@ -225,7 +237,7 @@ cw::rc_t cw::thread::pause( handle_t h, unsigned cmdFlags )
|
||||
rc = _waitForState(p,waitId);
|
||||
|
||||
if( rc != kOkRC )
|
||||
cwLogError(rc,"Thread timed out waiting for '%s'.", pauseFl ? "pause" : "un-pause");
|
||||
cwLogError(rc,"Thread '%s' timed out waiting for '%s'. pauseMicros:%i stateMicros:%i sleepMicros:%i", p->label, pauseFl ? "pause" : "un-pause",p->pauseMicros,p->stateMicros,p->sleepMicros);
|
||||
|
||||
return rc;
|
||||
|
||||
@ -256,6 +268,26 @@ cw::thread::thread_id_t cw::thread::id()
|
||||
return id.u.id;
|
||||
}
|
||||
|
||||
const char* cw::thread::label( handle_t h )
|
||||
{
|
||||
thread_t* p = _handleToPtr(h);
|
||||
return p->label==nullptr ? "<no_thread_label>" : p->label;
|
||||
}
|
||||
|
||||
|
||||
unsigned cw::thread::stateTimeOutMicros( handle_t h)
|
||||
{
|
||||
thread_t* p = _handleToPtr(h);
|
||||
return p->stateMicros;
|
||||
}
|
||||
|
||||
unsigned cw::thread::pauseMicros( handle_t h )
|
||||
{
|
||||
thread_t* p = _handleToPtr(h);
|
||||
return p->pauseMicros;
|
||||
}
|
||||
|
||||
|
||||
namespace cw
|
||||
{
|
||||
bool _threadTestCb( void* p )
|
||||
@ -273,7 +305,7 @@ cw::rc_t cw::threadTest()
|
||||
rc_t rc;
|
||||
char c = 0;
|
||||
|
||||
if((rc = thread::create(h,_threadTestCb,&val)) != kOkRC )
|
||||
if((rc = thread::create(h,_threadTestCb,&val,"thread_test")) != kOkRC )
|
||||
return rc;
|
||||
|
||||
if((rc = thread::pause(h,0)) != kOkRC )
|
||||
|
16
cwThread.h
16
cwThread.h
@ -5,6 +5,8 @@ namespace cw
|
||||
{
|
||||
namespace thread
|
||||
{
|
||||
const int kDefaultStateTimeOutMicros=100000;
|
||||
const int kDefaultPauseMicros = 10000;
|
||||
typedef enum
|
||||
{
|
||||
kNotInitThId,
|
||||
@ -15,13 +17,21 @@ namespace cw
|
||||
|
||||
typedef handle<struct thread_str> handle_t;
|
||||
|
||||
// Return false to indicate that the thread should terminate.
|
||||
typedef bool (*cbFunc_t)( void* arg );
|
||||
|
||||
typedef unsigned long long thread_id_t;
|
||||
|
||||
// The thread is in the 'paused' state after it is created.
|
||||
// stateMicros = total time out duration for switching to the exit state or for switching in/out of pause state.
|
||||
// pauseMicros = duration of thread sleep interval when in paused state.
|
||||
rc_t create( handle_t& hRef, cbFunc_t func, void* funcArg, int stateTimeOutMicros=100000, int pauseMicros=10000 );
|
||||
rc_t create( handle_t& hRef,
|
||||
cbFunc_t func,
|
||||
void* funcArg,
|
||||
const char* label, // Assign a label which will show up via `top -H` or `ps -T`.
|
||||
int stateTimeOutMicros=kDefaultStateTimeOutMicros,
|
||||
int pauseMicros=kDefaultPauseMicros );
|
||||
|
||||
rc_t destroy( handle_t& hRef );
|
||||
|
||||
|
||||
@ -33,6 +43,10 @@ namespace cw
|
||||
|
||||
// Return the thread id of the calling context.
|
||||
thread_id_t id();
|
||||
const char* label( handle_t h );
|
||||
|
||||
unsigned stateTimeOutMicros( handle_t h);
|
||||
unsigned pauseMicros( handle_t h );
|
||||
}
|
||||
rc_t threadTest();
|
||||
}
|
||||
|
@ -25,13 +25,13 @@ namespace cw
|
||||
thread_mach_t* _handleToPtr( handle_t h )
|
||||
{ return handleToPtr<handle_t,thread_mach_t>(h); }
|
||||
|
||||
rc_t _add( thread_mach_t* p, threadFunc_t func, void* arg )
|
||||
rc_t _add( thread_mach_t* p, threadFunc_t func, void* arg, const char* label )
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
thread_t* t = mem::allocZ<thread_t>();
|
||||
|
||||
if((rc = thread::create(t->thH, func, arg )) != kOkRC )
|
||||
if((rc = thread::create(t->thH, func, arg, label==nullptr ? "thread_mach" : label )) != kOkRC )
|
||||
{
|
||||
rc = cwLogError(rc,"Thread create failed.");
|
||||
goto errLabel;
|
||||
@ -90,7 +90,7 @@ cw::rc_t cw::thread_mach::create( handle_t& hRef, threadFunc_t threadFunc, void*
|
||||
{
|
||||
void* arg = ctxA + (i*contexRecdByteN);
|
||||
|
||||
if((rc = _add(p, threadFunc, arg)) != kOkRC )
|
||||
if((rc = _add(p, threadFunc, arg, nullptr)) != kOkRC )
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
@ -103,10 +103,10 @@ cw::rc_t cw::thread_mach::create( handle_t& hRef, threadFunc_t threadFunc, void*
|
||||
return rc;
|
||||
}
|
||||
|
||||
cw::rc_t cw::thread_mach::add( handle_t h, threadFunc_t threadFunc, void* arg )
|
||||
cw::rc_t cw::thread_mach::add( handle_t h, threadFunc_t threadFunc, void* arg, const char* label )
|
||||
{
|
||||
thread_mach_t* p = _handleToPtr(h);
|
||||
return _add(p,threadFunc,arg);
|
||||
return _add(p,threadFunc,arg, label);
|
||||
}
|
||||
|
||||
cw::rc_t cw::thread_mach::destroy( handle_t& hRef )
|
||||
|
@ -16,7 +16,7 @@ namespace cw
|
||||
|
||||
// Create an additional thread. Note that the additional thread will be started by the next
|
||||
// call to 'start()'.
|
||||
rc_t add( handle_t h, threadFunc_t threadFunc, void* arg );
|
||||
rc_t add( handle_t h, threadFunc_t threadFunc, void* arg, const char* label );
|
||||
|
||||
// Start all threads
|
||||
rc_t start( handle_t h );
|
||||
|
98
cwTime.cpp
98
cwTime.cpp
@ -44,11 +44,16 @@ void cw::time::get( spec_t& t )
|
||||
#include <sys/time.h> // gettimeofday()
|
||||
void cw::time::get( spec_t& t )
|
||||
{
|
||||
// NOTcw::mutex::lock(h,timeout) relies on using
|
||||
// CLOCK_REALTIME. If the source of this clock changes
|
||||
// then change cw::mutex::loc(h,timeout) as well
|
||||
clock_gettime(CLOCK_REALTIME,&t);
|
||||
clock_gettime(CLOCK_MONOTONIC,&t);
|
||||
}
|
||||
|
||||
cw::time::spec_t cw::time::current_time()
|
||||
{
|
||||
spec_t t;
|
||||
clock_gettime(CLOCK_REALTIME,&t);
|
||||
return t;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// this assumes that the seconds have been normalized to a recent start time
|
||||
@ -177,8 +182,8 @@ cw::rc_t cw::time::now( spec_t& ts )
|
||||
|
||||
memset(&ts,0,sizeof(ts));
|
||||
|
||||
if((errRC = clock_gettime(CLOCK_REALTIME, &ts)) != 0 )
|
||||
rc = cwLogSysError(kInvalidOpRC,errRC,"Unable to obtain system time.");
|
||||
if((errRC = clock_gettime(CLOCK_MONOTONIC, &ts)) != 0 )
|
||||
rc = cwLogSysError(kInvalidOpRC,errRC,"Unable to obtain system time.");
|
||||
|
||||
return rc;
|
||||
}
|
||||
@ -213,51 +218,40 @@ void cw::time::subtractMicros( spec_t& ts, unsigned micros )
|
||||
|
||||
}
|
||||
|
||||
|
||||
void cw::time::advanceMicros( spec_t& ts, unsigned us )
|
||||
{
|
||||
if( us > 1000000 )
|
||||
{
|
||||
// strip off whole seconds from usec
|
||||
unsigned sec = us / 1000000;
|
||||
const unsigned us_per_sec = 1000000;
|
||||
const unsigned ns_per_sec = 1000000000;
|
||||
|
||||
// find the remaining fractional second in microseconds
|
||||
us = (us - sec*1000000);
|
||||
unsigned sec = us / us_per_sec;
|
||||
|
||||
ts.tv_sec += sec;
|
||||
}
|
||||
ts.tv_sec += sec;
|
||||
ts.tv_nsec += (us - sec*us_per_sec)*1000;
|
||||
|
||||
ts.tv_nsec += us * 1000; // convert microseconds to nanoseconds
|
||||
|
||||
// stip off whole seconds from tv_nsec
|
||||
while( ts.tv_nsec > 1000000000 )
|
||||
{
|
||||
ts.tv_nsec -= 1000000000;
|
||||
ts.tv_sec +=1;
|
||||
}
|
||||
sec = ts.tv_nsec / ns_per_sec;
|
||||
|
||||
ts.tv_sec += sec;
|
||||
ts.tv_nsec -= sec * ns_per_sec;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void cw::time::advanceMs( spec_t& ts, unsigned ms )
|
||||
{
|
||||
if( ms > 1000 )
|
||||
{
|
||||
// strip off whole seconds from ms
|
||||
unsigned sec = ms / 1000;
|
||||
|
||||
// find the remaining fractional second in milliseconds
|
||||
ms = (ms - sec*1000);
|
||||
const unsigned ms_per_sec = 1000;
|
||||
const unsigned ns_per_sec = 1000000000;
|
||||
|
||||
ts.tv_sec += sec;
|
||||
}
|
||||
unsigned sec = ms / ms_per_sec;
|
||||
|
||||
ts.tv_nsec += ms * 1000000; // convert millisconds to nanoseconds
|
||||
ts.tv_sec += sec;
|
||||
ts.tv_nsec += (ms - (sec*ms_per_sec)) * 1000000;
|
||||
|
||||
// stip off whole seconds from tv_nsec
|
||||
while( ts.tv_nsec > 1000000000 )
|
||||
{
|
||||
ts.tv_nsec -= 1000000000;
|
||||
ts.tv_sec +=1;
|
||||
}
|
||||
sec = ts.tv_nsec / ns_per_sec;
|
||||
|
||||
ts.tv_sec += sec;
|
||||
ts.tv_nsec -= sec * ns_per_sec;
|
||||
}
|
||||
|
||||
cw::rc_t cw::time::futureMs( spec_t& ts, unsigned ms )
|
||||
@ -277,7 +271,7 @@ void cw::time::secondsToSpec( spec_t& ts, unsigned sec )
|
||||
|
||||
double cw::time::specToSeconds( const spec_t& t )
|
||||
{
|
||||
spec_t ts = t;
|
||||
spec_t ts = t;
|
||||
double sec = ts.tv_sec;
|
||||
while( ts.tv_nsec >= 1000000000 )
|
||||
{
|
||||
@ -288,6 +282,19 @@ double cw::time::specToSeconds( const spec_t& t )
|
||||
return sec + ((double)ts.tv_nsec)/1e9;
|
||||
}
|
||||
|
||||
unsigned long long cw::time::specToMicroseconds( const spec_t& ts )
|
||||
{
|
||||
const unsigned long long us_per_sec = 1000000;
|
||||
const unsigned long long ns_per_sec = 1000000000;
|
||||
|
||||
unsigned long long us = ts.tv_sec * us_per_sec;
|
||||
unsigned long long sec = ts.tv_nsec / ns_per_sec;
|
||||
us += sec * us_per_sec;
|
||||
us += (ts.tv_nsec - (sec * ns_per_sec))/1000;
|
||||
|
||||
return us;
|
||||
}
|
||||
|
||||
|
||||
void cw::time::millisecondsToSpec( spec_t& ts, unsigned ms )
|
||||
{
|
||||
@ -298,15 +305,24 @@ void cw::time::millisecondsToSpec( spec_t& ts, unsigned ms )
|
||||
ts.tv_nsec = ns;
|
||||
}
|
||||
|
||||
void cw::time::microsecondsToSpec( spec_t& ts, unsigned us )
|
||||
void cw::time::microsecondsToSpec( spec_t& ts, unsigned long long us )
|
||||
{
|
||||
unsigned sec = us/1000000;
|
||||
unsigned ns = (us - (sec*1000000)) * 1000;
|
||||
const unsigned long long usPerSec = 1000000;
|
||||
unsigned long long sec = us/usPerSec;
|
||||
unsigned long long ns = (us - (sec*usPerSec)) * 1000;
|
||||
|
||||
ts.tv_sec = sec;
|
||||
ts.tv_nsec = ns;
|
||||
}
|
||||
|
||||
cw::time::spec_t cw::time::microsecondsToSpec( unsigned long long us )
|
||||
{
|
||||
spec_t ts;
|
||||
microsecondsToSpec(ts,us);
|
||||
return ts;
|
||||
}
|
||||
|
||||
|
||||
|
||||
unsigned cw::time::formatDateTime( char* buffer, unsigned bufN, bool includeDateFl )
|
||||
{
|
||||
|
8
cwTime.h
8
cwTime.h
@ -20,6 +20,7 @@ namespace cw
|
||||
|
||||
// Get the time
|
||||
void get( spec_t& tRef );
|
||||
spec_t current_time(); // same as get()
|
||||
|
||||
// Return the elapsed time (t1 - t0) in microseconds
|
||||
// t1 is assumed to be at a later time than t0.
|
||||
@ -61,7 +62,7 @@ namespace cw
|
||||
|
||||
void setZero( spec_t& t0 );
|
||||
|
||||
rc_t now( spec_t& ts );
|
||||
rc_t now( spec_t& ts ); // same as get()
|
||||
|
||||
void subtractMicros( spec_t& ts, unsigned us );
|
||||
|
||||
@ -75,8 +76,11 @@ namespace cw
|
||||
void secondsToSpec( spec_t& ts, unsigned sec );
|
||||
double specToSeconds( const spec_t& ts );
|
||||
|
||||
unsigned long long specToMicroseconds( const spec_t& ts );
|
||||
|
||||
void millisecondsToSpec( spec_t& ts, unsigned ms );
|
||||
void microsecondsToSpec( spec_t& ts, unsigned us );
|
||||
void microsecondsToSpec( spec_t& ts, unsigned long long us );
|
||||
spec_t microsecondsToSpec( unsigned long long us );
|
||||
|
||||
// Return count of bytes in in buf[]
|
||||
unsigned formatDateTime( char* buf, unsigned bufN, bool includeDateFl=false );
|
||||
|
100
cwUi.cpp
100
cwUi.cpp
@ -75,6 +75,15 @@ namespace cw
|
||||
bool destroyFl; // used by the deleteElement() algorithm
|
||||
} ele_t;
|
||||
|
||||
|
||||
const unsigned hashN = 0xffff;
|
||||
|
||||
typedef struct bucket_str
|
||||
{
|
||||
ele_t* ele;
|
||||
struct bucket_str* link;
|
||||
} bucket_t;
|
||||
|
||||
typedef struct ui_str
|
||||
{
|
||||
unsigned eleAllocN; // size of eleA[]
|
||||
@ -108,11 +117,59 @@ namespace cw
|
||||
unsigned sentMsgN;
|
||||
unsigned recvMsgN;
|
||||
|
||||
bucket_t hashA[ hashN ];
|
||||
|
||||
} ui_t;
|
||||
|
||||
ui_t* _handleToPtr( handle_t h )
|
||||
{ return handleToPtr<handle_t,ui_t>(h); }
|
||||
|
||||
unsigned short _gen_hash_index( unsigned parentUuId, unsigned appId )
|
||||
{
|
||||
assert( parentUuId != kInvalidId && appId != kInvalidId );
|
||||
unsigned hc = parentUuId + cwSwap32(appId);
|
||||
|
||||
return (unsigned short)(((hc & 0xffff0000)>>16) + (hc & 0x0000ffff));
|
||||
}
|
||||
|
||||
|
||||
void _store_ele_in_hash_table( ui_t* p, ele_t* e )
|
||||
{
|
||||
unsigned parentUuId = e->logical_parent == nullptr ? kInvalidIdx : e->logical_parent->uuId;
|
||||
|
||||
if( parentUuId == kInvalidId || e->appId == kInvalidId )
|
||||
return;
|
||||
|
||||
unsigned short hash_idx = _gen_hash_index( parentUuId, e->appId );
|
||||
|
||||
if( p->hashA[ hash_idx ].ele == nullptr )
|
||||
p->hashA[ hash_idx ].ele = e;
|
||||
else
|
||||
{
|
||||
bucket_t* b = mem::allocZ<bucket_t>();
|
||||
b->link = p->hashA[ hash_idx ].link;
|
||||
b->ele = e;
|
||||
p->hashA[hash_idx].link = b;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
unsigned _find_ele_in_hash_table( ui_t* p, unsigned parentUuId, unsigned appId, unsigned chanId )
|
||||
{
|
||||
if( parentUuId != kInvalidId && appId != kInvalidId )
|
||||
{
|
||||
unsigned hash_idx = _gen_hash_index(parentUuId,appId);
|
||||
bucket_t* b = p->hashA + hash_idx;
|
||||
|
||||
for(; b!=nullptr; b=b->link)
|
||||
if( b->ele->appId==appId && b->ele->logical_parent->uuId==parentUuId && (chanId==kInvalidId || b->ele->chanId==chanId) )
|
||||
return b->ele->uuId;
|
||||
}
|
||||
|
||||
return kInvalidId;
|
||||
|
||||
}
|
||||
|
||||
void _print_eles( ui_t* p )
|
||||
{
|
||||
for(unsigned i=0; i<p->eleN; ++i)
|
||||
@ -306,16 +363,23 @@ namespace cw
|
||||
if( appId == kRootAppId )
|
||||
return kRootUuId;
|
||||
|
||||
// try looking up the result in the hash table
|
||||
unsigned uuid;
|
||||
if((uuid = _find_ele_in_hash_table(p,parentUuId,appId,chanId)) != kInvalidId )
|
||||
return uuid;
|
||||
|
||||
// if the result could not be found in the hash table (possibly because parentUuId is set to the wildcard i.e. kInvalidId)
|
||||
// then do an exhaustive search
|
||||
for(unsigned i=0; i<p->eleN; ++i)
|
||||
if( p->eleA[i]!=nullptr && p->eleA[i]->logical_parent != nullptr ) //skip the root
|
||||
if( p->eleA[i] != nullptr
|
||||
&& p->eleA[i]->appId == appId
|
||||
&& ( parentUuId == kInvalidId || (p->eleA[i]->logical_parent!=nullptr && p->eleA[i]->logical_parent->uuId == parentUuId) )
|
||||
&& ( chanId == kInvalidId || p->eleA[i]->chanId == chanId ) )
|
||||
{
|
||||
if( ( parentUuId == kInvalidId || p->eleA[i]->logical_parent->uuId == parentUuId ) &&
|
||||
( chanId == kInvalidId || p->eleA[i]->chanId == chanId ) &&
|
||||
p->eleA[i]->appId == appId )
|
||||
{
|
||||
return p->eleA[i]->uuId;
|
||||
}
|
||||
|
||||
return p->eleA[i]->uuId;
|
||||
}
|
||||
|
||||
return kInvalidId;
|
||||
}
|
||||
|
||||
@ -550,6 +614,9 @@ namespace cw
|
||||
|
||||
unsigned _find_and_available_element_slot( ui_t* p )
|
||||
{
|
||||
if( p->eleN < p->eleAllocN && p->eleA[ p->eleN ] == nullptr )
|
||||
return p->eleN;
|
||||
|
||||
for(unsigned i=0; i<p->eleN; ++i)
|
||||
if( p->eleA[i] == nullptr )
|
||||
return i;
|
||||
@ -608,7 +675,7 @@ namespace cw
|
||||
// if there are no available slots
|
||||
if( avail_ele_idx == p->eleAllocN )
|
||||
{
|
||||
p->eleAllocN += 128;
|
||||
p->eleAllocN *= 2;
|
||||
p->eleA = mem::resizeZ<ele_t*>(p->eleA,p->eleAllocN);
|
||||
}
|
||||
|
||||
@ -635,6 +702,8 @@ namespace cw
|
||||
e->appId = m->appId;
|
||||
}
|
||||
|
||||
_store_ele_in_hash_table(p, e );
|
||||
|
||||
//printf("uuid:%i appId:%i par-uuid:%i %s\n", e->uuId,e->appId,e->parent==nullptr ? -1 : e->parent->uuId, cwStringNullGuard(e->eleName));
|
||||
|
||||
return e;
|
||||
@ -698,7 +767,7 @@ namespace cw
|
||||
if((rc = o->get("name",eleName, cw::kOptionalFl)) != kOkRC )
|
||||
{
|
||||
// div's and titles don't need a 'name'
|
||||
if( rc == kLabelNotFoundRC && (divAliasFl || textCompare(eleType,"label")==0) )
|
||||
if( rc == kEleNotFoundRC && (divAliasFl || textCompare(eleType,"label")==0) )
|
||||
rc = kOkRC;
|
||||
else
|
||||
{
|
||||
@ -1268,7 +1337,7 @@ cw::rc_t cw::ui::create(
|
||||
ui_t* p = mem::allocZ<ui_t>();
|
||||
|
||||
|
||||
p->eleAllocN = 128;
|
||||
p->eleAllocN = 1024;
|
||||
p->eleA = mem::allocZ<ele_t*>( p->eleAllocN );
|
||||
p->eleN = 0;
|
||||
p->uiCbFunc = uiCbFunc;
|
||||
@ -2025,6 +2094,7 @@ cw::rc_t cw::ui::destroyElement( handle_t h, unsigned uuId )
|
||||
if( p->eleA[i] != nullptr && p->eleA[i]->destroyFl )
|
||||
{
|
||||
_destroy_element( p->eleA[i] );
|
||||
|
||||
p->eleA[i] = nullptr;
|
||||
}
|
||||
|
||||
@ -2106,6 +2176,12 @@ cw::rc_t cw::ui::sendValueString( handle_t h, unsigned uuId, const char* value )
|
||||
//return _sendValue<const char*>(p,kInvalidId,uuId,"\"%s\"",value,"value",strlen(value)+10);
|
||||
}
|
||||
|
||||
cw::rc_t cw::ui::sendMsg( handle_t h, const char* msg )
|
||||
{
|
||||
ui_t* p = _handleToPtr(h);
|
||||
return _websockSend(p,kInvalidId,msg);
|
||||
}
|
||||
|
||||
void cw::ui::report( handle_t h )
|
||||
{
|
||||
ui_t* p = _handleToPtr(h);
|
||||
@ -2223,7 +2299,7 @@ cw::rc_t cw::ui::ws::parseArgs( const object_t& o, args_t& args, const char* ob
|
||||
op = &o;
|
||||
else
|
||||
if((op = o.find(object_label)) == nullptr )
|
||||
return cwLogError(kLabelNotFoundRC,"The ui configuration label '%s' was not found.", cwStringNullGuard(object_label));
|
||||
return cwLogError(kEleNotFoundRC,"The ui configuration label '%s' was not found.", cwStringNullGuard(object_label));
|
||||
|
||||
if((rc = op->getv(
|
||||
"physRootDir", args.physRootDir,
|
||||
@ -2496,7 +2572,7 @@ cw::rc_t cw::ui::srv::create( handle_t& h,
|
||||
goto errLabel;
|
||||
}
|
||||
|
||||
if((rc = thread::create( p->thH, _threadCallback, p )) != kOkRC )
|
||||
if((rc = thread::create( p->thH, _threadCallback, p, "ui", thread::kDefaultStateTimeOutMicros, thread::kDefaultPauseMicros )) != kOkRC )
|
||||
{
|
||||
cwLogError(rc,"The websock UI server thread create failed.");
|
||||
goto errLabel;
|
||||
|
3
cwUi.h
3
cwUi.h
@ -169,6 +169,7 @@ namespace cw
|
||||
rc_t sendValueDouble( handle_t h, unsigned uuId, double value );
|
||||
rc_t sendValueString( handle_t h, unsigned uuId, const char* value );
|
||||
|
||||
rc_t sendMsg( handle_t h, const char* msg );
|
||||
|
||||
void report( handle_t h );
|
||||
void realTimeReport( handle_t h );
|
||||
@ -224,6 +225,8 @@ namespace cw
|
||||
|
||||
// This function should be called periodically to send and receive
|
||||
// queued messages to and from the websocket.
|
||||
// Note that this call may block for up to 'wsTimeOutMs' milliseconds
|
||||
// on the websocket handle.
|
||||
rc_t exec( handle_t h );
|
||||
|
||||
// This function executes the internal default websock callback function.
|
||||
|
20
cwVectOps.h
20
cwVectOps.h
@ -148,6 +148,26 @@ namespace cw
|
||||
}
|
||||
|
||||
|
||||
//==================================================================================================================
|
||||
// absolute value
|
||||
//
|
||||
template<typename T>
|
||||
T* abs( T* v, unsigned n )
|
||||
{
|
||||
for(unsigned i=0; i<n; ++i)
|
||||
v[i] = abs(v[i]);
|
||||
return v;
|
||||
}
|
||||
|
||||
template<typename T0, typename T1>
|
||||
T0* abs( T0* v0, const T1* v1, unsigned n )
|
||||
{
|
||||
for(unsigned i=0; i<n; ++i)
|
||||
v0[i] = abs(v1[i]);
|
||||
return v0;
|
||||
}
|
||||
|
||||
|
||||
//==================================================================================================================
|
||||
// Arithmetic
|
||||
//
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include "cwText.h"
|
||||
#include "cwObject.h"
|
||||
#include "cwTime.h"
|
||||
#include "cwPresetSel.h"
|
||||
#include "cwFile.h"
|
||||
#include "cwFileSys.h"
|
||||
#include "cwMidi.h"
|
||||
|
@ -41,6 +41,12 @@ namespace cw
|
||||
int _pollfdMaxN;
|
||||
int _pollfdN;
|
||||
|
||||
unsigned _sendMsgCnt; // Count of msg's sent
|
||||
unsigned _sendMaxByteN; // Max size across all sent msg's
|
||||
|
||||
unsigned _recvMsgCnt; // Count of msg's recv'd
|
||||
unsigned _recvMaxByteN; // Max size across all recv'd msg's
|
||||
|
||||
} websock_t;
|
||||
|
||||
inline websock_t* _handleToPtr(handle_t h)
|
||||
@ -264,7 +270,11 @@ namespace cw
|
||||
//printf("recv: sess:%i proto:%s : %p : len:%li\n",sess->id,proto->name,ws->_cbFunc,len);
|
||||
|
||||
if( ws->_cbFunc != nullptr && len>0)
|
||||
{
|
||||
ws->_cbFunc(ws->_cbArg,proto->id,sess->id,kMessageTId,in,len);
|
||||
ws->_recvMsgCnt += 1;
|
||||
ws->_recvMaxByteN = std::max(ws->_recvMaxByteN,(unsigned)len);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@ -303,7 +313,7 @@ namespace cw
|
||||
|
||||
msg_t* t = m1->link;
|
||||
|
||||
mem::free(m1->msg);
|
||||
//mem::free(m1->msg);
|
||||
mem::free(m1);
|
||||
|
||||
m1 = t;
|
||||
@ -321,6 +331,8 @@ namespace cw
|
||||
{
|
||||
msg_t* m;
|
||||
|
||||
cwLogInfo("Websock: sent: msgs:%i largest msg:%i - recv: msgs:%i largest msg:%i - LWS_PRE:%i",p->_sendMsgCnt,p->_sendMaxByteN,p->_recvMsgCnt,p->_recvMaxByteN,LWS_PRE);
|
||||
|
||||
if( p->_ctx != nullptr )
|
||||
{
|
||||
lws_context_destroy(p->_ctx);
|
||||
@ -332,7 +344,7 @@ namespace cw
|
||||
|
||||
while((m = p->_q->pop()) != nullptr)
|
||||
{
|
||||
mem::free(m->msg);
|
||||
//mem::free(m->msg);
|
||||
mem::free(m);
|
||||
}
|
||||
|
||||
@ -351,7 +363,7 @@ namespace cw
|
||||
{
|
||||
msg_t* tmp = m->link;
|
||||
|
||||
mem::free(m->msg);
|
||||
//mem::free(m->msg);
|
||||
mem::free(m);
|
||||
m = tmp;
|
||||
}
|
||||
@ -491,9 +503,14 @@ cw::rc_t cw::websock::send(handle_t h, unsigned protocolId, unsigned sessionId,
|
||||
{
|
||||
rc_t rc = kOkRC;
|
||||
|
||||
msg_t* m = mem::allocZ<msg_t>(1);
|
||||
m->msg = mem::allocZ<unsigned char>(byteN);
|
||||
memcpy(m->msg,msg,byteN);
|
||||
uint8_t* mem = mem::allocZ<uint8_t>( sizeof(msg_t) + LWS_PRE + byteN );
|
||||
//msg_t* m = mem::allocZ<msg_t>(1);
|
||||
//m->msg = mem::allocZ<unsigned char>(byteN);
|
||||
|
||||
msg_t* m = (msg_t*)mem;
|
||||
m->msg = mem + sizeof(msg_t);
|
||||
|
||||
memcpy(m->msg+LWS_PRE,msg,byteN);
|
||||
m->msgByteN = byteN;
|
||||
m->protocolId = protocolId;
|
||||
m->sessionId = sessionId;
|
||||
@ -501,6 +518,9 @@ cw::rc_t cw::websock::send(handle_t h, unsigned protocolId, unsigned sessionId,
|
||||
websock_t* p = _handleToPtr(h);
|
||||
p->_q->push(m);
|
||||
|
||||
p->_sendMsgCnt += 1;
|
||||
p->_sendMaxByteN = std::max(p->_sendMaxByteN,byteN);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -578,12 +598,13 @@ cw::rc_t cw::websock::exec( handle_t h, unsigned timeOutMs )
|
||||
|
||||
|
||||
// add the pre-padding bytes to the msg
|
||||
unsigned char* msg = mem::allocZ<unsigned char>(LWS_PRE + m->msgByteN);
|
||||
memcpy( msg+LWS_PRE, m->msg, m->msgByteN );
|
||||
//unsigned char* msg = mem::allocZ<unsigned char>(LWS_PRE + m->msgByteN);
|
||||
//memcpy( msg+LWS_PRE, m->msg, m->msgByteN );
|
||||
|
||||
mem::free(m->msg); // free the original msg buffer
|
||||
//mem::free(m->msg); // free the original msg buffer
|
||||
|
||||
//m->msg = msg;
|
||||
|
||||
m->msg = msg;
|
||||
m->msgId = ps->nextNewMsgId; // set the msg id
|
||||
ps->begMsg->link = m; // put the msg on the front of the outgoing queue
|
||||
ps->begMsg = m; //
|
||||
|
@ -68,7 +68,7 @@ cw::rc_t cw::websockSrv::create(
|
||||
goto errLabel;
|
||||
|
||||
|
||||
if((rc = thread::create(p->_thread,_websockSrvThreadCb,p)) != kOkRC )
|
||||
if((rc = thread::create(p->_thread,_websockSrvThreadCb,p,"web_sock_srv")) != kOkRC )
|
||||
goto errLabel;
|
||||
|
||||
p->_timeOutMs = timeOutMs;
|
||||
|
BIN
docs/2-3-trees.pdf
Normal file
BIN
docs/2-3-trees.pdf
Normal file
Binary file not shown.
@ -3,170 +3,23 @@ var _rootId = "0";
|
||||
var _nextEleId = 0;
|
||||
var _focusId = null;
|
||||
var _focusVal = null;
|
||||
var _rootDivEle = null;
|
||||
var _rootEle = null;
|
||||
|
||||
function set_app_title( suffix, className )
|
||||
{
|
||||
var ele = document.getElementById('connectTitleId');
|
||||
ele.innerHTML = suffix
|
||||
ele.className = className
|
||||
}
|
||||
|
||||
|
||||
function uiOnError( msg, r)
|
||||
{
|
||||
console.log("Error:" + msg);
|
||||
}
|
||||
|
||||
function uiGetParent( r )
|
||||
{
|
||||
parent_ele = document.getElementById(r.parent_id);
|
||||
|
||||
if( parent_ele == null )
|
||||
if(ele != null)
|
||||
{
|
||||
uiOnError("Parent not found. parent_id:" + r.parent_id,r);
|
||||
ele.innerHTML = suffix
|
||||
ele.className = className
|
||||
}
|
||||
|
||||
return parent_ele;
|
||||
}
|
||||
|
||||
function uiCreateEle( r )
|
||||
{
|
||||
var parent_ele;
|
||||
|
||||
if((parent_ele = uiGetParent(r)) != null )
|
||||
else
|
||||
{
|
||||
ele = document.createElement(r.ele_type)
|
||||
ele.id = r.ele_id;
|
||||
ele.className = r.value;
|
||||
|
||||
parent_ele.appendChild(ele)
|
||||
console.log("Ele. not found. Set title failed.")
|
||||
}
|
||||
}
|
||||
|
||||
function uiRemoveChildren( r )
|
||||
{
|
||||
ele = document.getElementById(r.ele_id)
|
||||
|
||||
while (ele.firstChild)
|
||||
{
|
||||
ele.removeChild(ele.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
function uiDivCreate( r )
|
||||
{ uiCreateEle(r) }
|
||||
|
||||
function uiLabelCreate( r )
|
||||
{
|
||||
var parent_ele;
|
||||
|
||||
if((parent_ele = uiGetParent(r)) != null )
|
||||
{
|
||||
ele = document.createElement("label")
|
||||
ele.htmlFor = r.ele_id
|
||||
ele.innerHTML = r.value;
|
||||
parent_ele.appendChild(ele)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function uiSelectCreate( r )
|
||||
{
|
||||
uiCreateEle(r)
|
||||
}
|
||||
|
||||
function uiSelectClear( r )
|
||||
{ uiRemoveChildren(r) }
|
||||
|
||||
function uiSelectInsert( r )
|
||||
{
|
||||
var select_ele;
|
||||
|
||||
if((select_ele = uiGetParent(r)) != null )
|
||||
{
|
||||
var option = document.createElement('option');
|
||||
|
||||
option.id = r.ele_id;
|
||||
option.innerHTML = r.value;
|
||||
option.value = r.ele_id;
|
||||
option.onclick = function() { uiOnSelectClick(this) }
|
||||
|
||||
select_ele.appendChild(option)
|
||||
}
|
||||
}
|
||||
|
||||
function uiSelectChoose( r )
|
||||
{
|
||||
var select_ele;
|
||||
|
||||
if((select_ele = uiGetParent(r)) != null )
|
||||
{
|
||||
if( select_ele.hasChildNodes())
|
||||
{
|
||||
var children = select_ele.childNodes
|
||||
for(var i=0; i<children.length; i++)
|
||||
{
|
||||
if( children[i].id == r.ele_id )
|
||||
{
|
||||
select_ele.selectedIndex = i
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function uiOnSelectClick( ele )
|
||||
{
|
||||
cmdstr = "mode ui ele_type select op choose parent_id "+ele.parentElement.id+" option_id " + ele.id
|
||||
websocket.send(cmdstr);
|
||||
|
||||
}
|
||||
|
||||
function uiNumberOnKeyUp( e )
|
||||
{
|
||||
if( e.keyCode == 13 )
|
||||
{
|
||||
//console.log(e)
|
||||
cmdstr = "mode ui ele_type number op change parent_id "+e.srcElement.parentElement.id+" ele_id " + e.srcElement.id + " value " + e.srcElement.value
|
||||
websocket.send(cmdstr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function uiNumberCreate( r )
|
||||
{
|
||||
var parent_ele;
|
||||
|
||||
if((parent_ele = uiGetParent(r)) != null )
|
||||
{
|
||||
ele = document.createElement("input")
|
||||
ele.id = r.ele_id
|
||||
ele.setAttribute('type','number')
|
||||
ele.addEventListener('keyup',uiNumberOnKeyUp)
|
||||
parent_ele.appendChild(ele)
|
||||
}
|
||||
}
|
||||
|
||||
function uiNumberSet( r )
|
||||
{
|
||||
var ele;
|
||||
|
||||
//console.log("ele_id:" + r.ele_id + " parent_id:" + r.parent_id + " value:" + r.value)
|
||||
|
||||
if((ele = document.getElementById(r.parent_id)) != null)
|
||||
{
|
||||
switch( r.ele_id )
|
||||
{
|
||||
case "0": ele.min = r.value; break;
|
||||
case "1": ele.max = r.value; break;
|
||||
case "2": ele.step = r.value; break;
|
||||
case "3": ele.value = r.value; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function dom_child_by_id( parentEle, child_id )
|
||||
{
|
||||
@ -204,9 +57,31 @@ function dom_set_number( ele_id, val )
|
||||
|
||||
|
||||
//==============================================================================
|
||||
|
||||
function dom_id_to_ele( id )
|
||||
{ return document.getElementById(id); }
|
||||
{
|
||||
var ele = null;
|
||||
|
||||
if( _rootEle == null )
|
||||
{
|
||||
console.log("fail dtoe: root null");
|
||||
}
|
||||
else
|
||||
{
|
||||
if( id == _rootId )
|
||||
return _rootEle
|
||||
|
||||
//ele = _rootEle.getElementById(id);
|
||||
|
||||
ele = document.getElementById(id)
|
||||
if( ele == null )
|
||||
{
|
||||
console.log("fail dtoe:" + id + " " + typeof(id) );
|
||||
}
|
||||
}
|
||||
return ele
|
||||
}
|
||||
|
||||
// { return document.getElementById(id); }
|
||||
|
||||
function dom_set_checkbox( ele_id, fl )
|
||||
{ dom_id_to_ele(ele_id).checked = fl }
|
||||
@ -1197,6 +1072,11 @@ function ui_destroy( d )
|
||||
ele.parentElement.removeChild( ele )
|
||||
}
|
||||
|
||||
function ui_attach( d )
|
||||
{
|
||||
console.log("ATTACH");
|
||||
//_rootDivEle.appendChild(_rootEle)
|
||||
}
|
||||
|
||||
|
||||
function ws_send( s )
|
||||
@ -1230,6 +1110,10 @@ function _ws_on_msg( d )
|
||||
ui_set( d )
|
||||
break;
|
||||
|
||||
case 'attach':
|
||||
ui_attach(d)
|
||||
break;
|
||||
|
||||
default:
|
||||
ui_error("Unknown UI operation. " + d.op )
|
||||
}
|
||||
@ -1275,7 +1159,7 @@ function ws_form_url(urlSuffix)
|
||||
return pcol + u[0] + "/" + urlSuffix;
|
||||
}
|
||||
|
||||
function main()
|
||||
function main_0()
|
||||
{
|
||||
d = { "className":"uiAppDiv", "uuId":_rootId }
|
||||
rootEle = ui_create_div( document.body, d )
|
||||
@ -1290,7 +1174,28 @@ function main()
|
||||
_ws.onmessage = ws_on_msg
|
||||
_ws.onopen = ws_on_open
|
||||
_ws.onclose = ws_on_close;
|
||||
|
||||
|
||||
}
|
||||
|
||||
function main()
|
||||
{
|
||||
d = { "className":"uiAppDiv", "uuId":"rootDivEleId" }
|
||||
_rootDivEle = ui_create_div( document.body, d )
|
||||
|
||||
//_rootEle = document.createDocumentFragment();
|
||||
_rootEle = _rootDivEle
|
||||
|
||||
_rootEle.uuId = 0;
|
||||
_rootEle.id = _nextEleId;
|
||||
_nextEleId += 1;
|
||||
|
||||
//console.log(ws_form_url(""))
|
||||
|
||||
_ws = new WebSocket(ws_form_url(""),"ui_protocol")
|
||||
|
||||
_ws.onmessage = ws_on_msg
|
||||
_ws.onopen = ws_on_open
|
||||
_ws.onclose = ws_on_close;
|
||||
|
||||
console.log("main() done.")
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
button:{ name: ioReportBtnId, title:"IO Report" },
|
||||
button:{ name: netPrintBtnId, title:"Print Network" }
|
||||
button:{ name: reportBtnId, title:"App Report" },
|
||||
button:{ name: latencyBtnId, title:"Latency Reset"},
|
||||
button:{ name: saveBtnId, title:"Save" },
|
||||
|
||||
select:{ name: perfSelId, title:"Load",children: {} },
|
||||
@ -19,6 +20,12 @@
|
||||
|
||||
},
|
||||
|
||||
row: {
|
||||
check: { name: presetProbPriCheckId, title: "Pri Prob::" },
|
||||
check: { name: presetProbSecCheckId, title: "Sec Prob:" },
|
||||
check: { name: presetInterpCheckId, title: "Interp:"}
|
||||
},
|
||||
|
||||
row: {
|
||||
check: { name: printMidiCheckId, title: "Print MIDI" },
|
||||
check: { name: pianoMidiCheckId, title: "Piano MIDI" },
|
||||
|
Loading…
Reference in New Issue
Block a user