Merge branch 'master' of gitea.larke.org:kevin/libcw

This commit is contained in:
kevin 2024-02-18 17:58:52 -05:00
commit 021a1c2808
78 changed files with 5839 additions and 1539 deletions

View File

@ -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 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 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 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 libcwSRC += src/libcw/cwFileSys.cpp src/libcw/cwText.cpp src/libcw/cwFile.cpp src/libcw/cwTime.cpp src/libcw/cwLex.cpp
@ -28,7 +31,7 @@ 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 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 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 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 libcwSRC += src/libcw/cwFlow.cpp src/libcw/cwFlowTypes.cpp src/libcw/cwFlowProc.cpp src/libcw/cwFlowCross.cpp
if cwWEBSOCK if cwWEBSOCK
@ -54,8 +57,12 @@ libcwSRC += src/libcw/cwAudioDevice.cpp
if cwALSA if cwALSA
libcwHDR += src/libcw/cwMidiPort.h src/libcw/cwAudioDeviceAlsa.h src/libcw/cwAudioDeviceFile.h libcwHDR += src/libcw/cwMidiDevice.h src/libcw/cwMidiParser.h src/libcw/cwMidiAlsa.h src/libcw/cwMidiFileDev.h src/libcw/cwMidiDeviceTest.h
libcwSRC += src/libcw/cwMidiPort.cpp src/libcw/cwMidiAlsa.cpp src/libcw/cwAudioDeviceAlsa.cpp src/libcw/cwAudioDeviceFile.cpp src/libcw/cwAudioDeviceTest.cpp 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 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 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

View File

@ -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->pollfds = mem::allocZ<struct pollfd>( p->pollfdsAllocCnt );
p->pollfdsDesc = mem::allocZ<pollfdsDesc_t>(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."); rc = cwLogError(rc,"Thread create failed.");
} }

View File

@ -664,7 +664,7 @@ cw::rc_t cw::audio::device::file::create( handle_t& hRef, struct driver_str*&
p->driver.deviceSeek = deviceSeek; p->driver.deviceSeek = deviceSeek;
p->driver.deviceRealTimeReport = deviceRealTimeReport; 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."); rc = cwLogError(rc,"Audio device file thread create failed.");
goto errLabel; goto errLabel;

View File

@ -412,7 +412,7 @@ int main( int argc, const char* argv[] )
// create the TCP listening thread // 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; goto errLabel;
// Allocate Avahi thread // Allocate Avahi thread

47
cwB23Tree.cpp Normal file
View 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
View 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

View File

@ -38,7 +38,7 @@ namespace cw
kOpFailRC, // 19 kOpFailRC, // 19
kSyntaxErrorRC, // 20 kSyntaxErrorRC, // 20
kBufTooSmallRC, // 21 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 kDuplicateRC, // 23 - an invalid duplicate was detected
kAssertFailRC, // 24 - used with cwLogFatal kAssertFailRC, // 24 - used with cwLogFatal
kInvalidDataTypeRC, // 25 kInvalidDataTypeRC, // 25

View File

@ -237,7 +237,7 @@ namespace cw
for(unsigned i=0; i<titleN; ++i) for(unsigned i=0; i<titleN; ++i)
if( _title_to_col_index( p, titleA[i] ) == kInvalidIdx ) 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; 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); 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 ) cw::rc_t cw::csv::rewind( handle_t h )
{ {

View File

@ -22,6 +22,7 @@ namespace cw
const char* col_title( handle_t h, unsigned idx ); const char* col_title( handle_t h, unsigned idx );
unsigned title_col_index( handle_t h, const char* title ); 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. // Reset the CSV to make the title line current.
// The next call to 'next_line()' will make the first data row current. // The next call to 'next_line()' will make the first data row current.

View File

@ -942,7 +942,7 @@ namespace cw
return cwLogError(rc,"Unable to create EuCon server."); return cwLogError(rc,"Unable to create EuCon server.");
// Create the application thread // 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."); return cwLogError(rc,"App thread create failed.");
// Start the application thread // Start the application thread

File diff suppressed because it is too large Load Diff

View File

@ -46,6 +46,7 @@ namespace cw
} external_device_t; } external_device_t;
void print_abuf( const struct abuf_str* abuf ); void print_abuf( const struct abuf_str* abuf );
void print_external_device( const external_device_t* dev ); void print_external_device( const external_device_t* dev );
@ -58,6 +59,7 @@ namespace cw
rc_t destroy( handle_t& hRef ); rc_t destroy( handle_t& hRef );
unsigned preset_cfg_flags( handle_t h );
// Run one cycle of the network. // Run one cycle of the network.
rc_t exec_cycle( handle_t h ); rc_t exec_cycle( handle_t h );
@ -66,6 +68,9 @@ namespace cw
rc_t exec( handle_t h ); rc_t exec( handle_t h );
rc_t apply_preset( handle_t h, const char* presetLabel ); 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, bool value );
rc_t set_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, int value ); rc_t set_variable_value( handle_t h, const char* inst_label, const char* var_label, unsigned chIdx, int value );

View File

@ -9,6 +9,7 @@
#include "cwMtx.h" #include "cwMtx.h"
#include "cwDspTypes.h" // real_t, sample_t #include "cwDspTypes.h" // real_t, sample_t
#include "cwDspTransforms.h" #include "cwDspTransforms.h"
#include "cwFlowDecl.h"
#include "cwFlow.h" #include "cwFlow.h"
#include "cwFlowTypes.h" #include "cwFlowTypes.h"
#include "cwFlowProc.h" #include "cwFlowProc.h"
@ -355,6 +356,15 @@ cw::rc_t cw::flow_cross::destroy( handle_t& hRef )
return rc; 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 ) 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; 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 ) 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); } { return _set_variable_value(h,destId,inst_label,var_label,chIdx,value); }

View File

@ -25,10 +25,13 @@ namespace cw
rc_t destroy( handle_t& hRef ); rc_t destroy( handle_t& hRef );
unsigned preset_cfg_flags( handle_t h );
// Run one cycle of the network. // Run one cycle of the network.
rc_t exec_cycle( handle_t h ); 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 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, 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 ); 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
View 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

View File

@ -8,6 +8,7 @@
#include "cwMtx.h" #include "cwMtx.h"
#include "cwDspTypes.h" // real_t, sample_t #include "cwDspTypes.h" // real_t, sample_t
#include "cwFlowDecl.h"
#include "cwFlow.h" #include "cwFlow.h"
#include "cwFlowTypes.h" #include "cwFlowTypes.h"
#include "cwFlowProc.h" #include "cwFlowProc.h"

View File

@ -7,6 +7,7 @@
#include "cwVectOps.h" #include "cwVectOps.h"
#include "cwMtx.h" #include "cwMtx.h"
#include "cwDspTypes.h" // real_t, sample_t #include "cwDspTypes.h" // real_t, sample_t
#include "cwFlowDecl.h"
#include "cwFlow.h" #include "cwFlow.h"
#include "cwFlowTypes.h" #include "cwFlowTypes.h"

View File

@ -211,6 +211,9 @@ namespace cw
const object_t* presetCfg; // presets designed for this network 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 cycleIndex; // Incremented with each processing cycle
unsigned maxCycleCount; // count of cycles to run on flow::exec() or 0 if there is no limit. unsigned maxCycleCount; // count of cycles to run on flow::exec() or 0 if there is no limit.

160
cwIo.cpp
View File

@ -9,7 +9,7 @@
#include "cwIo.h" #include "cwIo.h"
#include "cwMidi.h" #include "cwMidi.h"
#include "cwMidiPort.h" #include "cwMidiDevice.h"
#include "cwObject.h" #include "cwObject.h"
@ -31,6 +31,7 @@
#include "cwWebSock.h" #include "cwWebSock.h"
#include "cwUi.h" #include "cwUi.h"
#include "cwVectOps.h"
namespace cw namespace cw
{ {
@ -165,6 +166,10 @@ namespace cw
unsigned uiMapN; // unsigned uiMapN; //
bool uiAsyncFl; 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; } io_t;
@ -366,7 +371,7 @@ namespace cw
time::get(t->nextTime); 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."); rc = cwLogError(rc,"Timer thread assignment failed.");
} }
@ -604,7 +609,7 @@ namespace cw
msg_t m; msg_t m;
midi_msg_t mm; midi_msg_t mm;
const midi::packet_t* pkt = pktArray + i; 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; rc_t rc = kOkRC;
@ -628,7 +633,6 @@ namespace cw
rc_t _midiPortCreate( io_t* p, const object_t* c ) rc_t _midiPortCreate( io_t* p, const object_t* c )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
unsigned parserBufByteN = 1024;
const object_t* cfg = nullptr; const object_t* cfg = nullptr;
// get the MIDI port cfg // get the MIDI port cfg
@ -638,14 +642,12 @@ namespace cw
return kOkRC; return kOkRC;
} }
if((rc = cfg->getv("parserBufByteN", parserBufByteN, if((rc = cfg->getv("asyncFl", p->midiAsyncFl )) != kOkRC )
"asyncFl", p->midiAsyncFl )) != kOkRC )
{ {
rc = cwLogError(kSyntaxErrorRC,"MIDI configuration parse failed."); rc = cwLogError(kSyntaxErrorRC,"MIDI configuration parse failed.");
} }
// initialie the MIDI system if((rc = create(p->midiH, _midiCallback, p, cfg)) != kOkRC )
if((rc = create(p->midiH, _midiCallback, p, parserBufByteN, "app")) != kOkRC )
return rc; return rc;
@ -846,7 +848,7 @@ namespace cw
// create the socket thread // create the socket thread
if( p->sockN > 0 ) 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."); rc = cwLogError(rc,"Error creating socket thread.");
goto errLabel; goto errLabel;
@ -1070,7 +1072,7 @@ namespace cw
return true; 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 }; enum { kAudioGroupGetBuf, kAudioGroupAdvBuf };
void _audioGroupProcSampleBufs( io_t* p, audioGroup_t* ag, unsigned processTypeId, unsigned inputFl ) 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 // 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(). // which is triggered when all the devices in the group are ready by _audioGroupNotifyIfReady().
bool _audioGroupThreadFunc( void* arg ) bool _audioGroupThreadFunc( void* arg )
@ -1134,6 +1169,8 @@ namespace cw
if((rc = _ioCallback( ag->p, ag->asyncFl, &msg)) != kOkRC ) if((rc = _ioCallback( ag->p, ag->asyncFl, &msg)) != kOkRC )
cwLogError(rc,"Audio app callback failed %i.",ag->asyncFl); 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, true );
_audioGroupProcSampleBufs( ag->p, ag, kAudioGroupAdvBuf, false ); _audioGroupProcSampleBufs( ag->p, ag, kAudioGroupAdvBuf, false );
} }
@ -1530,7 +1567,7 @@ namespace cw
} }
// create the audio group thread // 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."); rc = cwLogError(rc,"Error creating audio group thread.");
goto errLabel; goto errLabel;
@ -2107,6 +2144,8 @@ namespace cw
ui::ws::releaseArgs(args); 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() ) if( p->wsUiH.isValid() )
{ {
ui::flushCache( ui::ws::uiHandle( p->wsUiH )); 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 ); rc = ui::ws::exec( p->wsUiH );
} }
@ -2422,7 +2462,7 @@ void cw::io::realTimeReport( handle_t h )
// Thread // 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; rc_t rc = kOkRC;
io_t* p = _handleToPtr(h); 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; t->link = p->threadL;
p->threadL = t; 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."); rc = cwLogError(rc,"Thread create failed.");
return rc; return rc;
@ -2668,6 +2708,19 @@ cw::rc_t cw::io::midiDeviceSend( handle_t h, unsigned devIdx, unsigned portIdx,
// Audio // 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 ) unsigned cw::io::audioDeviceCount( handle_t h )
{ {
io_t* p = _handleToPtr(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; 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 ) void cw::io::uiReport( handle_t h )
{ {
ui::handle_t uiH; ui::handle_t uiH;

25
cwIo.h
View File

@ -164,7 +164,11 @@ namespace cw
rc_t start( handle_t h ); rc_t start( handle_t h );
rc_t pause( handle_t h ); rc_t pause( handle_t h );
rc_t stop( 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 ); rc_t exec( handle_t h, void* execCbArg=nullptr );
bool isShuttingDown( handle_t h ); bool isShuttingDown( handle_t h );
void report( handle_t h ); void report( handle_t h );
void realTimeReport( handle_t h ); void realTimeReport( handle_t h );
@ -173,7 +177,7 @@ namespace cw
// //
// Thread // 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 // Audio
// //
bool audioIsEnabled( handle_t h );
unsigned audioDeviceCount( handle_t h ); unsigned audioDeviceCount( handle_t h );
unsigned audioActiveDeviceCount( handle_t h );
unsigned audioDeviceLabelToIndex( handle_t h, const char* label ); unsigned audioDeviceLabelToIndex( handle_t h, const char* label );
const char* audioDeviceLabel( handle_t h, unsigned devIdx ); const char* audioDeviceLabel( handle_t h, unsigned devIdx );
rc_t audioDeviceSetUserId( handle_t h, unsigned devIdx, unsigned userId ); 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, double value );
rc_t uiSendValue( handle_t h, unsigned uuId, const char* 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 uiRealTimeReport( handle_t h );
void uiReport( handle_t h ); void uiReport( handle_t h );

View File

@ -870,8 +870,8 @@ namespace cw
am->devIdx = pkt->devIdx; am->devIdx = pkt->devIdx;
am->portIdx = pkt->portIdx; am->portIdx = pkt->portIdx;
am->timestamp = mm->timeStamp; am->timestamp = mm->timeStamp;
am->ch = mm->status & 0x0f; am->ch = mm->ch;
am->status = mm->status & 0xf0; am->status = mm->status;
am->d0 = mm->d0; am->d0 = mm->d0;
am->d1 = mm->d1; am->d1 = mm->d1;

View File

@ -10,6 +10,7 @@
#include "cwMtx.h" #include "cwMtx.h"
#include "cwDspTypes.h" #include "cwDspTypes.h"
#include "cwFlowDecl.h"
#include "cwFlow.h" #include "cwFlow.h"
#include "cwFlowTypes.h" #include "cwFlowTypes.h"
#include "cwFlowCross.h" #include "cwFlowCross.h"
@ -361,8 +362,10 @@ cw::rc_t cw::io_flow::create( handle_t& hRef,
errLabel: errLabel:
if( rc != kOkRC ) if( rc != kOkRC )
{
rc = cwLogError(rc,"io_flow create failed.");
_destroy(p); _destroy(p);
}
return rc; return rc;
@ -385,6 +388,12 @@ cw::rc_t cw::io_flow::destroy( handle_t& hRef )
return rc; 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 ) 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 ) 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 ); } { 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 ) 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 ); } { return flow_cross::set_variable_value( _handleToPtr(h)->crossFlowH, destId, inst_label, var_label, chIdx, value ); }

View File

@ -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 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 ); rc_t destroy( handle_t& hRef );
unsigned preset_cfg_flags( handle_t h );
rc_t exec( handle_t h, const io::msg_t& msg ); rc_t exec( handle_t h, const io::msg_t& msg );
@ -21,6 +23,8 @@ namespace cw
// activate the next network. // 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 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, 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, 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 ); 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 );

View File

@ -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 ) 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 ); 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); //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 ) if( p->thruFl && am != nullptr )
_transmit_msg( p, am, false ); _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>(); 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 p->ioH = ioH; // p->ioH is used in _destory() so initialize it here
// parse the cfg // parse the cfg
@ -2161,23 +2159,22 @@ cw::rc_t cw::midi_record_play::vel_table_disable( handle_t h, unsigned devIdx )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
midi_record_play_t* p = _handleToPtr(h); 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); rc = cwLogError(kInvalidArgRC,"The device index '%i'is invalid.",devIdx);
goto errLabel; goto errLabel;
} }
p->midiDevA[devIdx].velTableArray = nullptr; for(unsigned i=begDevIdx; i<endDevIdx; ++i)
}
else
{ {
for(unsigned i=0; i<p->midiDevN; ++i) mem::release(p->midiDevA[i].velTableArray);
p->midiDevA[i].velTableArray = nullptr; p->midiDevA[i].velTableN = 0;
} }
errLabel: errLabel:
return rc; return rc;
} }

View File

@ -94,13 +94,13 @@ cw::rc_t cw::min_test( const object_t* cfg )
if((rc = create(app.ioH,cfg,minTestCb,&app)) != kOkRC ) if((rc = create(app.ioH,cfg,minTestCb,&app)) != kOkRC )
return rc; 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."); rc = cwLogError(rc,"Thread 0 create failed.");
goto errLabel; 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."); rc = cwLogError(rc,"Thread 1 create failed.");
goto errLabel; goto errLabel;

View File

@ -16,25 +16,25 @@
#include "cwScoreFollowerPerf.h" #include "cwScoreFollowerPerf.h"
#include "cwIoMidiRecordPlay.h" #include "cwIoMidiRecordPlay.h"
#include "cwIoPresetSelApp.h" #include "cwIoPresetSelApp.h"
#include "cwPianoScore.h"
#include "cwVectOps.h" #include "cwVectOps.h"
#include "cwMath.h" #include "cwMath.h"
#include "cwDspTypes.h" #include "cwDspTypes.h"
#include "cwMtx.h" #include "cwMtx.h"
#include "cwFlowDecl.h"
#include "cwFlow.h" #include "cwFlow.h"
#include "cwFlowTypes.h" #include "cwFlowTypes.h"
#include "cwFlowCross.h" #include "cwFlowCross.h"
#include "cwIoFlow.h" #include "cwIoFlow.h"
#include "cwPresetSel.h" #include "cwPresetSel.h"
#include "cwVelTableTuner.h" #include "cwVelTableTuner.h"
//#include "cwCmInterface.h"
#include "cwDynRefTbl.h" #include "cwDynRefTbl.h"
#include "cwScoreParse.h" #include "cwScoreParse.h"
#include "cwSfScore.h" #include "cwSfScore.h"
#include "cwSfTrack.h" #include "cwSfTrack.h"
#include "cwPerfMeas.h"
#include "cwPianoScore.h"
#include "cwScoreFollower.h" #include "cwScoreFollower.h"
#define INVALID_LOC (0)
namespace cw namespace cw
{ {
@ -49,6 +49,8 @@ namespace cw
kIoReportBtnId, kIoReportBtnId,
kNetPrintBtnId, kNetPrintBtnId,
kReportBtnId, kReportBtnId,
kLatencyBtnId,
kStartBtnId, kStartBtnId,
kStopBtnId, kStopBtnId,
@ -76,6 +78,11 @@ namespace cw
kPerfSelId, kPerfSelId,
kAltSelId, kAltSelId,
kPriPresetProbCheckId,
kSecPresetProbCheckId,
kPresetInterpCheckId,
kEnaRecordCheckId, kEnaRecordCheckId,
kMidiSaveBtnId, kMidiSaveBtnId,
kMidiLoadBtnId, kMidiLoadBtnId,
@ -148,6 +155,7 @@ namespace cw
{ kPanelDivId, kIoReportBtnId, "ioReportBtnId" }, { kPanelDivId, kIoReportBtnId, "ioReportBtnId" },
{ kPanelDivId, kNetPrintBtnId, "netPrintBtnId" }, { kPanelDivId, kNetPrintBtnId, "netPrintBtnId" },
{ kPanelDivId, kReportBtnId, "reportBtnId" }, { kPanelDivId, kReportBtnId, "reportBtnId" },
{ kPanelDivId, kLatencyBtnId, "latencyBtnId" },
{ kPanelDivId, kStartBtnId, "startBtnId" }, { kPanelDivId, kStartBtnId, "startBtnId" },
{ kPanelDivId, kStopBtnId, "stopBtnId" }, { kPanelDivId, kStopBtnId, "stopBtnId" },
@ -174,6 +182,9 @@ namespace cw
{ kPanelDivId, kLoadBtnId, "loadBtnId" }, { kPanelDivId, kLoadBtnId, "loadBtnId" },
{ kPanelDivId, kPerfSelId, "perfSelId" }, { kPanelDivId, kPerfSelId, "perfSelId" },
{ kPanelDivId, kAltSelId, "altSelId" }, { kPanelDivId, kAltSelId, "altSelId" },
{ kPanelDivId, kPriPresetProbCheckId, "presetProbPriCheckId" },
{ kPanelDivId, kSecPresetProbCheckId, "presetProbSecCheckId" },
{ kPanelDivId, kPresetInterpCheckId, "presetInterpCheckId" },
{ kPanelDivId, kEnaRecordCheckId, "enaRecordCheckId" }, { kPanelDivId, kEnaRecordCheckId, "enaRecordCheckId" },
{ kPanelDivId, kMidiSaveBtnId, "midiSaveBtnId" }, { kPanelDivId, kMidiSaveBtnId, "midiSaveBtnId" },
@ -294,14 +305,16 @@ namespace cw
score_follower::handle_t sfH; score_follower::handle_t sfH;
midi_record_play::handle_t mrpH; midi_record_play::handle_t mrpH;
perf_score::handle_t scoreH; perf_score::handle_t perfScoreH;
loc_map_t* locMap; loc_map_t* locMap;
unsigned locMapN; unsigned locMapN;
unsigned insertLoc; // last valid insert location id received from the GUI unsigned insertLoc; // last valid insert location id received from the GUI
unsigned minLoc; // min/max locations of the currently loaded performance unsigned minScoreLoc; // min/max locations of the currently loaded score
unsigned maxLoc; 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 beg_play_loc; // beg/end play loc's from the UI
unsigned end_play_loc; unsigned end_play_loc;
@ -318,6 +331,8 @@ namespace cw
bool printMidiFl; bool printMidiFl;
unsigned multiPresetFlags;
bool seqActiveFl; // true if the sequence is currently active (set by 'Play Seq' btn) bool seqActiveFl; // true if the sequence is currently active (set by 'Play Seq' btn)
unsigned seqStartedFl; // set by the first seq idle callback unsigned seqStartedFl; // set by the first seq idle callback
unsigned seqFragId; // unsigned seqFragId; //
@ -567,7 +582,7 @@ namespace cw
preset_sel::destroy(app.psH); preset_sel::destroy(app.psH);
io_flow::destroy(app.ioFlowH); io_flow::destroy(app.ioFlowH);
midi_record_play::destroy(app.mrpH); midi_record_play::destroy(app.mrpH);
perf_score::destroy( app.scoreH ); perf_score::destroy( app.perfScoreH );
mem::release(app.locMap); mem::release(app.locMap);
return kOkRC; return kOkRC;
} }
@ -873,39 +888,79 @@ namespace cw
cwLogInfo("No preset fragment was found for the requested timestamp."); cwLogInfo("No preset fragment was found for the requested timestamp.");
else 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 // if the preset sequence player is active then apply the next selected seq. preset
// otherwise select the next primary preset for ths fragment // otherwise select the next primary preset for ths fragment
unsigned seq_idx_n = app->seqActiveFl ? app->seqPresetIdx : kInvalidIdx; unsigned seq_idx_n = app->seqActiveFl ? app->seqPresetIdx : kInvalidIdx;
// get the preset index to play for this fragment if( score_evt != nullptr )
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 ); //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 else
{ {
const char* preset_label = preset_sel::preset_label(app->psH,preset_idx); // get the preset index to play for this fragment
if((preset_idx = fragment_play_preset_index(app->psH, frag, seq_idx_n)) == kInvalidIdx )
// don't display preset updates unless the score is actually loaded cwLogInfo("No preset has been assigned to the fragment at end loc. '%i'.",frag->endLoc );
printf("Apply preset: '%s' : loc:%i\n", preset_idx==kInvalidIdx ? "<invalid>" : preset_label, loc ); else
preset_label = preset_sel::preset_label(app->psH,preset_idx);
_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);
if( preset_label != nullptr ) 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 );
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_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, "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::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 ); io_flow::begin_cross_fade( app->ioFlowH, frag->fadeOutMs );
} }
} }
}
return kOkRC; return kOkRC;
} }
@ -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 _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 this is a MIDI note-on event - then udpate the score follower
if( midi::isNoteOn(status,d1) && muid != kInvalidIdx ) 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); snprintf(buf,buf_byte_cnt,"ch:%i status:0x%02x d0:%i d1:%i",ch,status,d0,d1);
} }
else 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); printf("%s\n",buf);
} }
@ -1052,7 +1107,7 @@ namespace cw
// TODO: ZERO SHOULD BE A VALID LOC VALUE - MAKE -1 THE INVALID LOC VALUE // 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 ) ) 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; rc_t rc = kOkRC;
if( msg.u.midi != nullptr ) 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; const midi::packet_t* pkt = m.pkt;
// for each midi msg // for each midi msg
for(unsigned j = 0; j<pkt->msgCnt; ++j) for(unsigned j = 0; j<pkt->msgCnt; ++j)
{ {
@ -1089,8 +1144,8 @@ namespace cw
} }
else // this is a triple else // this is a triple
{ {
midi::msg_t* mm = pkt->msgArray + j;
time::spec_t timestamp; time::spec_t timestamp;
midi::msg_t* mm = pkt->msgArray + j;
unsigned id = app->enableRecordFl ? last_store_index(app->mrpH) : kInvalidId; unsigned id = app->enableRecordFl ? last_store_index(app->mrpH) : kInvalidId;
unsigned loc = app->beg_play_loc; unsigned loc = app->beg_play_loc;
@ -1098,8 +1153,8 @@ namespace cw
if( midi::isChStatus(mm->status) ) 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 ) 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->status & 0x0f, mm->status & 0xf0, mm->d0, mm->d1 ); _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; 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); rc = cwLogError(kInvalidArgRC,"The score event associated with the 'in_audio_beg_loc' loc:%i could not be found.",loc);
goto errLabel; 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); rc = cwLogError(kInvalidArgRC,"The score event associated with the begin play loc:%i could not be found.",loc);
goto errLabel; goto errLabel;
@ -1176,7 +1231,7 @@ namespace cw
bool _is_performance_loaded( app_t* app ) 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 ) 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); evt_cnt = midi_record_play::event_count(app->mrpH);
io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), evt_cnt ); io::uiSendValue( app->ioH, uiFindElementUuId(app->ioH,kCurMidiEvtCntId), evt_cnt );
} }
errLabel: 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,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), 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. // Update the UI with the value from the the fragment data record.
@ -1585,7 +1637,10 @@ namespace cw
// _clear_status(app); // _clear_status(app);
//else //else
if( !enableFl ) if( !enableFl )
{
_set_status(app,"Invalid fragment play range. beg:%i end:%i",begPlayLoc,endPlayLoc); _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 // Set the fragment beg/end play range
get_value( app->psH, fragId, preset_sel::kBegPlayLocVarId, kInvalidId, fragBegLoc ); 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 // 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 ); _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; goto errLabel;
} }
get_loc_range(app->psH,app->minScoreLoc,app->maxScoreLoc);
// Settting psNextFrag to a non-null value causes the // Settting psNextFrag to a non-null value causes the
app->psNextFrag = preset_sel::get_fragment_base(app->psH); app->psNextFrag = preset_sel::get_fragment_base(app->psH);
@ -1872,6 +1929,8 @@ namespace cw
if( app->psNextFrag == nullptr ) if( app->psNextFrag == nullptr )
{ {
io::uiSendMsg( app->ioH, "{ \"op\":\"attach\" }" );
// the fragments are loaded enable the 'load' and 'alt' menu // 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, kPerfSelId ), true );
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kAltSelId ), true ); io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kAltSelId ), true );
@ -1890,6 +1949,7 @@ namespace cw
return rc; return rc;
} }
/*
rc_t _restore_fragment_data( app_t* app ) rc_t _restore_fragment_data( app_t* app )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
@ -1920,6 +1980,8 @@ namespace cw
goto errLabel; goto errLabel;
} }
get_loc_range(app->psH,app->minScoreLoc,app->maxScoreLoc);
//preset_sel::report( app->psH ); //preset_sel::report( app->psH );
f = preset_sel::get_fragment_base(app->psH); f = preset_sel::get_fragment_base(app->psH);
@ -1944,7 +2006,7 @@ namespace cw
return rc; return rc;
} }
*/
rc_t _on_ui_save( app_t* app ) rc_t _on_ui_save( app_t* app )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
@ -1994,19 +2056,19 @@ namespace cw
midiEventCntRef = 0; midiEventCntRef = 0;
// get the count of MIDI events // 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) for(; e != nullptr; e=e->link)
if( e->status != 0 ) if( e->status != 0 )
midiEventN += 1; midiEventN += 1;
// copy the MIDI events // 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[] // allocate the locMap[]
app->locMap = mem::resizeZ<loc_map_t>( app->locMap, midiEventN ); app->locMap = mem::resizeZ<loc_map_t>( app->locMap, midiEventN );
app->locMapN = midiEventN; app->locMapN = midiEventN;
app->minLoc = INVALID_LOC; app->minPerfLoc = score_parse::kInvalidLocId;
app->maxLoc = INVALID_LOC; app->maxPerfLoc = score_parse::kInvalidLocId;
// allocate the the player msg array // allocate the the player msg array
m = mem::allocZ<midi_record_play::midi_msg_t>( midiEventN ); 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].loc = e->loc;
app->locMap[i].timestamp = m[i].timestamp; app->locMap[i].timestamp = m[i].timestamp;
if( e->loc != INVALID_LOC ) if( e->loc != score_parse::kInvalidLocId )
{ {
if( app->minLoc == INVALID_LOC ) if( app->minPerfLoc == score_parse::kInvalidLocId )
app->minLoc = e->loc; app->minPerfLoc = e->loc;
else else
app->minLoc = std::min(app->minLoc,e->loc); app->minPerfLoc = std::min(app->minPerfLoc,e->loc);
if( app->maxLoc == INVALID_LOC ) if( app->maxPerfLoc == score_parse::kInvalidLocId )
app->maxLoc = e->loc; app->maxPerfLoc = e->loc;
else else
app->maxLoc = std::max(app->maxLoc,e->loc); app->maxPerfLoc = std::max(app->maxPerfLoc,e->loc);
} }
++i; ++i;
@ -2054,7 +2116,7 @@ namespace cw
midiEventCntRef = midiEventN; 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: errLabel:
@ -2114,7 +2176,7 @@ namespace cw
return rc; 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; rc_t rc = kOkRC;
unsigned midiEventN = 0; unsigned midiEventN = 0;
@ -2122,8 +2184,8 @@ namespace cw
cwLogInfo("Loading"); cwLogInfo("Loading");
_set_status(app,"Loading..."); _set_status(app,"Loading...");
// load the performance or score // load the performance
if((rc= perf_score::create( app->scoreH, perf_fn )) != kOkRC ) if((rc= perf_score::create( app->perfScoreH, perf_fn )) != kOkRC )
{ {
cwLogError(rc,"Score create failed on '%s'.",perf_fn); cwLogError(rc,"Score create failed on '%s'.",perf_fn);
goto errLabel; goto errLabel;
@ -2153,12 +2215,12 @@ namespace cw
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kInsertLocId ), true ); io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kInsertLocId ), true );
// set the UI begin/end play to the locations of the newly loaded performance // set the UI begin/end play to the locations of the newly loaded performance
app->end_play_loc = app->maxLoc; app->end_play_loc = app->maxPerfLoc;
app->beg_play_loc = app->minLoc; app->beg_play_loc = app->minPerfLoc;
// Update the master range of the play beg/end number widgets // 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, kBegPlayLocNumbId), app->minPerfLoc, app->maxPerfLoc, 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, 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, kBegPlayLocNumbId ), true );
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kEndPlayLocNumbId ), 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, kSfResetBtnId ), true );
io::uiSetEnable( app->ioH, io::uiFindElementUuId( app->ioH, kSfResetLocNumbId ), 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::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->minLoc); io::uiSendValue( app->ioH, io::uiFindElementUuId(app->ioH, kSfResetLocNumbId), app->minPerfLoc);
cwLogInfo("'%s' loaded.",perf_fn); cwLogInfo("'%s' loaded.",perf_fn);
@ -2216,10 +2278,10 @@ namespace cw
goto errLabel; 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 // 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."); rc = cwLogError(kSyntaxErrorRC,"The performance load failed.");
goto errLabel; goto errLabel;
@ -2604,6 +2666,7 @@ namespace cw
return rc; return rc;
} }
/*
rc_t _on_midi_load_btn_0(app_t* app) rc_t _on_midi_load_btn_0(app_t* app)
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
@ -2612,13 +2675,13 @@ namespace cw
object_t* cfg = nullptr; object_t* cfg = nullptr;
unsigned sfResetLoc; 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)); rc = cwLogError(rc,"Piano score performance load failed on '%s'.",cwStringNullGuard(app->midiLoadFname));
goto errLabel; 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)); rc = cwLogError(rc,"Performance load failed on '%s'.",cwStringNullGuard(app->midiLoadFname));
goto errLabel; goto errLabel;
@ -2659,12 +2722,13 @@ namespace cw
return rc; return rc;
} }
*/
rc_t _on_midi_load_btn(app_t* app) rc_t _on_midi_load_btn(app_t* app)
{ {
rc_t rc = kOkRC; 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)); rc = cwLogError(rc,"Piano score performance load failed on '%s'.",cwStringNullGuard(app->midiLoadFname));
goto errLabel; goto errLabel;
@ -2675,7 +2739,6 @@ namespace cw
return rc; return rc;
} }
rc_t _on_midi_load_fname(app_t* app, const char* fname) rc_t _on_midi_load_fname(app_t* app, const char* fname)
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
@ -2725,9 +2788,11 @@ namespace cw
if((rc = preset_sel::set_value( app->psH, kInvalidId, varId, kInvalidId, value )) != kOkRC ) 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)); rc = cwLogError(rc,"Master value set failed on varId:%i %s.%s.",varId,cwStringNullGuard(inst_label),cwStringNullGuard(var_label));
else else
{
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 ) 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)); rc = cwLogError(rc,"Master value send failed on %s.%s.",cwStringNullGuard(inst_label),cwStringNullGuard(var_label));
}
return rc; return rc;
} }
@ -2762,12 +2827,14 @@ namespace cw
case kPvWndSmpCntId: case kPvWndSmpCntId:
var_label = "wndSmpN"; var_label = "wndSmpN";
app->pvWndSmpCnt = m.value->u.u; app->pvWndSmpCnt = 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 ); rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, "pva", var_label, flow::kAnyChIdx, m.value->u.u );
break; break;
case kSdBypassId: case kSdBypassId:
var_label = "bypass"; var_label = "bypass";
app->sdBypassFl = m.value->u.b; app->sdBypassFl = 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 ); rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, "sd", var_label, flow::kAnyChIdx, m.value->u.b );
break; break;
@ -2809,6 +2876,7 @@ namespace cw
case kCmpBypassId: case kCmpBypassId:
var_label = "cmp-bypass"; var_label = "cmp-bypass";
app->cmpBypassFl = m.value->u.b; app->cmpBypassFl = 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 ); rc = io_flow::set_variable_value( app->ioFlowH, flow_cross::kAllDestId, "cmp", "bypass", flow::kAnyChIdx, m.value->u.b );
break; break;
@ -2816,7 +2884,7 @@ namespace cw
assert(0); 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 ); 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 ) if(rc != kOkRC )
@ -2825,7 +2893,7 @@ namespace cw
return rc; 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; rc_t rc = kOkRC;
dsp::real_t value; dsp::real_t value;
@ -2847,6 +2915,7 @@ namespace cw
io::uiSendValue( app->ioH, io::uiFindElementUuId( app->ioH, kSyncDelayMsId ), app->dfltSyncDelayMs ); io::uiSendValue( app->ioH, io::uiFindElementUuId( app->ioH, kSyncDelayMsId ), app->dfltSyncDelayMs );
} }
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 ) 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."); rc = cwLogError(rc,"Error setting sync delay 'flow' value.");
@ -2887,7 +2956,7 @@ namespace cw
if((rc = _fragment_load_data(app)) != kOkRC ) if((rc = _fragment_load_data(app)) != kOkRC )
rc = cwLogError(rc,"Preset data restore failed."); rc = cwLogError(rc,"Preset data restore failed.");
_on_live_midi_fl(app,app->useLiveMidiFl); _on_live_midi_checkbox(app,app->useLiveMidiFl);
return rc; return rc;
} }
@ -2907,6 +2976,7 @@ namespace cw
break; break;
case kNetPrintBtnId: case kNetPrintBtnId:
if( app->ioFlowH.isValid() )
io_flow::print_network(app->ioFlowH,flow_cross::kCurDestId); io_flow::print_network(app->ioFlowH,flow_cross::kCurDestId);
break; break;
@ -2926,6 +2996,11 @@ namespace cw
preset_sel::report_presets(app->psH); preset_sel::report_presets(app->psH);
break; break;
case kLatencyBtnId:
latency_measure_report(app->ioH);
latency_measure_setup(app->ioH);
break;
case kSaveBtnId: case kSaveBtnId:
_on_ui_save(app); _on_ui_save(app);
break; break;
@ -2938,6 +3013,18 @@ namespace cw
_on_alt_select(app,m.value->u.u); _on_alt_select(app,m.value->u.u);
break; 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: case kMidiThruCheckId:
cwLogInfo("MIDI thru:%i",m.value->u.b); cwLogInfo("MIDI thru:%i",m.value->u.b);
_set_midi_thru_state(app, m.value->u.b); _set_midi_thru_state(app, m.value->u.b);
@ -2952,8 +3039,7 @@ namespace cw
break; break;
case kLiveCheckId: case kLiveCheckId:
//app->useLiveMidiFl = m.value->u.b; _on_live_midi_checkbox(app, m.value->u.b );
_on_live_midi_fl(app, m.value->u.b );
break; break;
case kTrackMidiCheckId: case kTrackMidiCheckId:
@ -3175,6 +3261,18 @@ namespace cw
_on_echo_midi_enable( app, m.uuId, midi_record_play::kSampler_MRP_DevIdx ); _on_echo_midi_enable( app, m.uuId, midi_record_play::kSampler_MRP_DevIdx );
break; 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: case kWetInGainId:
_on_echo_master_value( app, preset_sel::kMasterWetInGainVarId, m.uuId ); _on_echo_master_value( app, preset_sel::kMasterWetInGainVarId, m.uuId );
break; break;
@ -3364,7 +3462,7 @@ namespace cw
case io::kMidiTId: case io::kMidiTId:
if( app->useLiveMidiFl && app->mrpH.isValid() ) if( app->useLiveMidiFl && app->mrpH.isValid() )
_on_live_midi( app, *m ); _on_live_midi_event( app, *m );
break; break;
case io::kAudioTId: 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 // create the IO Flow controller
if( !audioIsEnabled(app.ioH) )
{
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 ) 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."); rc = cwLogError(rc,"The IO Flow controller create failed.");
goto errLabel; goto errLabel;
} }
app.multiPresetFlags = preset_cfg_flags(app.ioFlowH);
}
printf("ioFlow is %s valid.\n",app.ioFlowH.isValid() ? "" : "not");
// start the IO framework instance // start the IO framework instance
if((rc = io::start(app.ioH)) != kOkRC ) 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; goto errLabel;
} }
//io::uiReport(app.ioH);
// execute the io framework // execute the io framework
while( !io::isShuttingDown(app.ioH)) while( !io::isShuttingDown(app.ioH))
{ {
time::spec_t t0;
time::get(t0);
// This call may block on the websocket handle.
io::exec(app.ioH); 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 // stop the io framework

View File

@ -1317,7 +1317,7 @@ cw::rc_t cw::net::mdns::test()
} }
// create the TCP listening thread // 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; goto errLabel;

View File

@ -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 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 ) if( p0 != nullptr )
{ {
// get a pointer to the base of the exsting block // get a pointer to the base of the exsting block

View File

@ -88,26 +88,32 @@ namespace cw
template< typename T> bool isStatus( T s ) { return (kNoteOffMdId <= removeCh(s) /*&& ((unsigned)(s)) <= kSysRtResetMdId*/ ); } 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 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 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 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 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 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 isPedal( T s, T d0 ) { return isCtlStatus(s) && kSustainCtlMdId <= (d0) && (d0) <= kLegatoCtlMdId; }
template< typename T> bool isSustainPedalDown( T s, T d0, T d1) { return ( isSustainPedal(s,d0) && (d1)>=64 ); } template< typename T> bool isPedalDown( T d1 ) { return ( (d1)>=64 ); }
template< typename T> bool isSustainPedalUp( T s, T d0, T d1) { return ( isSustainPedal(s,d0) && (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 isSustainPedal( T s, T d0 ) { return isCtlStatus(s) && (d0)==kSustainCtlMdId; }
template< typename T> bool isSostenutoPedalDown( T s, T d0, T d1) { return ( isSostenutoPedal(s,d0) && (d1)>=64 ); } template< typename T> bool isSustainPedalDown( T s, T d0, T d1) { return ( isSustainPedal(s,d0) && isPedalDown(d1) ); }
template< typename T> bool isSostenutoPedalUp( T s, T d0, T d1) { return ( isSostenutoPedal(s,d0) && (d1)<64 ); } 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; typedef uint8_t byte_t;

View File

@ -3,10 +3,17 @@
#include "cwCommonImpl.h" #include "cwCommonImpl.h"
#include "cwMem.h" #include "cwMem.h"
#include "cwTime.h" #include "cwTime.h"
#include "cwMidi.h" #include "cwText.h"
#include "cwObject.h"
#include "cwTextBuf.h" #include "cwTextBuf.h"
#include "cwMidiPort.h" #include "cwMidi.h"
#include "cwThread.h" #include "cwMidiDecls.h"
#include "cwMidiDevice.h"
#include "cwMidiParser.h"
#include <poll.h>
#include "cwMidiAlsa.h"
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
@ -16,6 +23,10 @@ namespace cw
{ {
namespace device namespace device
{ {
namespace alsa
{
const unsigned kCtoD_MapN = 256;
typedef struct typedef struct
{ {
bool inputFl; // true if this an input port bool inputFl; // true if this an input port
@ -38,7 +49,7 @@ namespace cw
} dev_t; } dev_t;
typedef struct device_str typedef struct alsa_device_str
{ {
unsigned devCnt; // MIDI devices attached to this computer unsigned devCnt; // MIDI devices attached to this computer
dev_t* devArray; dev_t* devArray;
@ -47,7 +58,6 @@ namespace cw
snd_seq_t* h; // ALSA system sequencer handle snd_seq_t* h; // ALSA system sequencer handle
snd_seq_addr_t alsa_addr; // ALSA client/port address representing the application snd_seq_addr_t alsa_addr; // ALSA client/port address representing the application
int alsa_queue; // ALSA device queue int alsa_queue; // ALSA device queue
thread::handle_t thH; // MIDI input listening thread
int alsa_fdCnt; // MIDI input driver file descriptor array int alsa_fdCnt; // MIDI input driver file descriptor array
struct pollfd* alsa_fd; struct pollfd* alsa_fd;
dev_t* prvRcvDev; // the last device and port to rcv MIDI dev_t* prvRcvDev; // the last device and port to rcv MIDI
@ -55,26 +65,20 @@ namespace cw
unsigned prvTimeMicroSecs; // time of last recognized event in microseconds unsigned prvTimeMicroSecs; // time of last recognized event in microseconds
unsigned eventCnt; // count of recognized events unsigned eventCnt; // count of recognized events
time::spec_t baseTimeStamp; time::spec_t baseTimeStamp;
} device_t;
device_t* _handleToPtr( handle_t h ){ return handleToPtr<handle_t,device_t>(h); } unsigned clientIdToDevIdxMap[ kCtoD_MapN ];
rc_t _cmMpErrMsgV(rc_t rc, int alsaRc, const char* fmt, va_list vl ) bool latency_meas_enable_in_fl;
{ bool latency_meas_enable_out_fl;
if( alsaRc < 0 ) latency_meas_result_t latency_meas_result;
cwLogError(kOpFailRC,"ALSA Error:%i %s",alsaRc,snd_strerror(alsaRc));
return cwLogVError(rc,fmt,vl); } alsa_device_t;
}
#define _cmMpErrMsg( rc, alsaRc, str ) cwLogError(kOpFailRC,"%s : ALSA Error:%i %s",(str),(alsaRc),snd_strerror(alsaRc))
#define _cmMpErrMsg1( rc, alsaRc, fmt, arg ) cwLogError(kOpFailRC, fmt"%s : ALSA Error:%i %s",(arg),(alsaRc),snd_strerror(alsaRc))
alsa_device_t* _handleToPtr( handle_t h ){ return handleToPtr<handle_t,alsa_device_t>(h); }
rc_t _cmMpErrMsg(rc_t rc, int alsaRc, const char* fmt, ... )
{
va_list vl;
va_start(vl,fmt);
rc = _cmMpErrMsgV(rc,alsaRc,fmt,vl);
va_end(vl);
return rc;
}
unsigned _cmMpGetPortCnt( snd_seq_t* h, snd_seq_port_info_t* pip, bool inputFl ) unsigned _cmMpGetPortCnt( snd_seq_t* h, snd_seq_port_info_t* pip, bool inputFl )
{ {
@ -89,12 +93,12 @@ namespace cw
return i; return i;
} }
dev_t* _cmMpClientIdToDev( device_t* p, int clientId ) dev_t* _cmMpClientIdToDev( alsa_device_t* p, unsigned char clientId )
{ {
unsigned i; unsigned devIdx = p->clientIdToDevIdxMap[ clientId ];
for(i=0; i<p->devCnt; ++i)
if( p->devArray[i].clientId == clientId ) if( devIdx != kInvalidIdx )
return p->devArray + i; return p->devArray + devIdx;
return NULL; return NULL;
} }
@ -117,35 +121,31 @@ namespace cw
*d1 = v & 0x7f; *d1 = v & 0x7f;
} }
rc_t _cmMpPoll(device_t* p) rc_t _handle_input_msg( alsa_device_t* p )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
int timeOutMs = 50;
snd_seq_event_t *ev; snd_seq_event_t *ev;
if (poll(p->alsa_fd, p->alsa_fdCnt, timeOutMs) > 0)
{
int rc = 1;
do do
{ {
rc = snd_seq_event_input(p->h,&ev); int alsa_rc = snd_seq_event_input(p->h,&ev);
// if no input // if no input
if( rc == -EAGAIN ) if( alsa_rc == -EAGAIN )
{ {
// TODO: report or at least count error // TODO: report or at least count error
break; break;
} }
// if input buffer overrun // if input buffer overrun
if( rc == -ENOSPC ) if( alsa_rc == -ENOSPC )
{ {
// TODO: report or at least count error // TODO: report or at least count error
break; break;
} }
// get the device this event arrived from // get the device this event arrived from
if( p->prvRcvDev==NULL || p->prvRcvDev->clientId != ev->source.client ) if( p->prvRcvDev==NULL || p->prvRcvDev->clientId != ev->source.client )
p->prvRcvDev = _cmMpClientIdToDev(p,ev->source.client); p->prvRcvDev = _cmMpClientIdToDev(p,ev->source.client);
@ -176,6 +176,15 @@ namespace cw
status = kNoteOnMdId; status = kNoteOnMdId;
d0 = ev->data.note.note; d0 = ev->data.note.note;
d1 = ev->data.note.velocity; d1 = ev->data.note.velocity;
if( p->latency_meas_enable_in_fl )
{
p->latency_meas_enable_in_fl = false;
time::get(p->latency_meas_result.note_on_input_ts);
//p->latency_meas_result.note_on_input_ts.tv_sec = ev->time.time.tv_sec;
//p->latency_meas_result.note_on_input_ts.tv_nsec = ev->time.time.tv_nsec;
}
//printf("%s (%i : %i) (%i)\n", snd_seq_ev_is_abstime(ev)?"abs":"rel",ev->time.time.tv_sec,ev->time.time.tv_nsec, deltaMicroSecs/1000); //printf("%s (%i : %i) (%i)\n", snd_seq_ev_is_abstime(ev)?"abs":"rel",ev->time.time.tv_sec,ev->time.time.tv_nsec, deltaMicroSecs/1000);
break; break;
@ -256,6 +265,7 @@ namespace cw
if( status != 0 ) if( status != 0 )
{ {
uint8_t ch = ev->data.note.channel; uint8_t ch = ev->data.note.channel;
/*
time::spec_t ts; time::spec_t ts;
ts.tv_sec = p->baseTimeStamp.tv_sec + ev->time.time.tv_sec; ts.tv_sec = p->baseTimeStamp.tv_sec + ev->time.time.tv_sec;
ts.tv_nsec = p->baseTimeStamp.tv_nsec + ev->time.time.tv_nsec; ts.tv_nsec = p->baseTimeStamp.tv_nsec + ev->time.time.tv_nsec;
@ -267,6 +277,11 @@ namespace cw
//printf("MIDI: %ld %ld : 0x%x %i %i\n",ts.tv_sec,ts.tv_nsec,status,d0,d1); //printf("MIDI: %ld %ld : 0x%x %i %i\n",ts.tv_sec,ts.tv_nsec,status,d0,d1);
parser::midiTriple(p->prvRcvPort->parserH, &ts, status | ch, d0, d1 );
*/
time::spec_t ts;
ts.tv_sec = ev->time.time.tv_sec;
ts.tv_nsec = ev->time.time.tv_nsec;
parser::midiTriple(p->prvRcvPort->parserH, &ts, status | ch, d0, d1 ); parser::midiTriple(p->prvRcvPort->parserH, &ts, status | ch, d0, d1 );
p->prvTimeMicroSecs = microSecs1; p->prvTimeMicroSecs = microSecs1;
@ -276,20 +291,11 @@ namespace cw
}while( snd_seq_event_input_pending(p->h,0)); }while( snd_seq_event_input_pending(p->h,0));
parser::transmit(p->prvRcvPort->parserH); parser::transmit(p->prvRcvPort->parserH);
}
return rc; return rc;
} }
rc_t _cmMpAllocStruct( alsa_device_t* p, const char* appNameStr, cbFunc_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt )
bool _threadCbFunc(void* arg)
{
device_t* p = static_cast<device_t*>(arg);
_cmMpPoll(p);
return true;
}
rc_t _cmMpAllocStruct( device_t* p, const char* appNameStr, cbFunc_t cbFunc, void* cbDataPtr, unsigned parserBufByteCnt )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
snd_seq_client_info_t* cip = NULL; snd_seq_client_info_t* cip = NULL;
@ -297,6 +303,9 @@ namespace cw
snd_seq_port_subscribe_t *subs = NULL; snd_seq_port_subscribe_t *subs = NULL;
unsigned i,j,k,arc; unsigned i,j,k,arc;
for(i=0; i<kCtoD_MapN; ++i)
p->clientIdToDevIdxMap[i] = kInvalidIdx;
// alloc the subscription recd on the stack // alloc the subscription recd on the stack
snd_seq_port_subscribe_alloca(&subs); snd_seq_port_subscribe_alloca(&subs);
@ -379,6 +388,8 @@ namespace cw
p->devArray[i].oPortArray = NULL; p->devArray[i].oPortArray = NULL;
p->devArray[i].clientId = client; p->devArray[i].clientId = client;
assert( (unsigned)client < kCtoD_MapN );
p->clientIdToDevIdxMap[ client ] = i;
snd_seq_port_info_set_client(pip,client); snd_seq_port_info_set_client(pip,client);
snd_seq_port_info_set_port(pip,-1); snd_seq_port_info_set_port(pip,-1);
@ -432,7 +443,7 @@ namespace cw
snd_seq_port_subscribe_set_time_update(subs, 1); snd_seq_port_subscribe_set_time_update(subs, 1);
snd_seq_port_subscribe_set_time_real(subs, 1); snd_seq_port_subscribe_set_time_real(subs, 1);
if((arc = snd_seq_subscribe_port(p->h, subs)) < 0) if((arc = snd_seq_subscribe_port(p->h, subs)) < 0)
rc = _cmMpErrMsg(kOpFailRC,arc,"Input port to app. subscription failed on port '%s'.",cwStringNullGuard(port)); rc = _cmMpErrMsg1(kOpFailRC,arc,"Input port to app. subscription failed on port '%s'.",cwStringNullGuard(port));
++j; ++j;
} }
@ -450,7 +461,7 @@ namespace cw
snd_seq_port_subscribe_set_sender(subs, &p->alsa_addr); snd_seq_port_subscribe_set_sender(subs, &p->alsa_addr);
snd_seq_port_subscribe_set_dest( subs, &addr); snd_seq_port_subscribe_set_dest( subs, &addr);
if((arc = snd_seq_subscribe_port(p->h, subs)) < 0 ) if((arc = snd_seq_subscribe_port(p->h, subs)) < 0 )
rc = _cmMpErrMsg(kOpFailRC,arc,"App to output port subscription failed on port '%s'.",cwStringNullGuard(port)); rc = _cmMpErrMsg1(kOpFailRC,arc,"App to output port subscription failed on port '%s'.",cwStringNullGuard(port));
++k; ++k;
} }
@ -501,7 +512,7 @@ namespace cw
} }
rc_t _destroy( device_t* p ) rc_t _destroy( alsa_device_t* p )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
@ -509,12 +520,6 @@ namespace cw
{ {
int arc; int arc;
// stop the thread first
if((rc = thread::destroy(p->thH)) != kOkRC )
{
rc = _cmMpErrMsg(rc,0,"Thread destroy failed.");
goto errLabel;
}
// stop the queue // stop the queue
if( p->h != NULL ) if( p->h != NULL )
@ -572,14 +577,13 @@ namespace cw
errLabel: errLabel:
return rc; return rc;
} }
} // alsa
} // device } // device
} // midi } // midi
} // cw } // cw
cw::rc_t cw::midi::device::create( handle_t& h, cbFunc_t cbFunc, void* cbArg, unsigned parserBufByteCnt, const char* appNameStr ) cw::rc_t cw::midi::device::alsa::create( handle_t& h, cbFunc_t cbFunc, void* cbArg, unsigned parserBufByteCnt, const char* appNameStr )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
int arc = 0; int arc = 0;
@ -587,18 +591,11 @@ cw::rc_t cw::midi::device::create( handle_t& h, cbFunc_t cbFunc, void* cbArg, u
if((rc = destroy(h)) != kOkRC ) if((rc = destroy(h)) != kOkRC )
return rc; return rc;
device_t* p = mem::allocZ<device_t>(1); alsa_device_t* p = mem::allocZ<alsa_device_t>(1);
p->h = NULL; p->h = NULL;
p->alsa_queue = -1; p->alsa_queue = -1;
// create the listening thread
if((rc = thread::create( p->thH, _threadCbFunc, p)) != kOkRC )
{
rc = _cmMpErrMsg(rc,0,"Thread initialization failed.");
goto errLabel;
}
// initialize the ALSA sequencer // initialize the ALSA sequencer
if((arc = snd_seq_open(&p->h, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK )) < 0 ) if((arc = snd_seq_open(&p->h, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK )) < 0 )
{ {
@ -634,9 +631,6 @@ cw::rc_t cw::midi::device::create( handle_t& h, cbFunc_t cbFunc, void* cbArg, u
// all time stamps will be an offset from this time stamp // all time stamps will be an offset from this time stamp
clock_gettime(CLOCK_MONOTONIC,&p->baseTimeStamp); clock_gettime(CLOCK_MONOTONIC,&p->baseTimeStamp);
if((rc = thread::unpause(p->thH)) != kOkRC )
rc = _cmMpErrMsg(rc,0,"Thread start failed.");
h.set(p); h.set(p);
errLabel: errLabel:
@ -649,14 +643,14 @@ cw::rc_t cw::midi::device::create( handle_t& h, cbFunc_t cbFunc, void* cbArg, u
} }
cw::rc_t cw::midi::device::destroy( handle_t& h ) cw::rc_t cw::midi::device::alsa::destroy( handle_t& h )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
if( !h.isValid() ) if( !h.isValid() )
return rc; return rc;
device_t* p = _handleToPtr(h); alsa_device_t* p = _handleToPtr(h);
if((rc = _destroy(p)) != kOkRC ) if((rc = _destroy(p)) != kOkRC )
return rc; return rc;
@ -666,18 +660,32 @@ cw::rc_t cw::midi::device::destroy( handle_t& h )
return rc; return rc;
} }
bool cw::midi::device::isInitialized(handle_t h) bool cw::midi::device::alsa::isInitialized(handle_t h)
{ return h.isValid(); } { return h.isValid(); }
unsigned cw::midi::device::count(handle_t h) unsigned cw::midi::device::alsa::count(handle_t h)
{ {
device_t* p = _handleToPtr(h); alsa_device_t* p = _handleToPtr(h);
return p->devCnt; return p->devCnt;
} }
const char* cw::midi::device::name( handle_t h, unsigned devIdx ) struct pollfd* cw::midi::device::alsa::pollFdArray( handle_t h, unsigned& arrayCntRef )
{ {
device_t* p = _handleToPtr(h); alsa_device_t* p = _handleToPtr(h);
arrayCntRef = p->alsa_fdCnt;
return p->alsa_fd;
}
cw::rc_t cw::midi::device::alsa::handleInputMsg( handle_t h )
{
alsa_device_t* p = _handleToPtr(h);
return _handle_input_msg(p);
}
const char* cw::midi::device::alsa::name( handle_t h, unsigned devIdx )
{
alsa_device_t* p = _handleToPtr(h);
if( p==NULL || devIdx>=p->devCnt) if( p==NULL || devIdx>=p->devCnt)
return NULL; return NULL;
@ -685,9 +693,20 @@ const char* cw::midi::device::name( handle_t h, unsigned devIdx )
return p->devArray[devIdx].nameStr; return p->devArray[devIdx].nameStr;
} }
unsigned cw::midi::device::portCount( handle_t h, unsigned devIdx, unsigned flags ) unsigned cw::midi::device::alsa::nameToIndex(handle_t h, const char* deviceName)
{ {
device_t* p = _handleToPtr(h); alsa_device_t* p = _handleToPtr(h);
for(unsigned i=0; i<p->devCnt; ++i)
if( textIsEqual(p->devArray[i].nameStr,deviceName) )
return i;
return kInvalidIdx;
}
unsigned cw::midi::device::alsa::portCount( handle_t h, unsigned devIdx, unsigned flags )
{
alsa_device_t* p = _handleToPtr(h);
if( p==NULL || devIdx>=p->devCnt) if( p==NULL || devIdx>=p->devCnt)
return 0; return 0;
@ -698,9 +717,9 @@ unsigned cw::midi::device::portCount( handle_t h, unsigned devIdx, unsigned
return p->devArray[devIdx].oPortCnt; return p->devArray[devIdx].oPortCnt;
} }
const char* cw::midi::device::portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx ) const char* cw::midi::device::alsa::portName( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx )
{ {
device_t* p = _handleToPtr(h); alsa_device_t* p = _handleToPtr(h);
if( p==NULL || devIdx>=p->devCnt) if( p==NULL || devIdx>=p->devCnt)
return 0; return 0;
@ -719,13 +738,42 @@ const char* cw::midi::device::portName( handle_t h, unsigned devIdx, unsign
return p->devArray[devIdx].oPortArray[portIdx].nameStr; return p->devArray[devIdx].oPortArray[portIdx].nameStr;
} }
unsigned cw::midi::device::alsa::portNameToIndex( handle_t h, unsigned devIdx, unsigned flags, const char* portName )
{
alsa_device_t* p = _handleToPtr(h);
cw::rc_t cw::midi::device::send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t status, uint8_t d0, uint8_t d1 ) if( p==nullptr || devIdx>=p->devCnt )
return kInvalidIdx;
if( cwIsFlag(flags,kInMpFl) )
{
for(unsigned i=0; i<p->devArray[devIdx].iPortCnt; ++i)
if( textIsEqual(p->devArray[devIdx].iPortArray[i].nameStr,portName) )
return i;
}
else
{
for(unsigned i=0; i<p->devArray[devIdx].oPortCnt; ++i)
if( textIsEqual(p->devArray[devIdx].oPortArray[i].nameStr,portName) )
return i;
}
return kInvalidIdx;
}
cw::rc_t cw::midi::device::alsa::portEnable( handle_t h, unsigned devIdx, unsigned flags, unsigned portIdx, bool enableFl )
{
return cwLogError(kNotImplementedRC,"Port enable/disable has not been implemneted on ALSA MIDI ports.");
}
cw::rc_t cw::midi::device::alsa::send( handle_t h, unsigned devIdx, unsigned portIdx, uint8_t status, uint8_t d0, uint8_t d1 )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
snd_seq_event_t ev; snd_seq_event_t ev;
int arc; int arc;
device_t* p = _handleToPtr(h); alsa_device_t* p = _handleToPtr(h);
assert( p!=NULL && devIdx < p->devCnt && portIdx < p->devArray[devIdx].oPortCnt ); assert( p!=NULL && devIdx < p->devCnt && portIdx < p->devArray[devIdx].oPortCnt );
@ -752,6 +800,12 @@ cw::rc_t cw::midi::device::send( handle_t h, unsigned devIdx, unsigned portIdx,
ev.type = SND_SEQ_EVENT_NOTEON; ev.type = SND_SEQ_EVENT_NOTEON;
ev.data.note.note = d0; ev.data.note.note = d0;
ev.data.note.velocity = d1; ev.data.note.velocity = d1;
if( p->latency_meas_enable_out_fl )
{
p->latency_meas_enable_out_fl = false;
time::get(p->latency_meas_result.note_on_output_ts);
}
break; break;
case kPolyPresMdId: case kPolyPresMdId:
@ -792,7 +846,7 @@ cw::rc_t cw::midi::device::send( handle_t h, unsigned devIdx, unsigned portIdx,
break; break;
default: default:
rc = _cmMpErrMsg(kInvalidArgRC,0,"Cannot send an invalid MIDI status byte:0x%x.",status & 0xf0); rc = _cmMpErrMsg1(kInvalidArgRC,0,"Cannot send an invalid MIDI status byte:0x%x.",status & 0xf0);
goto errLabel; goto errLabel;
} }
@ -808,95 +862,34 @@ cw::rc_t cw::midi::device::send( handle_t h, unsigned devIdx, unsigned portIdx,
return rc; return rc;
} }
cw::rc_t cw::midi::device::sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt ) cw::rc_t cw::midi::device::alsa::sendData( handle_t h, unsigned devIdx, unsigned portIdx, const uint8_t* dataPtr, unsigned byteCnt )
{ {
return cwLogError(kInvalidOpRC,"cmMpDeviceSendData() has not yet been implemented for ALSA."); return cwLogError(kInvalidOpRC,"cmMpDeviceSendData() has not yet been implemented for ALSA.");
} }
cw::rc_t cw::midi::device::installCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ) void cw::midi::device::alsa::latency_measure_reset(handle_t h)
{ {
rc_t rc = kOkRC; alsa_device_t* p = _handleToPtr(h);
unsigned di;
unsigned dn = count(h);
device_t* p = _handleToPtr(h);
for(di=0; di<dn; ++di) p->latency_meas_result.note_on_input_ts = {};
if( di==devIdx || devIdx == kInvalidIdx ) p->latency_meas_result.note_on_output_ts = {};
{ p->latency_meas_enable_in_fl = true;
unsigned pi; p->latency_meas_enable_out_fl = true;
unsigned pn = portCount(h,di,kInMpFl);
for(pi=0; pi<pn; ++pi)
if( pi==portIdx || portIdx == kInvalidIdx )
if( parser::installCallback( p->devArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) != kOkRC )
goto errLabel;
}
errLabel:
return rc;
} }
cw::rc_t cw::midi::device::removeCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr ) cw::midi::device::latency_meas_result_t cw::midi::device::alsa::latency_measure_result(handle_t h)
{ {
rc_t rc = kOkRC; alsa_device_t* p = _handleToPtr(h);
unsigned di; return p->latency_meas_result;
unsigned dn = count(h);
unsigned remCnt = 0;
device_t* p = _handleToPtr(h);
for(di=0; di<dn; ++di)
if( di==devIdx || devIdx == kInvalidIdx )
{
unsigned pi;
unsigned pn = portCount(h,di,kInMpFl);
for(pi=0; pi<pn; ++pi)
if( pi==portIdx || portIdx == kInvalidIdx )
if( parser::hasCallback(p->devArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) )
{
if( parser::removeCallback( p->devArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) != kOkRC )
goto errLabel;
else
++remCnt;
}
}
if( remCnt == 0 && dn > 0 )
rc = _cmMpErrMsg(kInvalidArgRC,0,"The callback was not found on any of the specified devices or ports.");
errLabel:
return rc;
}
bool cw::midi::device::usesCallback( handle_t h, unsigned devIdx, unsigned portIdx, cbFunc_t cbFunc, void* cbDataPtr )
{
unsigned di;
unsigned dn = count(h);
device_t* p = _handleToPtr(h);
for(di=0; di<dn; ++di)
if( di==devIdx || devIdx == kInvalidIdx )
{
unsigned pi;
unsigned pn = portCount(h,di,kInMpFl);
for(pi=0; pi<pn; ++pi)
if( pi==portIdx || portIdx == kInvalidIdx )
if( parser::hasCallback( p->devArray[di].iPortArray[pi].parserH, cbFunc, cbDataPtr ) )
return true;
}
return false;
} }
void cw::midi::device::alsa::report( handle_t h, textBuf::handle_t tbH )
void cw::midi::device::report( handle_t h, textBuf::handle_t tbH )
{ {
device_t* p = _handleToPtr(h); alsa_device_t* p = _handleToPtr(h);
unsigned i,j; unsigned i,j;
textBuf::print( tbH,"Buffer size bytes in:%i out:%i\n",snd_seq_get_input_buffer_size(p->h),snd_seq_get_output_buffer_size(p->h)); textBuf::print( tbH,"ALSA Buffer size bytes in:%i out:%i\n",snd_seq_get_input_buffer_size(p->h),snd_seq_get_output_buffer_size(p->h));
for(i=0; i<p->devCnt; ++i) for(i=0; i<p->devCnt; ++i)
{ {

44
cwMidiAlsa.h Normal file
View 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);
}
}
}
}

View File

@ -8,21 +8,25 @@ namespace cw
typedef struct msg_str typedef struct msg_str
{ {
time::spec_t timeStamp; time::spec_t timeStamp;
uint8_t status; // midi status byte 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 d0; // midi data byte 0
uint8_t d1; // midi data byte 1 uint8_t d1; // midi data byte 1
uint8_t pad;
} msg_t; } msg_t;
typedef struct packet_str 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 devIdx; // The device the msg originated from
unsigned portIdx; // The port index on the source device 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 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) 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 unsigned msgCnt; // Count of mdMsg records or sys-ex bytes
} packet_t; } packet_t;
typedef void (*cbFunc_t)( const packet_t* pktArray, unsigned pktCnt );
} }
} }
#endif #endif

666
cwMidiDevice.cpp Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,10 @@
namespace cw
{
namespace midi
{
namespace device
{
rc_t test( const object_t* cfg );
}
}
}

View File

@ -152,7 +152,7 @@ namespace cw
const trackMsg_t* trackMsg( handle_t h, unsigned trackIdx ); const trackMsg_t* trackMsg( handle_t h, unsigned trackIdx );
// Returns the total count of records in the midi file and the // 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. // Return kInvalidCnt if 'h' is invalid.
unsigned msgCount( handle_t h ); unsigned msgCount( handle_t h );
@ -221,7 +221,7 @@ namespace cw
rc_t genCsvFile( const char* midiFn, const char* csvFn, bool printWarningsFl=true ); 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( 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() ); 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
View 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
View 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 );
}
}
}
}

View File

@ -4,13 +4,8 @@
#include "cwMem.h" #include "cwMem.h"
#include "cwTime.h" #include "cwTime.h"
#include "cwMidi.h" #include "cwMidi.h"
#include "cwTextBuf.h" #include "cwMidiDecls.h"
#include "cwMidiParser.h"
#include "cwMidiPort.h"
//===================================================================================================
//
//
namespace cw namespace cw
{ {
@ -63,7 +58,7 @@ namespace cw
cbRecd_t* c = p->cbChain; cbRecd_t* c = p->cbChain;
for(; c!=NULL; c=c->linkPtr) for(; c!=NULL; c=c->linkPtr)
{ {
pkt->cbDataPtr = c->cbDataPtr; pkt->cbArg = c->cbDataPtr;
c->cbFunc( pkt, pktCnt ); c->cbFunc( pkt, pktCnt );
} }
} }
@ -89,7 +84,6 @@ namespace cw
p->pkt.msgArray = NULL; p->pkt.msgArray = NULL;
p->pkt.sysExMsg = p->buf; p->pkt.sysExMsg = p->buf;
p->pkt.msgCnt = p->bufIdx; p->pkt.msgCnt = p->bufIdx;
//p->cbFunc( &p->pkt, 1 );
_cmMpParserCb(p,&p->pkt,1); _cmMpParserCb(p,&p->pkt,1);
p->bufIdx = 0; p->bufIdx = 0;
@ -110,7 +104,9 @@ namespace cw
// fill the buffer msg // fill the buffer msg
msgPtr->timeStamp = *timeStamp; msgPtr->timeStamp = *timeStamp;
msgPtr->status = p->status; msgPtr->status = p->status & 0xf0;
msgPtr->ch = p->status & 0x0f;
msgPtr->uid = kInvalidId;
switch( p->dataCnt ) 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.devIdx = devIdx;
p->pkt.portIdx = portIdx; 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->cbChain = NULL;
p->buf = mem::allocZ<uint8_t>( bufByteCnt ); p->buf = mem::allocZ<uint8_t>( bufByteCnt );
p->bufByteCnt = bufByteCnt; p->bufByteCnt = bufByteCnt;
@ -406,6 +398,7 @@ cw::rc_t cw::midi::parser::transmit( handle_t h )
return kOkRC; return kOkRC;
} }
cw::rc_t cw::midi::parser::installCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr ) cw::rc_t cw::midi::parser::installCallback( handle_t h, cbFunc_t cbFunc, void* cbDataPtr )
{ {
parser_t* p = _handleToPtr(h); parser_t* p = _handleToPtr(h);
@ -474,95 +467,3 @@ bool cw::midi::parser::hasCallback( handle_t h, cbFunc_t cbFunc, void* cbArg )
return false; 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
View 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 );
}
}
}

View File

@ -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

View File

@ -36,6 +36,15 @@ namespace cw
return rc; 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; rc_t rc = kOkRC;
mutex_t* p = _handleToPtr(h); mutex_t* p = _handleToPtr(h);
int sysRc; 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) ) 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 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 = pthread_cond_timedwait(&p->cvar,&p->mutex,&ts)) != 0 )
{ {
if( sysRC == ETIMEDOUT ) if( sysRC == ETIMEDOUT )

View File

@ -365,7 +365,7 @@ cw::rc_t cw::nbmem::test_multi_threaded()
ctx.threadA[i].varA = mem::allocZ<void*>(threadVarN); ctx.threadA[i].varA = mem::allocZ<void*>(threadVarN);
ctx.threadA[i].varN = 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; break;
} }

View File

@ -211,7 +211,7 @@ namespace cw
if( cwIsNotFlag(flags, kOptionalFl) ) if( cwIsNotFlag(flags, kOptionalFl) )
return cwLogError(kInvalidIdRC,"The pair label '%s' could not be found.",cwStringNullGuard(label)); return cwLogError(kInvalidIdRC,"The pair label '%s' could not be found.",cwStringNullGuard(label));
return kLabelNotFoundRC; return kEleNotFoundRC;
} }
return o->value(v); return o->value(v);
@ -226,7 +226,7 @@ namespace cw
rc_t rc = get(label,valRef,flags); rc_t rc = get(label,valRef,flags);
// if no error occurred .... // 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 rc = _getv(flags, std::forward<ARGS>(args)...); // ... recurse to find next label/value pair
else else
rc = cwLogError(rc,"Object parse failed for the pair label:'%s'.",cwStringNullGuard(label)); rc = cwLogError(rc,"Object parse failed for the pair label:'%s'.",cwStringNullGuard(label));

View File

@ -4,45 +4,28 @@
#include "cwMem.h" #include "cwMem.h"
#include "cwText.h" #include "cwText.h"
#include "cwObject.h" #include "cwObject.h"
#include "cwPianoScore.h"
#include "cwMidi.h" #include "cwMidi.h"
#include "cwTime.h" #include "cwTime.h"
#include "cwFile.h" #include "cwFile.h"
#include "cwCsv.h" #include "cwCsv.h"
#include "cwVectOps.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) #define INVALID_PERF_MEAS (-1)
namespace cw namespace cw
{ {
namespace perf_score 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 typedef struct score_str
{ {
event_t* base; event_t* base;
@ -57,24 +40,6 @@ namespace cw
} score_t; } 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) score_t* _handleToPtr(handle_t h)
{ {
return handleToPtr<handle_t,score_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 ) 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>(); event_t* e = mem::allocZ<event_t>();
const char* sci_pitch; const char* sci_pitch;
unsigned sci_pitch_char_cnt; unsigned sci_pitch_char_cnt;
int has_stats_fl = 0;
if((rc = getv(csvH, if((rc = getv(csvH,
"meas",e->meas, "meas",e->meas,
@ -133,12 +160,21 @@ namespace cw
"d0", e->d0, "d0", e->d0,
"d1", e->d1, "d1", e->d1,
"bar", e->bar, "bar", e->bar,
"section", e->section )) != kOkRC ) "section", e->section)) != kOkRC )
{ {
rc = cwLogError(rc,"Error parsing CSV."); rc = cwLogError(rc,"Error parsing CSV.");
goto errLabel; 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( score_fl )
{ {
if((rc = getv(csvH,"oloc",e->loc )) != kOkRC ) if((rc = getv(csvH,"oloc",e->loc )) != kOkRC )
@ -147,6 +183,10 @@ namespace cw
goto errLabel; goto errLabel;
} }
if( e->section > 0 && has_stats_fl )
if((rc = _read_meas_stats(p,csvH,e)) != kOkRC )
goto errLabel;
} }
else else
{ {
@ -154,11 +194,16 @@ namespace cw
"even", e->even, "even", e->even,
"dyn", e->dyn, "dyn", e->dyn,
"tempo", e->tempo, "tempo", e->tempo,
"cost", e->cost )) != kOkRC ) "cost", e->cost)) != kOkRC )
{ {
rc = cwLogError(rc,"Error parsing CSV."); rc = cwLogError(rc,"Error parsing CSV.");
goto errLabel; 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 ) 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; csv::handle_t csvH;
rc_t rc = kOkRC; rc_t rc = kOkRC;
bool score_fl = false; 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 ) if((rc = csv::create(csvH,csvFname)) != kOkRC )
{ {
@ -220,21 +255,12 @@ namespace cw
goto errLabel; 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 ) if( title_col_index(csvH,"oloc") != kInvalidIdx )
score_fl = true; 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 ) for(unsigned i=0; (rc = next_line(csvH)) == kOkRC; ++i )
if((rc = _read_csv_line(p,score_fl,csvH)) != kOkRC ) 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 _parse_event_list( score_t* p, const object_t* cfg )
{ {
rc_t rc; rc_t rc;
@ -574,16 +351,6 @@ namespace cw
textCopy( e->sci_pitch,sizeof(e->sci_pitch),sci_pitch); textCopy( e->sci_pitch,sizeof(e->sci_pitch),sci_pitch);
textCopy( e->dmark,sizeof(e->dmark),dmark); textCopy( e->dmark,sizeof(e->dmark),dmark);
textCopy( e->grace_mark, sizeof(e->grace_mark),grace_mark); 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 // assign the UID
e->uid = i; 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( _does_file_have_loc_info(fn) )
{ {
// if((rc = _parse_csv(p,fn)) != kOkRC )
if((rc = _read_csv( p, fn )) != kOkRC ) if((rc = _read_csv( p, fn )) != kOkRC )
goto errLabel; goto errLabel;
@ -715,6 +481,8 @@ cw::rc_t cw::perf_score::create( handle_t& hRef, const char* fn )
p->has_locs_fl = false; p->has_locs_fl = false;
} }
_setup_feat_vectors(p);
hRef.set(p); hRef.set(p);
errLabel: errLabel:
@ -874,6 +642,8 @@ cw::rc_t cw::perf_score::test( const object_t* cfg )
goto errLabel; goto errLabel;
} }
cwLogInfo("Creating score from '%s'.",cwStringNullGuard(fname));
if((rc = create( h, fname )) != kOkRC ) if((rc = create( h, fname )) != kOkRC )
{ {
rc = cwLogError(rc,"Score create failed."); rc = cwLogError(rc,"Score create failed.");

View File

@ -7,6 +7,15 @@ namespace cw
{ {
typedef handle<struct score_str> handle_t; 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 typedef struct event_str
{ {
unsigned uid; // unique id for this event unsigned uid; // unique id for this event
@ -28,11 +37,18 @@ namespace cw
unsigned barPitchIdx; // bar pitch index or 0 unsigned barPitchIdx; // bar pitch index or 0
unsigned section; // section number 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 even;
double dyn; double dyn;
double tempo; double tempo;
double cost; double cost;
double featV[ perf_meas::kValCnt ];
double featMinV[ perf_meas::kValCnt ];
double featMaxV[ perf_meas::kValCnt ];
struct event_str* link; // list link struct event_str* link; // list link
} event_t; } event_t;

View File

@ -6,13 +6,18 @@
#include "cwObject.h" #include "cwObject.h"
#include "cwTime.h" #include "cwTime.h"
#include "cwVectOps.h" #include "cwVectOps.h"
#include "cwFlowDecl.h"
#include "cwPresetSel.h" #include "cwPresetSel.h"
#include "cwFile.h" #include "cwFile.h"
#include "cwPianoScore.h"
#include "cwMidi.h" #include "cwMidi.h"
#include "cwDynRefTbl.h" #include "cwDynRefTbl.h"
#include "cwScoreParse.h" #include "cwScoreParse.h"
#include "cwSfScore.h" #include "cwSfScore.h"
#include "cwPerfMeas.h"
#include "cwPianoScore.h"
namespace cw 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->altLabelN = alt_labelL->child_count() + 1;
p->altLabelA = mem::allocZ<alt_label_t>(p->altLabelN); 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); 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 ) unsigned cw::preset_sel::fragment_count( handle_t h )
{ {
preset_sel_t* p = _handleToPtr(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 // set the return value
fragIdRef = f->fragId; fragIdRef = f->fragId;
// intiialize the preset array elements // intiialize the preset array elements
for(unsigned i=0; i<p->presetLabelN; ++i) 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 // release the fragment
mem::release(f->presetA); mem::release(f->presetA);
mem::release(f->multiPresetA);
mem::release(f); mem::release(f);
return kOkRC; 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("fragL", fragL_obj, root);
newPairObject("fragN", fragN, root);
newPairObject("masterWetInGain", p->master_wet_in_gain, root ); newPairObject("masterWetInGain", p->master_wet_in_gain, root );
newPairObject("masterWetOutGain", p->master_wet_out_gain, root ); newPairObject("masterWetOutGain", p->master_wet_out_gain, root );
newPairObject("masterDryGain", p->master_dry_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; rc_t rc = kOkRC;
preset_sel_t* p = _handleToPtr(h); preset_sel_t* p = _handleToPtr(h);
object_t* root = nullptr; object_t* root = nullptr;
unsigned fragN = 0;
const object_t* fragL_obj = nullptr; const object_t* fragL_obj = nullptr;
// parse the preset file // 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); _destroy_all_frags(p);
// parse the root level // parse the root level
if((rc = root->getv( "fragN", fragN, if((rc = root->getv( "fragL", fragL_obj,
"fragL", fragL_obj,
"masterWetInGain", p->master_wet_in_gain, "masterWetInGain", p->master_wet_in_gain,
"masterWetOutGain", p->master_wet_out_gain, "masterWetOutGain", p->master_wet_out_gain,
"masterDryGain", p->master_dry_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; goto errLabel;
} }
// for each fragment // for each fragment
for(unsigned i=0; i<fragN; ++i) for(unsigned i=0; i<fragL_obj->child_count(); ++i)
{ {
frag_t* f = nullptr; frag_t* f = nullptr;
const object_t* r = fragL_obj->child_ele(i); 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; double igain=0,ogain=0,wetDryGain=0,fadeOutMs=0;
const char* note = nullptr; const char* note = nullptr;
const object_t* presetL_obj = 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; goto errLabel;
} }
if( order > 0 || playFl )
multiPresetN += 1;
f->presetA[ preset_idx ].order = order; f->presetA[ preset_idx ].order = order;
f->presetA[ preset_idx ].alt_str = mem::duplStr(alt_str); f->presetA[ preset_idx ].alt_str = mem::duplStr(alt_str);
f->presetA[ preset_idx ].playFl = playFl; 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; 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 ) cw::rc_t cw::preset_sel::translate_frags( const object_t* cfg )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
@ -1918,4 +1976,4 @@ cw::rc_t cw::preset_sel::translate_frags( const object_t* cfg )
return rc; return rc;
} }
#endif

View File

@ -13,8 +13,8 @@ namespace cw
bool playFl; // play this preset bool playFl; // play this preset
bool seqFl; // play this preset during sequencing. bool seqFl; // play this preset during sequencing.
unsigned preset_idx; // preset index into preset_labelA[]. unsigned preset_idx; // preset index into preset_labelA[].
unsigned order; // unsigned order; // selection label
char* alt_str; char* alt_str; // 'alt' label
} preset_t; } preset_t;
typedef struct frag_str typedef struct frag_str
@ -35,6 +35,9 @@ namespace cw
preset_t* presetA; // presetA[ presetN ] - status of each preset preset_t* presetA; // presetA[ presetN ] - status of each preset
unsigned presetN; 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. unsigned* altPresetIdxA; // altPresetIdxA[ alt_count() ] selected preset idx for each alt.
bool uiSelectFl; bool uiSelectFl;
@ -81,6 +84,8 @@ namespace cw
unsigned alt_count( handle_t h ); unsigned alt_count( handle_t h );
const char* alt_label( handle_t h, unsigned alt_idx ); 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 ); unsigned fragment_count( handle_t h );
const frag_t* get_fragment_base( handle_t h ); const frag_t* get_fragment_base( handle_t h );
const frag_t* get_fragment( handle_t h, unsigned fragId ); const frag_t* get_fragment( handle_t h, unsigned fragId );

View File

@ -290,7 +290,7 @@ namespace cw
rc_t _gen_synced_perf_files( test_t* p, rc_t _gen_synced_perf_files( test_t* p,
sfscore::handle_t scoreH, sfscore::handle_t scoreH,
score_follower::handle_t sfH, score_follower::handle_t sfH,
perf_meas::handle_t perfMeasH ) perf_meas::handle_t perfMeasH)
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
const object_t* jobL = nullptr; const object_t* jobL = nullptr;
@ -345,6 +345,9 @@ namespace cw
unsigned end_loc = kInvalidId; unsigned end_loc = kInvalidId;
bool skip_score_follow_fl = false; bool skip_score_follow_fl = false;
cwLogInfo("\nProcessing:%s",cwStringNullGuard(dirEntryArray[i].name));
// read the meta object // read the meta object
if((rc = objectFromFile( meta_fname, meta_obj)) != kOkRC ) 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)); 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->srate,
p->print_rt_events_fl, p->print_rt_events_fl,
false, 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)); rc = cwLogError(rc,"The score follower failed on '%s'. Consider setting the 'skip_score_follow_fl' in '%s'.",cwStringNullGuard(midi_fname),cwStringNullGuard(meta_fname));
} }

View File

@ -121,6 +121,21 @@ namespace cw
return rc; 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) rc_t _destroy( score_follower_t* p)
{ {
destroy(p->dynRefH); destroy(p->dynRefH);
@ -580,6 +595,10 @@ cw::rc_t cw::score_follower::write_sync_perf_csv( handle_t h, const char* out_fn
rc_t rc = kOkRC; rc_t rc = kOkRC;
unsigned resultN = result_count(p->trackH); unsigned resultN = result_count(p->trackH);
auto resultA = result_base(p->trackH); auto resultA = result_base(p->trackH);
bool dampPedalDownFl = false;
bool sostPedalDownFl = false;
bool softPedalDownFl = false;
unsigned curBarNumb = 1;
file::handle_t fH; file::handle_t fH;
if( msgN == 0 ) 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 // 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) 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 d0 = m->u.chMsgPtr->d0;
uint8_t d1 = m->u.chMsgPtr->d1; uint8_t d1 = m->u.chMsgPtr->d1;
if( midi::isNoteOn(m->status,d1) ) if( midi::isNoteOn(m->status,d1) )
{ {
const unsigned INVALID_LOC = 0;
char sciPitch[ midi::kMidiSciPitchCharCnt + 1 ];
unsigned bar = 0; unsigned bar = 0;
midi::midiToSciPitch( d0, sciPitch, midi::kMidiSciPitchCharCnt ); const char* sectionLabel = "";
unsigned loc = INVALID_LOC; unsigned loc = score_parse::kInvalidLocId;
unsigned dlevel = -1;
char sciPitch[ midi::kMidiSciPitchCharCnt + 1 ];
midi::midiToSciPitch( d0, sciPitch, midi::kMidiSciPitchCharCnt );
// locate score matching record for this performed note
for(unsigned i=0; i<resultN; ++i) for(unsigned i=0; i<resultN; ++i)
{ {
const sfscore::event_t* e;
// FIX THIS: // FIX THIS:
// THE perfA[] INDEX IS STORED IN resultA[i].muid // THE perfA[] INDEX IS STORED IN resultA[i].muid
// this isn't right.
assert( resultA[i].muid != kInvalidIdx && resultA[i].muid < p->perfN ); 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 ); 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; 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 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);
} }
} }

View File

@ -163,6 +163,38 @@ namespace cw
return s; 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 _parse_section_row( score_parse_t* p, csv::handle_t csvH, event_t* e )
{ {
rc_t rc; rc_t rc;
@ -180,6 +212,9 @@ namespace cw
goto errLabel; goto errLabel;
} }
//if((rc = _parse_section_stats(p,csvH,e)) != kOkRC )
// goto errLabel;
e->section->csvRowNumb = e->csvRowNumb; e->section->csvRowNumb = e->csvRowNumb;
errLabel: errLabel:

View File

@ -2,6 +2,11 @@ namespace cw
{ {
namespace score_parse namespace score_parse
{ {
enum {
kInvalidLocId = 0
};
enum { enum {
kInvalidTId, kInvalidTId,
kBarTId, kBarTId,
@ -43,8 +48,26 @@ namespace cw
kVarCnt kVarCnt
}; };
typedef enum {
kDynStatIdx,
kEvenStatIdx,
kTempoStatIdx,
kCostStatIdx,
kStatCnt
} stats_idx_t;
struct set_str; struct set_str;
struct event_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 typedef struct section_str
{ {
char* label; // This sections label char* label; // This sections label
@ -55,6 +78,9 @@ namespace cw
struct event_str* endEvent; // last event in this section struct event_str* endEvent; // last event in this section
struct event_str* begSetEvent; // first set event in this section struct event_str* begSetEvent; // first set event in this section
struct event_str* endSetEvent; // last 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 struct section_str* link; // p->sectionL links
} section_t; } section_t;

View File

@ -92,7 +92,7 @@ cw::rc_t cw::serialPortSrv::create( handle_t& h, unsigned pollPeriodMs, unsigned
if((rc = serialPort::create( p->mgrH, recvBufByteN)) != kOkRC ) if((rc = serialPort::create( p->mgrH, recvBufByteN)) != kOkRC )
goto errLabel; goto errLabel;
if((rc = thread::create( p->threadH, threadCallback, p)) != kOkRC ) if((rc = thread::create( p->threadH, threadCallback, p, "serial_srv")) != kOkRC )
goto errLabel; goto errLabel;
p->pollPeriodMs = pollPeriodMs; p->pollPeriodMs = pollPeriodMs;

View File

@ -243,6 +243,20 @@ namespace cw
return rc; 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 _create_section_array( sfscore_t* p )
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
@ -785,6 +799,9 @@ namespace cw
if((rc = _create_section_array( p )) != kOkRC ) if((rc = _create_section_array( p )) != kOkRC )
goto errLabel; goto errLabel;
if((rc = _assign_section_to_events(p)) != kOkRC )
goto errLabel;
if((rc = _create_set_array( p )) != kOkRC ) if((rc = _create_set_array( p )) != kOkRC )
goto errLabel; goto errLabel;

View File

@ -9,6 +9,8 @@ namespace cw
struct loc_str; struct loc_str;
struct set_str; struct set_str;
typedef score_parse::stats_t stats_t;
// The score can be divided into arbitrary non-overlapping sections. // The score can be divided into arbitrary non-overlapping sections.
typedef struct section_str typedef struct section_str
{ {
@ -20,8 +22,7 @@ namespace cw
unsigned endEvtIndex; // last element in this section unsigned endEvtIndex; // last element in this section
unsigned setCnt; // Count of elements in setArray[] unsigned setCnt; // Count of elements in setArray[]
struct set_str** setArray; // Ptrs to sets which are applied to this section. struct set_str** setArray; // Ptrs to sets which are applied to this section.
//stats_t statsA[ score_parse::kStatCnt ];
//double vars[ score_parse::kVarCnt ]; // Set to DBL_MAX by default.
} section_t; } section_t;
typedef struct var_str typedef struct var_str
@ -43,6 +44,7 @@ namespace cw
unsigned flags; // Attribute flags for this event unsigned flags; // Attribute flags for this event
unsigned dynLevel; // Dynamcis value pppp to ffff (1 to 11) for this note. 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. 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 barNumb; // Bar id of the measure containing this event.
unsigned barNoteIdx; // Index of this note in this bar unsigned barNoteIdx; // Index of this note in this bar
unsigned csvRowNumb; // File row number (not index) from which this record originated unsigned csvRowNumb; // File row number (not index) from which this record originated

View File

@ -23,24 +23,24 @@ namespace cw
callback_func_t cbFunc; callback_func_t cbFunc;
void* cbArg; void* cbArg;
sfmatch::handle_t matchH; sfmatch::handle_t matchH;
unsigned mn; // size of midiBuf[] unsigned mn; // length of midiBuf[]
sfmatch::midi_t* midiBuf; // midiBuf[mn] 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 rn; // length of res[] (set to 2*score event count)
unsigned ri; // next avail res[] recd. unsigned ri; // next avail res[] recd.
double s_opt; // double s_opt; //
unsigned missCnt; // current count of consecutive trailing non-matches unsigned missCnt; // current count of consecutive trailing non-matches
unsigned ili; // index into loc[] to start scan following reset unsigned ili; // index into sfmatch_t.loc[] to start scan following reset
unsigned eli; // index into loc[] of the last positive match. unsigned eli; // index into sfmatch_t.loc[] of the last positive match.
unsigned mni; // current count of MIDI events since the last call to cmScMatcherReset() 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 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 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 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 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 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 scanCnt; // current count of times a resync-scan was executed during _step()
unsigned flags; unsigned flags;
} sftrack_t; } sftrack_t;

View File

@ -50,10 +50,12 @@ namespace cw
// Notes: // Notes:
// The cwSfTrack maintains an internal cwSfMatch object which is used to attempt to find the // 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. // 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 // 'scWndN' is used to set the cwSfMatch 'locN' argument. It defines the length of the
// each recceived MIDI note. // score window over which the MIDI event window will slide, while searching for the best match.
// 'midiWndN' must be <= 'scWndN'. //
// '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 ); rc_t destroy( handle_t& hRef );

View File

@ -1211,7 +1211,7 @@ cw::rc_t cw::socksrv::createMgrSrv( handle_t& hRef, unsigned timeOutMs, unsigne
goto errLabel; goto errLabel;
// create the thread // create the thread
if((rc = thread::create( p->thH, _threadFunc, p)) != kOkRC ) if((rc = thread::create( p->thH, _threadFunc, p, "sock")) != kOkRC )
goto errLabel; goto errLabel;
p->timeOutMs = timeOutMs; p->timeOutMs = timeOutMs;

View File

@ -7,6 +7,12 @@
#include "cwTime.h" #include "cwTime.h"
#include "cwMidi.h" #include "cwMidi.h"
#include "cwMidiFile.h" #include "cwMidiFile.h"
#include "cwDynRefTbl.h"
#include "cwScoreParse.h"
#include "cwSfScore.h"
#include "cwPerfMeas.h"
#include "cwPianoScore.h" #include "cwPianoScore.h"
#include "cwSvg.h" #include "cwSvg.h"
#include "cwMidiState.h" #include "cwMidiState.h"

View File

@ -7,13 +7,14 @@
#include "cwTime.h" #include "cwTime.h"
#include "cwMidi.h" #include "cwMidi.h"
#include "cwMidiFile.h" #include "cwMidiFile.h"
#include "cwPianoScore.h"
#include "cwSvg.h" #include "cwSvg.h"
#include "cwDynRefTbl.h" #include "cwDynRefTbl.h"
#include "cwScoreParse.h" #include "cwScoreParse.h"
#include "cwSfScore.h" #include "cwSfScore.h"
#include "cwSfMatch.h" #include "cwSfMatch.h"
#include "cwSfTrack.h" #include "cwSfTrack.h"
#include "cwPerfMeas.h"
#include "cwPianoScore.h"
#include "cwScoreFollowerPerf.h" #include "cwScoreFollowerPerf.h"
#include "cwScoreFollower.h" #include "cwScoreFollower.h"

View File

@ -110,7 +110,7 @@ cw::rc_t cw::net::srv::create(
if((rc = socket::create( p->sockH, port, flags, timeOutMs, remoteAddr, remotePort, localAddr )) != kOkRC ) if((rc = socket::create( p->sockH, port, flags, timeOutMs, remoteAddr, remotePort, localAddr )) != kOkRC )
goto errLabel; 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; goto errLabel;
p->flags = srvFlags; p->flags = srvFlags;

View File

@ -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 ) if((rc = create(app.sockH,localPort, kBlockingFl,timeOutMs, NULL, kInvalidPortNumber )) != kOkRC )
return rc; 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; goto errLabel;
if((rc = thread::unpause( app.threadH )) != kOkRC ) 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; return rc;
// create the listening thread (which is really only used by the server) // 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; goto errLabel;
// if this is a streaming client then connect to the server (which must have already been started) // if this is a streaming client then connect to the server (which must have already been started)

View File

@ -31,13 +31,14 @@ namespace cw
unsigned pauseMicros; unsigned pauseMicros;
unsigned sleepMicros; unsigned sleepMicros;
pthread_attr_t attr; pthread_attr_t attr;
char* label;
} thread_t; } thread_t;
inline thread_t* _handleToPtr(handle_t h) { return handleToPtr<handle_t,thread_t>(h); } 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. // 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; unsigned waitTimeMicroSecs = 0;
stateId_t curStateId; 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; rc_t rc;
int sysRC; 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->pauseMicros = pauseMicros;
p->stateId = kPausedThId; p->stateId = kPausedThId;
p->sleepMicros = 15000; p->sleepMicros = 15000;
p->label = mem::duplStr(label);
if((sysRC = pthread_attr_init(&p->attr)) != 0) 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."); rc = cwLogSysError(kOpFailRC,sysRC,"Thread attribute init failed.");
} }
else else
{
/* /*
// Creating the thread in a detached state should prevent it from leaking memory when // 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; p->stateId = kNotInitThId;
rc = cwLogSysError(kOpFailRC,sysRC,"Thread create failed."); rc = cwLogSysError(kOpFailRC,sysRC,"Thread create failed.");
} }
}
if( label != nullptr )
pthread_setname_np(p->pThreadH, label);
hRef.set(p); hRef.set(p);
cwLogInfo("Thread %s id:%p created.",cwStringNullGuard(label), p->pThreadH);
return rc; 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 // wait for the thread to exit and then deallocate the thread object
if((rc = _waitForState(p,kExitedThId)) != kOkRC ) 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 // Block until the thread is actually fully cleaned up
if((sysRC = pthread_join(p->pThreadH,NULL)) != 0) 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 ) //if( pthread_attr_destroy(&p->attr) != 0 )
// rc = cwLogError(kOpFailRC,"Thread attribute destroy failed."); // rc = cwLogError(kOpFailRC,"Thread attribute destroy failed.");
mem::release(p->label);
mem::release(p); mem::release(p);
hRef.clear(); hRef.clear();
@ -205,7 +217,7 @@ cw::rc_t cw::thread::pause( handle_t h, unsigned cmdFlags )
thread_t* p = _handleToPtr(h); thread_t* p = _handleToPtr(h);
stateId_t curStateId = p->stateId.load(std::memory_order_acquire); stateId_t curStateId = p->stateId.load(std::memory_order_acquire);
bool isPausedFl = curStateId == kPausedThId; bool isPausedFl = curStateId == kPausedThId;
unsigned waitId; stateId_t waitId;
if( isPausedFl == pauseFl ) if( isPausedFl == pauseFl )
return kOkRC; return kOkRC;
@ -225,7 +237,7 @@ cw::rc_t cw::thread::pause( handle_t h, unsigned cmdFlags )
rc = _waitForState(p,waitId); rc = _waitForState(p,waitId);
if( rc != kOkRC ) 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; return rc;
@ -256,6 +268,26 @@ cw::thread::thread_id_t cw::thread::id()
return id.u.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 namespace cw
{ {
bool _threadTestCb( void* p ) bool _threadTestCb( void* p )
@ -273,7 +305,7 @@ cw::rc_t cw::threadTest()
rc_t rc; rc_t rc;
char c = 0; char c = 0;
if((rc = thread::create(h,_threadTestCb,&val)) != kOkRC ) if((rc = thread::create(h,_threadTestCb,&val,"thread_test")) != kOkRC )
return rc; return rc;
if((rc = thread::pause(h,0)) != kOkRC ) if((rc = thread::pause(h,0)) != kOkRC )

View File

@ -5,6 +5,8 @@ namespace cw
{ {
namespace thread namespace thread
{ {
const int kDefaultStateTimeOutMicros=100000;
const int kDefaultPauseMicros = 10000;
typedef enum typedef enum
{ {
kNotInitThId, kNotInitThId,
@ -15,13 +17,21 @@ namespace cw
typedef handle<struct thread_str> handle_t; typedef handle<struct thread_str> handle_t;
// Return false to indicate that the thread should terminate.
typedef bool (*cbFunc_t)( void* arg ); typedef bool (*cbFunc_t)( void* arg );
typedef unsigned long long thread_id_t; 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. // 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. // 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 ); rc_t destroy( handle_t& hRef );
@ -33,6 +43,10 @@ namespace cw
// Return the thread id of the calling context. // Return the thread id of the calling context.
thread_id_t id(); thread_id_t id();
const char* label( handle_t h );
unsigned stateTimeOutMicros( handle_t h);
unsigned pauseMicros( handle_t h );
} }
rc_t threadTest(); rc_t threadTest();
} }

View File

@ -25,13 +25,13 @@ namespace cw
thread_mach_t* _handleToPtr( handle_t h ) thread_mach_t* _handleToPtr( handle_t h )
{ return handleToPtr<handle_t,thread_mach_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; rc_t rc = kOkRC;
thread_t* t = mem::allocZ<thread_t>(); 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."); rc = cwLogError(rc,"Thread create failed.");
goto errLabel; 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); void* arg = ctxA + (i*contexRecdByteN);
if((rc = _add(p, threadFunc, arg)) != kOkRC ) if((rc = _add(p, threadFunc, arg, nullptr)) != kOkRC )
goto errLabel; goto errLabel;
} }
@ -103,10 +103,10 @@ cw::rc_t cw::thread_mach::create( handle_t& hRef, threadFunc_t threadFunc, void*
return rc; 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); 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 ) cw::rc_t cw::thread_mach::destroy( handle_t& hRef )

View File

@ -16,7 +16,7 @@ namespace cw
// Create an additional thread. Note that the additional thread will be started by the next // Create an additional thread. Note that the additional thread will be started by the next
// call to 'start()'. // 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 // Start all threads
rc_t start( handle_t h ); rc_t start( handle_t h );

View File

@ -44,11 +44,16 @@ void cw::time::get( spec_t& t )
#include <sys/time.h> // gettimeofday() #include <sys/time.h> // gettimeofday()
void cw::time::get( spec_t& t ) void cw::time::get( spec_t& t )
{ {
// NOTcw::mutex::lock(h,timeout) relies on using clock_gettime(CLOCK_MONOTONIC,&t);
// CLOCK_REALTIME. If the source of this clock changes
// then change cw::mutex::loc(h,timeout) as well
clock_gettime(CLOCK_REALTIME,&t);
} }
cw::time::spec_t cw::time::current_time()
{
spec_t t;
clock_gettime(CLOCK_REALTIME,&t);
return t;
}
#endif #endif
// this assumes that the seconds have been normalized to a recent start time // this assumes that the seconds have been normalized to a recent start time
@ -177,7 +182,7 @@ cw::rc_t cw::time::now( spec_t& ts )
memset(&ts,0,sizeof(ts)); memset(&ts,0,sizeof(ts));
if((errRC = clock_gettime(CLOCK_REALTIME, &ts)) != 0 ) if((errRC = clock_gettime(CLOCK_MONOTONIC, &ts)) != 0 )
rc = cwLogSysError(kInvalidOpRC,errRC,"Unable to obtain system time."); rc = cwLogSysError(kInvalidOpRC,errRC,"Unable to obtain system time.");
return rc; return rc;
@ -213,51 +218,40 @@ void cw::time::subtractMicros( spec_t& ts, unsigned micros )
} }
void cw::time::advanceMicros( spec_t& ts, unsigned us ) void cw::time::advanceMicros( spec_t& ts, unsigned us )
{ {
if( us > 1000000 ) const unsigned us_per_sec = 1000000;
{ const unsigned ns_per_sec = 1000000000;
// strip off whole seconds from usec
unsigned sec = us / 1000000;
// find the remaining fractional second in microseconds unsigned sec = us / us_per_sec;
us = (us - sec*1000000);
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 sec = ts.tv_nsec / ns_per_sec;
// stip off whole seconds from tv_nsec
while( ts.tv_nsec > 1000000000 )
{
ts.tv_nsec -= 1000000000;
ts.tv_sec +=1;
}
ts.tv_sec += sec;
ts.tv_nsec -= sec * ns_per_sec;
} }
void cw::time::advanceMs( spec_t& ts, unsigned ms ) 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 const unsigned ms_per_sec = 1000;
ms = (ms - sec*1000); const unsigned ns_per_sec = 1000000000;
unsigned sec = ms / ms_per_sec;
ts.tv_sec += sec; ts.tv_sec += sec;
} ts.tv_nsec += (ms - (sec*ms_per_sec)) * 1000000;
ts.tv_nsec += ms * 1000000; // convert millisconds to nanoseconds sec = ts.tv_nsec / ns_per_sec;
// stip off whole seconds from tv_nsec ts.tv_sec += sec;
while( ts.tv_nsec > 1000000000 ) ts.tv_nsec -= sec * ns_per_sec;
{
ts.tv_nsec -= 1000000000;
ts.tv_sec +=1;
}
} }
cw::rc_t cw::time::futureMs( spec_t& ts, unsigned ms ) cw::rc_t cw::time::futureMs( spec_t& ts, unsigned ms )
@ -288,6 +282,19 @@ double cw::time::specToSeconds( const spec_t& t )
return sec + ((double)ts.tv_nsec)/1e9; 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 ) 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; 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; const unsigned long long usPerSec = 1000000;
unsigned ns = (us - (sec*1000000)) * 1000; unsigned long long sec = us/usPerSec;
unsigned long long ns = (us - (sec*usPerSec)) * 1000;
ts.tv_sec = sec; ts.tv_sec = sec;
ts.tv_nsec = ns; 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 ) unsigned cw::time::formatDateTime( char* buffer, unsigned bufN, bool includeDateFl )
{ {

View File

@ -20,6 +20,7 @@ namespace cw
// Get the time // Get the time
void get( spec_t& tRef ); void get( spec_t& tRef );
spec_t current_time(); // same as get()
// Return the elapsed time (t1 - t0) in microseconds // Return the elapsed time (t1 - t0) in microseconds
// t1 is assumed to be at a later time than t0. // t1 is assumed to be at a later time than t0.
@ -61,7 +62,7 @@ namespace cw
void setZero( spec_t& t0 ); 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 ); void subtractMicros( spec_t& ts, unsigned us );
@ -75,8 +76,11 @@ namespace cw
void secondsToSpec( spec_t& ts, unsigned sec ); void secondsToSpec( spec_t& ts, unsigned sec );
double specToSeconds( const spec_t& ts ); double specToSeconds( const spec_t& ts );
unsigned long long specToMicroseconds( const spec_t& ts );
void millisecondsToSpec( spec_t& ts, unsigned ms ); 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[] // Return count of bytes in in buf[]
unsigned formatDateTime( char* buf, unsigned bufN, bool includeDateFl=false ); unsigned formatDateTime( char* buf, unsigned bufN, bool includeDateFl=false );

View File

@ -75,6 +75,15 @@ namespace cw
bool destroyFl; // used by the deleteElement() algorithm bool destroyFl; // used by the deleteElement() algorithm
} ele_t; } ele_t;
const unsigned hashN = 0xffff;
typedef struct bucket_str
{
ele_t* ele;
struct bucket_str* link;
} bucket_t;
typedef struct ui_str typedef struct ui_str
{ {
unsigned eleAllocN; // size of eleA[] unsigned eleAllocN; // size of eleA[]
@ -108,11 +117,59 @@ namespace cw
unsigned sentMsgN; unsigned sentMsgN;
unsigned recvMsgN; unsigned recvMsgN;
bucket_t hashA[ hashN ];
} ui_t; } ui_t;
ui_t* _handleToPtr( handle_t h ) ui_t* _handleToPtr( handle_t h )
{ return handleToPtr<handle_t,ui_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 ) void _print_eles( ui_t* p )
{ {
for(unsigned i=0; i<p->eleN; ++i) for(unsigned i=0; i<p->eleN; ++i)
@ -306,16 +363,23 @@ namespace cw
if( appId == kRootAppId ) if( appId == kRootAppId )
return kRootUuId; 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) 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
if( ( parentUuId == kInvalidId || p->eleA[i]->logical_parent->uuId == parentUuId ) && && ( parentUuId == kInvalidId || (p->eleA[i]->logical_parent!=nullptr && p->eleA[i]->logical_parent->uuId == parentUuId) )
( chanId == kInvalidId || p->eleA[i]->chanId == chanId ) && && ( chanId == kInvalidId || p->eleA[i]->chanId == chanId ) )
p->eleA[i]->appId == appId )
{ {
return p->eleA[i]->uuId; return p->eleA[i]->uuId;
} }
}
return kInvalidId; return kInvalidId;
} }
@ -550,6 +614,9 @@ namespace cw
unsigned _find_and_available_element_slot( ui_t* p ) 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) for(unsigned i=0; i<p->eleN; ++i)
if( p->eleA[i] == nullptr ) if( p->eleA[i] == nullptr )
return i; return i;
@ -608,7 +675,7 @@ namespace cw
// if there are no available slots // if there are no available slots
if( avail_ele_idx == p->eleAllocN ) if( avail_ele_idx == p->eleAllocN )
{ {
p->eleAllocN += 128; p->eleAllocN *= 2;
p->eleA = mem::resizeZ<ele_t*>(p->eleA,p->eleAllocN); p->eleA = mem::resizeZ<ele_t*>(p->eleA,p->eleAllocN);
} }
@ -635,6 +702,8 @@ namespace cw
e->appId = m->appId; 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)); //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; return e;
@ -698,7 +767,7 @@ namespace cw
if((rc = o->get("name",eleName, cw::kOptionalFl)) != kOkRC ) if((rc = o->get("name",eleName, cw::kOptionalFl)) != kOkRC )
{ {
// div's and titles don't need a 'name' // 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; rc = kOkRC;
else else
{ {
@ -1268,7 +1337,7 @@ cw::rc_t cw::ui::create(
ui_t* p = mem::allocZ<ui_t>(); ui_t* p = mem::allocZ<ui_t>();
p->eleAllocN = 128; p->eleAllocN = 1024;
p->eleA = mem::allocZ<ele_t*>( p->eleAllocN ); p->eleA = mem::allocZ<ele_t*>( p->eleAllocN );
p->eleN = 0; p->eleN = 0;
p->uiCbFunc = uiCbFunc; 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 ) if( p->eleA[i] != nullptr && p->eleA[i]->destroyFl )
{ {
_destroy_element( p->eleA[i] ); _destroy_element( p->eleA[i] );
p->eleA[i] = nullptr; 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); //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 ) void cw::ui::report( handle_t h )
{ {
ui_t* p = _handleToPtr(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; op = &o;
else else
if((op = o.find(object_label)) == nullptr ) 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( if((rc = op->getv(
"physRootDir", args.physRootDir, "physRootDir", args.physRootDir,
@ -2496,7 +2572,7 @@ cw::rc_t cw::ui::srv::create( handle_t& h,
goto errLabel; 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."); cwLogError(rc,"The websock UI server thread create failed.");
goto errLabel; goto errLabel;

3
cwUi.h
View File

@ -169,6 +169,7 @@ namespace cw
rc_t sendValueDouble( handle_t h, unsigned uuId, double value ); rc_t sendValueDouble( handle_t h, unsigned uuId, double value );
rc_t sendValueString( handle_t h, unsigned uuId, const char* 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 report( handle_t h );
void realTimeReport( handle_t h ); void realTimeReport( handle_t h );
@ -224,6 +225,8 @@ namespace cw
// This function should be called periodically to send and receive // This function should be called periodically to send and receive
// queued messages to and from the websocket. // 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 ); rc_t exec( handle_t h );
// This function executes the internal default websock callback function. // This function executes the internal default websock callback function.

View File

@ -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 // Arithmetic
// //

View File

@ -5,7 +5,6 @@
#include "cwText.h" #include "cwText.h"
#include "cwObject.h" #include "cwObject.h"
#include "cwTime.h" #include "cwTime.h"
#include "cwPresetSel.h"
#include "cwFile.h" #include "cwFile.h"
#include "cwFileSys.h" #include "cwFileSys.h"
#include "cwMidi.h" #include "cwMidi.h"

View File

@ -41,6 +41,12 @@ namespace cw
int _pollfdMaxN; int _pollfdMaxN;
int _pollfdN; 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; } websock_t;
inline websock_t* _handleToPtr(handle_t h) 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); //printf("recv: sess:%i proto:%s : %p : len:%li\n",sess->id,proto->name,ws->_cbFunc,len);
if( ws->_cbFunc != nullptr && len>0) if( ws->_cbFunc != nullptr && len>0)
{
ws->_cbFunc(ws->_cbArg,proto->id,sess->id,kMessageTId,in,len); ws->_cbFunc(ws->_cbArg,proto->id,sess->id,kMessageTId,in,len);
ws->_recvMsgCnt += 1;
ws->_recvMaxByteN = std::max(ws->_recvMaxByteN,(unsigned)len);
}
break; break;
@ -303,7 +313,7 @@ namespace cw
msg_t* t = m1->link; msg_t* t = m1->link;
mem::free(m1->msg); //mem::free(m1->msg);
mem::free(m1); mem::free(m1);
m1 = t; m1 = t;
@ -321,6 +331,8 @@ namespace cw
{ {
msg_t* m; 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 ) if( p->_ctx != nullptr )
{ {
lws_context_destroy(p->_ctx); lws_context_destroy(p->_ctx);
@ -332,7 +344,7 @@ namespace cw
while((m = p->_q->pop()) != nullptr) while((m = p->_q->pop()) != nullptr)
{ {
mem::free(m->msg); //mem::free(m->msg);
mem::free(m); mem::free(m);
} }
@ -351,7 +363,7 @@ namespace cw
{ {
msg_t* tmp = m->link; msg_t* tmp = m->link;
mem::free(m->msg); //mem::free(m->msg);
mem::free(m); mem::free(m);
m = tmp; m = tmp;
} }
@ -491,9 +503,14 @@ cw::rc_t cw::websock::send(handle_t h, unsigned protocolId, unsigned sessionId,
{ {
rc_t rc = kOkRC; rc_t rc = kOkRC;
msg_t* m = mem::allocZ<msg_t>(1); uint8_t* mem = mem::allocZ<uint8_t>( sizeof(msg_t) + LWS_PRE + byteN );
m->msg = mem::allocZ<unsigned char>(byteN); //msg_t* m = mem::allocZ<msg_t>(1);
memcpy(m->msg,msg,byteN); //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->msgByteN = byteN;
m->protocolId = protocolId; m->protocolId = protocolId;
m->sessionId = sessionId; 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); websock_t* p = _handleToPtr(h);
p->_q->push(m); p->_q->push(m);
p->_sendMsgCnt += 1;
p->_sendMaxByteN = std::max(p->_sendMaxByteN,byteN);
return rc; 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 // add the pre-padding bytes to the msg
unsigned char* msg = mem::allocZ<unsigned char>(LWS_PRE + m->msgByteN); //unsigned char* msg = mem::allocZ<unsigned char>(LWS_PRE + m->msgByteN);
memcpy( msg+LWS_PRE, m->msg, 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 m->msgId = ps->nextNewMsgId; // set the msg id
ps->begMsg->link = m; // put the msg on the front of the outgoing queue ps->begMsg->link = m; // put the msg on the front of the outgoing queue
ps->begMsg = m; // ps->begMsg = m; //

View File

@ -68,7 +68,7 @@ cw::rc_t cw::websockSrv::create(
goto errLabel; 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; goto errLabel;
p->_timeOutMs = timeOutMs; p->_timeOutMs = timeOutMs;

BIN
docs/2-3-trees.pdf Normal file

Binary file not shown.

View File

@ -3,170 +3,23 @@ var _rootId = "0";
var _nextEleId = 0; var _nextEleId = 0;
var _focusId = null; var _focusId = null;
var _focusVal = null; var _focusVal = null;
var _rootDivEle = null;
var _rootEle = null;
function set_app_title( suffix, className ) function set_app_title( suffix, className )
{ {
var ele = document.getElementById('connectTitleId'); var ele = document.getElementById('connectTitleId');
if(ele != null)
{
ele.innerHTML = suffix ele.innerHTML = suffix
ele.className = className 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 )
{
uiOnError("Parent not found. parent_id:" + r.parent_id,r);
} }
else
return parent_ele;
}
function uiCreateEle( r )
{
var parent_ele;
if((parent_ele = uiGetParent(r)) != null )
{ {
ele = document.createElement(r.ele_type) console.log("Ele. not found. Set title failed.")
ele.id = r.ele_id;
ele.className = r.value;
parent_ele.appendChild(ele)
} }
} }
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 ) 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 ) 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 ) function dom_set_checkbox( ele_id, fl )
{ dom_id_to_ele(ele_id).checked = fl } { dom_id_to_ele(ele_id).checked = fl }
@ -1197,6 +1072,11 @@ function ui_destroy( d )
ele.parentElement.removeChild( ele ) ele.parentElement.removeChild( ele )
} }
function ui_attach( d )
{
console.log("ATTACH");
//_rootDivEle.appendChild(_rootEle)
}
function ws_send( s ) function ws_send( s )
@ -1230,6 +1110,10 @@ function _ws_on_msg( d )
ui_set( d ) ui_set( d )
break; break;
case 'attach':
ui_attach(d)
break;
default: default:
ui_error("Unknown UI operation. " + d.op ) ui_error("Unknown UI operation. " + d.op )
} }
@ -1275,7 +1159,7 @@ function ws_form_url(urlSuffix)
return pcol + u[0] + "/" + urlSuffix; return pcol + u[0] + "/" + urlSuffix;
} }
function main() function main_0()
{ {
d = { "className":"uiAppDiv", "uuId":_rootId } d = { "className":"uiAppDiv", "uuId":_rootId }
rootEle = ui_create_div( document.body, d ) rootEle = ui_create_div( document.body, d )
@ -1290,7 +1174,28 @@ function main()
_ws.onmessage = ws_on_msg _ws.onmessage = ws_on_msg
_ws.onopen = ws_on_open _ws.onopen = ws_on_open
_ws.onclose = ws_on_close; _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.")
} }

View File

@ -12,6 +12,7 @@
button:{ name: ioReportBtnId, title:"IO Report" }, button:{ name: ioReportBtnId, title:"IO Report" },
button:{ name: netPrintBtnId, title:"Print Network" } button:{ name: netPrintBtnId, title:"Print Network" }
button:{ name: reportBtnId, title:"App Report" }, button:{ name: reportBtnId, title:"App Report" },
button:{ name: latencyBtnId, title:"Latency Reset"},
button:{ name: saveBtnId, title:"Save" }, button:{ name: saveBtnId, title:"Save" },
select:{ name: perfSelId, title:"Load",children: {} }, 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: { row: {
check: { name: printMidiCheckId, title: "Print MIDI" }, check: { name: printMidiCheckId, title: "Print MIDI" },
check: { name: pianoMidiCheckId, title: "Piano MIDI" }, check: { name: pianoMidiCheckId, title: "Piano MIDI" },