2024-12-01 19:35:24 +00:00
//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
2021-05-10 12:37:29 +00:00
# include "cwCommon.h"
# include "cwLog.h"
# include "cwCommonImpl.h"
2024-05-29 16:36:57 +00:00
# include "cwTest.h"
2021-05-10 12:37:29 +00:00
# include "cwMem.h"
2023-03-20 12:54:11 +00:00
# include "cwText.h"
2021-05-10 12:37:29 +00:00
# include "cwObject.h"
# include "cwFileSys.h"
# include "cwFile.h"
# include "cwTime.h"
# include "cwMidiDecls.h"
# include "cwMidi.h"
2021-08-15 20:02:45 +00:00
# include "cwMidiFile.h"
2021-05-10 12:37:29 +00:00
# include "cwUiDecls.h"
# include "cwIo.h"
2023-05-25 20:02:51 +00:00
# include "cwScoreFollowerPerf.h"
2021-05-10 12:37:29 +00:00
# include "cwIoMidiRecordPlay.h"
2023-05-21 16:40:23 +00:00
# include "cwMidiState.h"
# include "cwSvgMidi.h"
2021-05-10 12:37:29 +00:00
2021-12-30 02:37:07 +00:00
# define TIMER_LABEL "midi_record_play_timer"
2021-05-10 12:37:29 +00:00
namespace cw
{
namespace midi_record_play
{
2023-01-14 18:38:18 +00:00
enum {
2023-01-21 16:38:24 +00:00
kStoppedMidiStateId = 0 ,
2023-01-14 18:38:18 +00:00
kPlayingMidiStateId ,
kStoppingMidiStateId
} ;
typedef struct midi_state_str
{
uint8_t keyVel [ midi : : kMidiNoteCnt ] ;
uint8_t ctlVal [ midi : : kMidiCtlCnt ] ;
unsigned state ;
} midi_state_t ;
2021-05-10 12:37:29 +00:00
typedef struct am_midi_msg_str
{
unsigned devIdx ;
unsigned portIdx ;
2021-11-01 14:13:48 +00:00
unsigned microsec ;
unsigned id ;
2021-05-10 12:37:29 +00:00
time : : spec_t timestamp ;
2022-05-06 20:03:43 +00:00
unsigned loc ;
2023-11-26 20:23:36 +00:00
const void * arg ;
2021-05-10 12:37:29 +00:00
uint8_t ch ;
uint8_t status ;
uint8_t d0 ;
uint8_t d1 ;
2022-12-22 20:52:49 +00:00
unsigned chordNoteCnt ; // count of notes in same chord as this note (only set on Note-on messages)
2021-05-10 12:37:29 +00:00
} am_midi_msg_t ;
2022-01-22 14:42:21 +00:00
2023-06-27 21:44:23 +00:00
typedef struct vel_tbl_str
{
uint8_t * velTblA ;
unsigned velTblN ;
bool enableFl ;
bool defaultFl ;
char * name ;
char * device ;
} vel_tbl_t ;
2022-01-22 14:42:21 +00:00
typedef struct midi_device_str
{
2023-06-27 21:44:23 +00:00
char * label ;
2022-01-22 14:42:21 +00:00
char * midiOutDevLabel ;
char * midiOutPortLabel ;
unsigned midiOutDevIdx ;
unsigned midiOutPortIdx ;
2023-06-27 21:44:23 +00:00
2022-01-22 14:42:21 +00:00
bool enableFl ;
2022-03-20 14:27:46 +00:00
unsigned velTableN ;
uint8_t * velTableArray ;
bool pedalMapEnableFl ;
2022-05-06 20:03:43 +00:00
2022-03-20 14:27:46 +00:00
unsigned pedalDownVelId ;
unsigned pedalDownVel ;
2023-01-14 18:38:18 +00:00
unsigned pedalUpVelId ;
unsigned pedalUpVel ;
2022-05-06 20:03:43 +00:00
unsigned pedalDownHalfVelId ;
2022-03-20 14:27:46 +00:00
unsigned pedalDownHalfVel ;
2022-05-06 20:03:43 +00:00
unsigned pedalUpHalfVelId ;
unsigned pedalUpHalfVel ;
2022-10-15 13:26:35 +00:00
unsigned velHistogram [ midi : : kMidiVelCnt ] ;
bool force_damper_down_fl ;
unsigned force_damper_down_threshold ;
unsigned force_damper_down_velocity ;
2022-11-11 19:04:44 +00:00
bool damper_dead_band_enable_fl ;
unsigned damper_dead_band_min_value ;
unsigned damper_dead_band_max_value ;
2022-12-22 20:52:49 +00:00
bool scale_chord_notes_enable_fl ;
double scale_chord_notes_factor ;
2023-01-14 18:38:18 +00:00
midi_state_t midi_state ;
2022-05-06 20:03:43 +00:00
2022-01-22 14:42:21 +00:00
} midi_device_t ;
2022-03-20 14:27:46 +00:00
2023-01-21 16:38:24 +00:00
enum
{
kHalfPedalDone ,
kWaitForBegin ,
kWaitForNoteOn ,
kWaitForNoteOff ,
kWaitForPedalUp ,
kWaitForPedalDown ,
} ;
2022-03-20 14:27:46 +00:00
2021-05-10 12:37:29 +00:00
typedef struct midi_record_play_str
{
io : : handle_t ioH ;
2022-01-22 14:42:21 +00:00
am_midi_msg_t * msgArray ; // msgArray[ msgArrayN ]
unsigned msgArrayN ; // Count of messages allocated in msgArray.
2022-03-20 14:27:46 +00:00
unsigned msgArrayInIdx ; // Next available space for loaded MIDI messages (also the current count of msgs in msgArray[])
2022-01-22 14:42:21 +00:00
unsigned msgArrayOutIdx ; // Next message to transmit in msgArray[]
unsigned midi_timer_period_micro_sec ; // Timer period in microseconds
2023-06-27 21:44:23 +00:00
unsigned midi_timer_index ; //
2022-05-14 14:22:29 +00:00
unsigned all_off_delay_ms ; // Wait this long before turning all notes off after the last note-on has played
2021-05-10 12:37:29 +00:00
2022-03-20 14:27:46 +00:00
am_midi_msg_t * iMsgArray ; // msgArray[ msgArrayN ]
unsigned iMsgArrayN ; // Count of messages allocated in msgArray.
unsigned iMsgArrayInIdx ; // Next available space for incoming MIDI messages (also the current count of msgs in msgArray[])
2023-05-25 20:02:51 +00:00
unsigned lastStoreIndex ;
2022-03-20 14:27:46 +00:00
2022-01-22 14:42:21 +00:00
midi_device_t * midiDevA ;
unsigned midiDevN ;
2021-05-10 12:37:29 +00:00
bool startedFl ;
bool recordFl ;
2022-11-11 19:04:44 +00:00
bool muteFl ;
2021-05-10 12:37:29 +00:00
bool thruFl ;
2022-03-20 14:27:46 +00:00
bool logInFl ; // log incoming message when not in 'record' mode.
bool logOutFl ; // log outgoing messages
2023-01-14 18:38:18 +00:00
bool supressMidiXmitFl ; // don't transmit MIDI
2022-10-15 13:26:35 +00:00
2023-01-14 22:17:19 +00:00
unsigned minDamperDownMs ;
2022-10-15 13:26:35 +00:00
bool velHistogramEnableFl ;
2021-05-10 12:37:29 +00:00
2022-03-20 14:27:46 +00:00
bool halfPedalFl ;
unsigned halfPedalState ;
unsigned halfPedalNextUs ;
unsigned halfPedalNoteDelayUs ;
unsigned halfPedalNoteDurUs ;
unsigned halfPedalUpDelayUs ;
unsigned halfPedalDownDelayUs ;
uint8_t halfPedalMidiPitch ;
uint8_t halfPedalMidiNoteVel ;
uint8_t halfPedalMidiPedalVel ;
2021-05-10 12:37:29 +00:00
time : : spec_t play_time ;
2021-08-15 20:02:45 +00:00
time : : spec_t start_time ;
2021-11-14 16:52:24 +00:00
time : : spec_t end_play_event_timestamp ;
2022-05-14 14:22:29 +00:00
time : : spec_t all_off_timestamp ;
2021-08-15 20:02:45 +00:00
2021-11-01 14:13:48 +00:00
event_callback_t cb ;
void * cb_arg ;
2023-02-26 18:40:49 +00:00
2021-05-10 12:37:29 +00:00
} midi_record_play_t ;
2022-03-20 14:27:46 +00:00
2021-05-10 12:37:29 +00:00
enum
{
kMidiRecordPlayTimerId
} ;
midi_record_play_t * _handleToPtr ( handle_t h )
{ return handleToPtr < handle_t , midi_record_play_t > ( h ) ; }
rc_t _destroy ( midi_record_play_t * p )
{
rc_t rc = kOkRC ;
2021-12-30 02:37:07 +00:00
2023-06-27 21:44:23 +00:00
if ( p - > midi_timer_index ! = kInvalidIdx )
io : : timerDestroy ( p - > ioH , p - > midi_timer_index ) ;
2022-01-22 14:42:21 +00:00
for ( unsigned i = 0 ; i < p - > midiDevN ; + + i )
{
2023-06-27 21:44:23 +00:00
mem : : release ( p - > midiDevA [ i ] . label ) ;
2022-01-22 14:42:21 +00:00
mem : : release ( p - > midiDevA [ i ] . midiOutDevLabel ) ;
mem : : release ( p - > midiDevA [ i ] . midiOutPortLabel ) ;
2022-03-20 14:27:46 +00:00
mem : : release ( p - > midiDevA [ i ] . velTableArray ) ;
2022-01-22 14:42:21 +00:00
}
mem : : release ( p - > midiDevA ) ;
2021-05-10 12:37:29 +00:00
mem : : release ( p - > msgArray ) ;
2022-03-20 14:27:46 +00:00
mem : : release ( p - > iMsgArray ) ;
2021-05-10 12:37:29 +00:00
mem : : release ( p ) ;
return rc ;
}
2023-06-27 21:44:23 +00:00
unsigned _label_to_device_index ( midi_record_play_t * p , const char * label )
{
for ( unsigned i = 0 ; i < p - > midiDevN ; + + i )
if ( textCompare ( p - > midiDevA [ i ] . label , label ) = = 0 )
return i ;
return kInvalidIdx ;
}
2023-01-14 18:38:18 +00:00
void _xmit_midi ( midi_record_play_t * p , unsigned devIdx , uint8_t ch , uint8_t status , uint8_t d0 , uint8_t d1 )
{
2024-02-08 16:18:49 +00:00
if ( ! p - > supressMidiXmitFl & & p - > midiDevA [ devIdx ] . enableFl )
2023-01-21 16:38:24 +00:00
io : : midiDeviceSend ( p - > ioH , p - > midiDevA [ devIdx ] . midiOutDevIdx , p - > midiDevA [ devIdx ] . midiOutPortIdx , status + ch , d0 , d1 ) ;
2023-01-14 18:38:18 +00:00
}
// initialze the midi state to all-notes-off and all controllers=0
void _midi_state_clear ( midi_record_play_t * p )
2023-01-21 16:38:24 +00:00
{
2023-01-14 18:38:18 +00:00
for ( unsigned i = 0 ; i < p - > midiDevN ; + + i )
{
2023-01-21 16:38:24 +00:00
for ( unsigned j = 0 ; j < midi : : kMidiNoteCnt ; + + j )
p - > midiDevA [ i ] . midi_state . keyVel [ j ] = 0 ;
2023-01-14 18:38:18 +00:00
2023-01-21 16:38:24 +00:00
for ( unsigned j = 0 ; j < midi : : kMidiCtlCnt ; + + j )
p - > midiDevA [ i ] . midi_state . ctlVal [ j ] = 0 ;
p - > midiDevA [ i ] . midi_state . state = kPlayingMidiStateId ;
}
2023-01-14 18:38:18 +00:00
}
void _midi_state_set_state ( midi_record_play_t * p , unsigned stateId )
{
for ( unsigned i = 0 ; i < p - > midiDevN ; + + i )
2023-01-21 16:38:24 +00:00
p - > midiDevA [ i ] . midi_state . state = stateId ;
2023-01-14 18:38:18 +00:00
}
bool _midi_state_is_state ( midi_record_play_t * p , unsigned stateId )
{
for ( unsigned i = 0 ; i < p - > midiDevN ; + + i )
2023-01-21 16:38:24 +00:00
if ( p - > midiDevA [ i ] . midi_state . state ! = stateId )
return false ;
2023-01-14 18:38:18 +00:00
return true ;
}
void _midi_state_set_stopping ( midi_record_play_t * p )
{ _midi_state_set_state ( p , kStoppingMidiStateId ) ; }
bool _midi_state_is_stopping ( midi_record_play_t * p )
{ return _midi_state_is_state ( p , kStoppingMidiStateId ) ; }
bool _midi_state_is_stopped ( midi_record_play_t * p )
{ return _midi_state_is_state ( p , kStoppedMidiStateId ) ; }
2023-01-21 16:38:24 +00:00
2023-01-14 18:38:18 +00:00
unsigned _midi_state_active_note_count ( midi_record_play_t * p )
{
unsigned n = 0 ;
for ( unsigned i = 0 ; i < p - > midiDevN ; + + i )
2023-01-21 16:38:24 +00:00
for ( unsigned j = 0 ; j < midi : : kMidiNoteCnt ; + + j )
n + = p - > midiDevA [ i ] . midi_state . keyVel [ j ] > 0 ? 1 : 0 ;
2023-01-14 18:38:18 +00:00
return n ;
}
rc_t _midi_state_update ( midi_record_play_t * p , midi_device_t & dev , uint8_t ch , uint8_t status , uint8_t d0 , uint8_t d1 )
{
rc_t rc = kOkRC ;
if ( ch ! = 0 )
{
2023-01-21 16:38:24 +00:00
rc = cwLogError ( kInvalidArgRC , " Only the state of MIDI channel 0 tracked. " ) ;
goto errLabel ;
2023-01-14 18:38:18 +00:00
}
if ( d0 > 127 | | d1 > 127 )
{
2023-01-21 16:38:24 +00:00
rc = cwLogError ( kInvalidArgRC , " Illegal MIDI byte value: st:%i d0:%i d1:%i " , status , d0 , d1 ) ;
goto errLabel ;
2023-01-14 18:38:18 +00:00
}
if ( midi : : isNoteOn ( status , d1 ) )
{
2023-01-21 16:38:24 +00:00
if ( dev . midi_state . state = = kPlayingMidiStateId )
dev . midi_state . keyVel [ d0 ] = d1 ;
goto errLabel ;
2023-01-14 18:38:18 +00:00
}
if ( midi : : isNoteOff ( status , d1 ) )
2023-01-21 16:38:24 +00:00
{
dev . midi_state . keyVel [ d0 ] = 0 ;
goto errLabel ;
2023-01-14 18:38:18 +00:00
}
if ( midi : : isCtl ( status ) )
{
2023-01-21 16:38:24 +00:00
dev . midi_state . ctlVal [ d0 ] = d1 ;
goto errLabel ;
2023-01-14 18:38:18 +00:00
}
2023-01-21 16:38:24 +00:00
2023-01-14 18:38:18 +00:00
errLabel :
return rc ;
}
bool _midi_state_are_all_notes_off ( midi_record_play_t * p )
{
if ( _midi_state_is_stopped ( p ) )
2023-01-21 16:38:24 +00:00
return true ;
2023-01-14 18:38:18 +00:00
if ( _midi_state_is_stopping ( p ) )
2023-01-21 16:38:24 +00:00
if ( _midi_state_active_note_count ( p ) = = 0 )
{
_midi_state_set_state ( p , kStoppedMidiStateId ) ;
return true ;
}
2023-01-14 18:38:18 +00:00
return false ;
}
uint8_t _midi_state_ctl_value ( midi_record_play_t * p , midi_device_t & dev , uint8_t d0 )
{
return dev . midi_state . ctlVal [ d0 ] ;
}
void _midi_state_xmit_pedals ( midi_record_play_t * p )
{
uint8_t ctlA [ ] = { midi : : kSustainCtlMdId , midi : : kSostenutoCtlMdId , midi : : kSoftPedalCtlMdId } ;
unsigned ctlN = sizeof ( ctlA ) / sizeof ( ctlA [ 0 ] ) ;
for ( unsigned i = 0 ; i < p - > midiDevN ; + + i )
2023-01-21 16:38:24 +00:00
for ( unsigned j = 0 ; j < ctlN ; + + j )
{
uint8_t d1 = _midi_state_ctl_value ( p , p - > midiDevA [ i ] , ctlA [ j ] ) ;
_xmit_midi ( p , i , 0 , midi : : kCtlMdId , ctlA [ j ] , d1 ) ;
}
2023-01-14 18:38:18 +00:00
}
2022-12-22 20:52:49 +00:00
void _set_chord_note_count ( am_midi_msg_t * m0 , const am_midi_msg_t * m , unsigned chordNoteCnt )
{
for ( ; m0 < m ; + + m0 )
if ( midi : : isNoteOn ( m0 - > status , m0 - > d1 ) )
m0 - > chordNoteCnt = chordNoteCnt ;
}
void _set_chord_note_count ( midi_record_play_t * p )
{
unsigned chordNoteCnt = 1 ;
am_midi_msg_t * m0 = p - > msgArray ;
am_midi_msg_t * m = nullptr ;
for ( unsigned i = 1 ; i < p - > msgArrayN ; + + i )
{
m = p - > msgArray + i ;
if ( midi : : isNoteOn ( m - > status , m - > d1 ) )
{
if ( time : : isEqual ( m0 - > timestamp , m - > timestamp ) )
+ + chordNoteCnt ;
else
{
_set_chord_note_count ( m0 , m , chordNoteCnt ) ;
chordNoteCnt = 1 ;
m0 = m ;
}
}
}
2023-06-27 21:44:23 +00:00
_set_chord_note_count ( m0 , m , chordNoteCnt ) ;
2022-12-22 20:52:49 +00:00
}
2021-05-10 12:37:29 +00:00
rc_t _parseCfg ( midi_record_play_t * p , const object_t & cfg )
{
rc_t rc = kOkRC ;
2022-01-22 14:42:21 +00:00
const object_t * midiDevL = nullptr ;
2021-05-10 12:37:29 +00:00
if ( ( rc = cfg . getv (
" max_midi_msg_count " , p - > msgArrayN ,
" midi_timer_period_micro_sec " , p - > midi_timer_period_micro_sec ,
2022-05-14 14:22:29 +00:00
" all_off_delay_ms " , p - > all_off_delay_ms ,
2022-03-20 14:27:46 +00:00
" midi_device_list " , midiDevL ,
" log_in_flag " , p - > logInFl ,
" log_out_flag " , p - > logOutFl ,
2023-01-21 16:38:24 +00:00
" min_damper_down_time_ms " , p - > minDamperDownMs ,
2022-03-20 14:27:46 +00:00
" half_pedal_flag " , p - > halfPedalFl ) ) ! = kOkRC )
2021-05-10 12:37:29 +00:00
{
rc = cwLogError ( kSyntaxErrorRC , " MIDI record play configuration parse failed. " ) ;
goto errLabel ;
}
2022-03-20 14:27:46 +00:00
p - > iMsgArrayN = p - > msgArrayN ;
2023-05-25 20:02:51 +00:00
p - > lastStoreIndex = kInvalidIdx ;
2022-01-22 14:42:21 +00:00
if ( midiDevL - > child_count ( ) > 0 )
{
p - > midiDevN = midiDevL - > child_count ( ) ;
p - > midiDevA = mem : : allocZ < midi_device_t > ( p - > midiDevN ) ;
printf ( " Midi record play devices:%i \n " , p - > midiDevN ) ;
for ( unsigned i = 0 ; i < p - > midiDevN ; + + i )
{
const object_t * ele = midiDevL - > child_ele ( i ) ;
2023-06-27 21:44:23 +00:00
const char * label = nullptr ;
2022-01-22 14:42:21 +00:00
const char * midiOutDevLabel = nullptr ;
const char * midiOutPortLabel = nullptr ;
2022-03-20 14:27:46 +00:00
const object_t * pedalRecd = nullptr ;
2022-01-22 14:42:21 +00:00
bool enableFl = false ;
2023-06-27 21:44:23 +00:00
if ( ( rc = ele - > getv ( " label " , label ,
" midi_out_device " , midiOutDevLabel ,
2022-01-22 14:42:21 +00:00
" midi_out_port " , midiOutPortLabel ,
" enableFl " , enableFl ) ) ! = kOkRC )
{
rc = cwLogError ( kSyntaxErrorRC , " MIDI record play device list configuration parse failed. " ) ;
goto errLabel ;
}
2022-03-20 14:27:46 +00:00
2022-01-22 14:42:21 +00:00
2022-11-11 19:04:44 +00:00
if ( ( rc = ele - > getv_opt (
2023-01-21 16:38:24 +00:00
" pedal " , pedalRecd ,
" force_damper_down_fl " , p - > midiDevA [ i ] . force_damper_down_fl ,
" force_damper_down_threshold " , p - > midiDevA [ i ] . force_damper_down_threshold ,
" force_damper_down_velocity " , p - > midiDevA [ i ] . force_damper_down_velocity ,
" damper_dead_band_enable_fl " , p - > midiDevA [ i ] . damper_dead_band_enable_fl ,
" damper_dead_band_min_value " , p - > midiDevA [ i ] . damper_dead_band_min_value ,
" damper_dead_band_max_value " , p - > midiDevA [ i ] . damper_dead_band_max_value ,
" scale_chord_notes_enable_fl " , p - > midiDevA [ i ] . scale_chord_notes_enable_fl ,
" scale_chord_notes_factor " , p - > midiDevA [ i ] . scale_chord_notes_factor ) ) ! = kOkRC )
2022-03-20 14:27:46 +00:00
{
rc = cwLogError ( kSyntaxErrorRC , " MIDI record play device optional argument parsing failed. " ) ;
goto errLabel ;
}
2022-10-15 13:26:35 +00:00
2022-11-11 19:04:44 +00:00
cwLogInfo ( " Force Pedal: enabled:%i thresh:%i veloc:%i dead band: enable:%i min:%i max:%i " ,
2023-01-21 16:38:24 +00:00
p - > midiDevA [ i ] . force_damper_down_fl ,
p - > midiDevA [ i ] . force_damper_down_threshold ,
p - > midiDevA [ i ] . force_damper_down_velocity ,
p - > midiDevA [ i ] . damper_dead_band_enable_fl ,
p - > midiDevA [ i ] . damper_dead_band_min_value ,
p - > midiDevA [ i ] . damper_dead_band_max_value ) ;
2023-06-27 21:44:23 +00:00
p - > midiDevA [ i ] . label = mem : : duplStr ( label ) ;
2022-01-22 14:42:21 +00:00
p - > midiDevA [ i ] . midiOutDevLabel = mem : : duplStr ( midiOutDevLabel ) ;
p - > midiDevA [ i ] . midiOutPortLabel = mem : : duplStr ( midiOutPortLabel ) ;
p - > midiDevA [ i ] . enableFl = enableFl ;
2022-03-20 14:27:46 +00:00
if ( pedalRecd ! = nullptr )
{
if ( ( rc = pedalRecd - > getv ( " down_id " , p - > midiDevA [ i ] . pedalDownVelId ,
" down_vel " , p - > midiDevA [ i ] . pedalDownVel ,
2023-01-21 16:38:24 +00:00
" up_id " , p - > midiDevA [ i ] . pedalUpVelId ,
" up_vel " , p - > midiDevA [ i ] . pedalUpVel ,
2022-05-06 20:03:43 +00:00
" half_down_id " , p - > midiDevA [ i ] . pedalDownHalfVelId ,
" half_down_vel " , p - > midiDevA [ i ] . pedalDownHalfVel ,
" half_up_id " , p - > midiDevA [ i ] . pedalUpHalfVelId ,
" half_up_vel " , p - > midiDevA [ i ] . pedalUpHalfVel
) ) ! = kOkRC )
2022-03-20 14:27:46 +00:00
{
rc = cwLogError ( kSyntaxErrorRC , " An error occured while parsing the pedal record for MIDI device:'%s' port:'%s'. " , midiOutDevLabel , midiOutPortLabel ) ;
goto errLabel ;
}
else
{
p - > midiDevA [ i ] . pedalMapEnableFl = true ;
}
}
2022-01-22 14:42:21 +00:00
}
}
2021-05-10 12:37:29 +00:00
// allocate the MIDI msg buffer
p - > msgArray = mem : : allocZ < am_midi_msg_t > ( p - > msgArrayN ) ;
2022-03-20 14:27:46 +00:00
p - > iMsgArray = mem : : allocZ < am_midi_msg_t > ( p - > iMsgArrayN ) ;
2021-05-10 12:37:29 +00:00
errLabel :
return rc ;
}
2021-11-14 16:52:24 +00:00
rc_t _stop ( midi_record_play_t * p ) ;
2022-03-20 14:27:46 +00:00
const am_midi_msg_t * _midi_store ( midi_record_play_t * p , unsigned devIdx , unsigned portIdx , const time : : spec_t & ts , uint8_t ch , uint8_t status , uint8_t d0 , uint8_t d1 )
{
am_midi_msg_t * am = nullptr ;
2022-10-15 19:07:06 +00:00
//if( !midi::isPedal(status,d0) )
// printf("MIDI store: %i : ch:%i st:%i d0:%i d1:%i\n",p->iMsgArrayInIdx,ch,status,d0,d1);
2023-05-25 20:02:51 +00:00
p - > lastStoreIndex = kInvalidIdx ;
2022-03-20 14:27:46 +00:00
// verify that space exists in the record buffer
if ( p - > iMsgArrayInIdx < p - > iMsgArrayN )
{
// MAKE THIS ATOMIC
2023-05-25 20:02:51 +00:00
unsigned id = p - > iMsgArrayInIdx ;
p - > lastStoreIndex = p - > iMsgArrayInIdx ;
2022-03-20 14:27:46 +00:00
+ + p - > iMsgArrayInIdx ;
am = p - > iMsgArray + id ;
am - > id = id ;
am - > devIdx = devIdx ;
am - > portIdx = portIdx ;
am - > timestamp = ts ;
am - > ch = ch ;
am - > status = status ;
am - > d0 = d0 ;
am - > d1 = d1 ;
}
return am ;
}
2023-01-14 18:38:18 +00:00
2023-11-26 20:23:36 +00:00
rc_t _event_callback ( midi_record_play_t * p , unsigned id , const time : : spec_t timestamp , unsigned loc , const void * arg , uint8_t ch , uint8_t status , uint8_t d0 , uint8_t d1 , unsigned chordNoteCnt , bool log_fl = true )
2021-11-01 14:13:48 +00:00
{
2021-11-14 16:52:24 +00:00
rc_t rc = kOkRC ;
2023-01-14 18:38:18 +00:00
// if we have arrived at the stop time (but we may still be waiting for note-end messages)
2022-05-14 14:22:29 +00:00
bool after_stop_time_fl = ! time : : isZero ( p - > end_play_event_timestamp ) & & time : : isGT ( timestamp , p - > end_play_event_timestamp ) ;
2023-01-14 18:38:18 +00:00
if ( after_stop_time_fl )
2023-01-21 16:38:24 +00:00
_midi_state_set_stopping ( p ) ;
2023-01-14 18:38:18 +00:00
// if we are after the stop time and after the all-notes-off time
// bool after_all_off_fl = after_stop_time_fl && time::isGT(timestamp,p->all_off_timestamp);
// if we are after the stop time and there are no more active notes
bool after_all_off_fl = after_stop_time_fl & & _midi_state_are_all_notes_off ( p ) ;
bool is_note_on_fl = midi : : isNoteOn ( status , d1 ) ;
bool is_damper_fl = midi : : isSustainPedal ( status , d0 ) ;
2022-11-11 19:04:44 +00:00
bool supress_fl = ( is_note_on_fl & & after_stop_time_fl ) | | p - > muteFl ;
bool is_pedal_fl = midi : : isPedal ( status , d0 ) ;
2021-11-14 16:52:24 +00:00
2023-05-01 01:20:25 +00:00
if ( after_all_off_fl )
2021-11-14 16:52:24 +00:00
{
rc = _stop ( p ) ;
}
2022-05-14 14:22:29 +00:00
else
2021-11-14 16:52:24 +00:00
{
2022-03-20 14:27:46 +00:00
if ( p - > halfPedalFl )
{
if ( status = = midi : : kCtlMdId & & d0 = = midi : : kSustainCtlMdId & & d1 ! = 0 )
d1 = p - > halfPedalMidiPedalVel ;
}
2022-05-14 14:22:29 +00:00
// for each midi device
2022-01-22 14:42:21 +00:00
for ( unsigned i = 0 ; i < p - > midiDevN ; + + i )
if ( p - > midiDevA [ i ] . enableFl )
2022-03-20 14:27:46 +00:00
{
uint8_t out_d1 = d1 ;
2022-05-14 14:22:29 +00:00
2022-03-20 14:27:46 +00:00
if ( ! p - > halfPedalFl )
{
2023-01-14 18:38:18 +00:00
// if this is a note-on and a vel. table exists
2022-05-14 14:22:29 +00:00
if ( is_note_on_fl and p - > midiDevA [ i ] . velTableArray ! = nullptr )
2022-03-20 14:27:46 +00:00
{
2023-01-21 16:38:24 +00:00
// verify that the vel. is legal given the table
2022-03-20 14:27:46 +00:00
if ( d1 > = p - > midiDevA [ i ] . velTableN )
2023-06-27 21:44:23 +00:00
cwLogError ( kInvalidIdRC , " A MIDI note-on velocity (%i) outside the velocity table (%i>%i) range was encountered on device:%s. " , d1 , ( p - > midiDevA [ i ] . velTableN ) - 1 , cwStringNullGuard ( p - > midiDevA [ i ] . label ) ) ;
2022-03-20 14:27:46 +00:00
else
out_d1 = p - > midiDevA [ i ] . velTableArray [ d1 ] ;
2022-12-22 20:52:49 +00:00
2023-01-21 16:38:24 +00:00
// scale the note velocities of chords to account for their combined volumes
2022-12-22 20:52:49 +00:00
if ( p - > midiDevA [ i ] . scale_chord_notes_enable_fl & & chordNoteCnt > 1 )
{
uint8_t delta = ( uint8_t ) lround ( out_d1 * p - > midiDevA [ i ] . scale_chord_notes_factor * ( chordNoteCnt - 1 ) ) ;
if ( delta < out_d1 )
{
//printf("%i %i %i %i\n",chordNoteCnt,delta,out_d1,out_d1-delta);
out_d1 - = delta ;
}
}
2022-03-20 14:27:46 +00:00
}
2022-11-11 19:04:44 +00:00
// store the note-on velocity histogram data
2022-10-15 13:26:35 +00:00
if ( p - > velHistogramEnableFl & & is_note_on_fl & & out_d1 < midi : : kMidiVelCnt )
p - > midiDevA [ i ] . velHistogram [ out_d1 ] + = 1 ;
2022-11-11 19:04:44 +00:00
// if the damper pedal velocity is in the dead band then don't send it
if ( p - > midiDevA [ i ] . damper_dead_band_enable_fl & & is_pedal_fl & & p - > midiDevA [ i ] . damper_dead_band_min_value < = out_d1 & & out_d1 < = p - > midiDevA [ i ] . damper_dead_band_max_value )
out_d1 = 0 ;
// if the damper pedal velocity is over the 'forcing' threshold then force the damper down
2022-10-15 13:26:35 +00:00
if ( p - > midiDevA [ i ] . force_damper_down_fl & & is_damper_fl & & out_d1 > p - > midiDevA [ i ] . force_damper_down_threshold )
out_d1 = p - > midiDevA [ i ] . force_damper_down_velocity ;
2023-01-14 18:38:18 +00:00
// map the pedal velocity
2022-03-20 14:27:46 +00:00
if ( status = = midi : : kCtlMdId & & d0 = = midi : : kSustainCtlMdId & & p - > midiDevA [ i ] . pedalMapEnableFl )
{
2023-01-14 18:38:18 +00:00
if ( d1 = = p - > midiDevA [ i ] . pedalUpVelId )
out_d1 = p - > midiDevA [ i ] . pedalUpVel ;
2022-03-20 14:27:46 +00:00
else
2022-05-06 20:03:43 +00:00
if ( d1 = = p - > midiDevA [ i ] . pedalDownVelId )
out_d1 = p - > midiDevA [ i ] . pedalDownVel ;
2022-03-20 14:27:46 +00:00
else
2022-05-06 20:03:43 +00:00
if ( d1 = = p - > midiDevA [ i ] . pedalDownHalfVelId )
out_d1 = p - > midiDevA [ i ] . pedalDownHalfVel ;
2022-10-01 22:51:30 +00:00
else
{
2022-10-15 13:26:35 +00:00
cwLogError ( kInvalidIdRC , " Unexpected pedal down velocity (%i) during pedal velocity mapping. Remove the 'pedal' stanza from the MIDI device cfg to prevent pedal mapping. " , d1 ) ;
2022-10-01 22:51:30 +00:00
}
2022-03-20 14:27:46 +00:00
}
}
2022-05-14 14:22:29 +00:00
2023-01-21 16:38:24 +00:00
_midi_state_update ( p , p - > midiDevA [ i ] , ch , status , d0 , out_d1 ) ;
2022-11-11 19:04:44 +00:00
if ( ! supress_fl )
{
2023-01-21 16:38:24 +00:00
_xmit_midi ( p , i , ch , status , d0 , out_d1 ) ;
2022-11-11 19:04:44 +00:00
}
2022-03-20 14:27:46 +00:00
}
2021-11-01 14:13:48 +00:00
2023-01-21 16:38:24 +00:00
// don't inform the app if we are past the end time
2022-05-14 14:22:29 +00:00
if ( ! after_stop_time_fl and p - > cb )
2023-11-26 20:23:36 +00:00
p - > cb ( p - > cb_arg , kMidiEventActionId , id , timestamp , loc , arg , ch , status , d0 , d1 ) ;
2022-03-20 14:27:46 +00:00
if ( log_fl & & p - > logOutFl )
{
// Note: The device of outgoing messages is set to p->midiDevN + 1 to distinguish it from
// incoming messages.
_midi_store ( p , p - > midiDevN , 0 , timestamp , ch , status , d0 , d1 ) ;
}
2021-11-14 16:52:24 +00:00
}
return rc ;
2021-11-01 14:13:48 +00:00
}
2022-10-26 12:47:54 +00:00
2022-03-20 14:27:46 +00:00
rc_t _transmit_msg ( midi_record_play_t * p , const am_midi_msg_t * am , bool log_fl = true )
{
2023-11-26 20:23:36 +00:00
return _event_callback ( p , am - > id , am - > timestamp , am - > loc , am - > arg , am - > ch , am - > status , am - > d0 , am - > d1 , am - > chordNoteCnt , log_fl ) ;
2022-03-20 14:27:46 +00:00
}
rc_t _transmit_note ( midi_record_play_t * p , unsigned ch , unsigned pitch , unsigned vel , unsigned microsecs )
2021-11-01 14:13:48 +00:00
{
2024-03-25 14:46:45 +00:00
time : : spec_t ts = { } ;
2022-03-20 14:27:46 +00:00
time : : microsecondsToSpec ( ts , microsecs ) ;
2023-11-26 20:23:36 +00:00
return _event_callback ( p , kInvalidId , ts , kInvalidId , nullptr , ch , midi : : kNoteOnMdId , pitch , vel , 0 ) ;
2021-11-01 14:13:48 +00:00
}
2022-03-20 14:27:46 +00:00
rc_t _transmit_ctl ( midi_record_play_t * p , unsigned ch , unsigned ctlId , unsigned ctlVal , unsigned microsecs )
2021-11-01 14:13:48 +00:00
{
2024-03-25 14:46:45 +00:00
time : : spec_t ts = { } ;
2022-03-20 14:27:46 +00:00
time : : microsecondsToSpec ( ts , microsecs ) ;
2023-11-26 20:23:36 +00:00
return _event_callback ( p , kInvalidId , ts , kInvalidId , nullptr , ch , midi : : kCtlMdId , ctlId , ctlVal , 0 ) ;
2021-11-01 14:13:48 +00:00
}
2022-03-20 14:27:46 +00:00
rc_t _transmit_pedal ( midi_record_play_t * p , unsigned ch , unsigned pedalCtlId , bool pedalDownFl , unsigned microsecs )
2021-11-01 14:13:48 +00:00
{
2022-03-20 14:27:46 +00:00
return _transmit_ctl ( p , ch , pedalCtlId , pedalDownFl ? 127 : 0 , microsecs ) ;
2021-11-01 14:13:48 +00:00
}
2022-03-20 14:27:46 +00:00
void _half_pedal_update ( midi_record_play_t * p , unsigned cur_time_us )
{
if ( cur_time_us > = p - > halfPedalNextUs )
{
unsigned midi_ch = 0 ;
switch ( p - > halfPedalState )
{
case kWaitForBegin :
printf ( " down: %i %i \n " , cur_time_us / 1000 , p - > halfPedalMidiPedalVel ) ;
_transmit_ctl ( p , midi_ch , midi : : kSustainCtlMdId , p - > halfPedalMidiPedalVel , cur_time_us ) ;
p - > halfPedalState = kWaitForNoteOn ;
p - > halfPedalNextUs + = p - > halfPedalNoteDelayUs ;
break ;
case kWaitForNoteOn :
printf ( " note: %i \n " , cur_time_us / 1000 ) ;
_transmit_note ( p , midi_ch , p - > halfPedalMidiPitch , p - > halfPedalMidiNoteVel , cur_time_us ) ;
p - > halfPedalNextUs + = p - > halfPedalNoteDurUs ;
p - > halfPedalState = kWaitForNoteOff ;
break ;
case kWaitForNoteOff :
printf ( " off: %i \n " , cur_time_us / 1000 ) ;
_transmit_note ( p , midi_ch , p - > halfPedalMidiPitch , 0 , cur_time_us ) ;
p - > halfPedalNextUs + = p - > halfPedalUpDelayUs ;
p - > halfPedalState = kWaitForPedalUp ;
break ;
case kWaitForPedalUp :
printf ( " up: %i \n " , cur_time_us / 1000 ) ;
_transmit_ctl ( p , midi_ch , midi : : kSustainCtlMdId , 0 , cur_time_us ) ;
p - > halfPedalNextUs + = p - > halfPedalDownDelayUs ;
p - > halfPedalState = kWaitForPedalDown ;
break ;
case kWaitForPedalDown :
//printf("down: %i\n",cur_time_us/1000);
//_transmit_ctl( p, midi_ch, midi::kSustainCtlMdId, p->halfPedalMidiPedalVel, cur_time_us);
//_stop(p);
p - > halfPedalState = kHalfPedalDone ;
break ;
case kHalfPedalDone :
break ;
}
}
}
2021-11-01 14:13:48 +00:00
2022-03-20 14:27:46 +00:00
// Set the next location to store an incoming MIDI message
2021-05-10 12:37:29 +00:00
void _set_midi_msg_next_index ( midi_record_play_t * p , unsigned next_idx )
{
2022-03-20 14:27:46 +00:00
p - > iMsgArrayInIdx = next_idx ;
2023-05-25 20:02:51 +00:00
if ( next_idx = = 0 )
p - > lastStoreIndex = kInvalidIdx ;
2021-05-10 12:37:29 +00:00
}
2022-03-20 14:27:46 +00:00
// Set the next index of the next MIDI message to transmit
2021-05-10 12:37:29 +00:00
void _set_midi_msg_next_play_index ( midi_record_play_t * p , unsigned next_idx )
{
p - > msgArrayOutIdx = next_idx ;
}
2021-08-15 20:02:45 +00:00
2022-09-10 12:54:17 +00:00
cw : : rc_t _am_file_read_version_0 ( const char * fn , file : : handle_t fH , am_midi_msg_t * amMsgArray , unsigned msgN )
{
// version 0 record type
typedef struct msg_str
{
unsigned dev_idx ;
unsigned port_idx ;
time : : spec_t timestamp ;
uint8_t ch ;
uint8_t st ;
uint8_t d0 ;
uint8_t d1 ;
unsigned microsecs ;
} zero_msg_t ;
cw : : rc_t rc = kOkRC ;
zero_msg_t * zMsgArray = mem : : allocZ < zero_msg_t > ( msgN ) ;
unsigned fileByteN = msgN * sizeof ( zero_msg_t ) ;
if ( ( rc = file : : read ( fH , zMsgArray , fileByteN ) ) ! = kOkRC )
{
rc = cwLogError ( kReadFailRC , " Data read failed on Audio-MIDI file: '%s'. " , fn ) ;
goto errLabel ;
}
for ( unsigned i = 0 ; i < msgN ; + + i )
{
am_midi_msg_t * am = amMsgArray + i ;
zero_msg_t * zm = zMsgArray + i ;
am - > devIdx = zm - > dev_idx ;
am - > portIdx = zm - > port_idx ;
am - > microsec = zm - > microsecs ;
am - > id = i ;
am - > timestamp = zm - > timestamp ;
am - > loc = i ;
am - > ch = zm - > ch ;
am - > status = zm - > st ;
am - > d0 = zm - > d0 ;
am - > d1 = zm - > d1 ;
}
errLabel :
mem : : release ( zMsgArray ) ;
return rc ;
}
2021-08-15 20:02:45 +00:00
// Read the am_midi_msg_t records from a file written by _midi_write()
// If msgArrayCntRef==0 and msgArrayRef==NULL then an array will be allocated and it is up
// to the caller to release it, otherwise the msgArrayCntRef should be set to the count
// of available records in msgArrayRef[]. Note the if there are more records in the file
// than there are record in msgArrayRef[] then a warning will be issued and only
// msgArrayCntRef records will be returned.
cw : : rc_t _am_file_read ( const char * fn , unsigned & msgArrayCntRef , am_midi_msg_t * & msgArrayRef )
{
2022-09-10 12:54:17 +00:00
rc_t rc = kOkRC ;
unsigned recordN = 0 ; // count of records in the ifle
unsigned fileByteN = 0 ; // count of bytes in the file
int version = 0 ; // version id (always a negative number)
bool alloc_fl = false ;
2022-11-11 16:52:59 +00:00
bool print_fl = false ;
2021-08-15 20:02:45 +00:00
file : : handle_t fH ;
2022-09-10 12:54:17 +00:00
2021-08-15 20:02:45 +00:00
if ( ( rc = file : : open ( fH , fn , file : : kReadFl ) ) ! = kOkRC )
{
rc = cwLogError ( kOpenFailRC , " Unable to locate the AM file: '%s'. " , fn ) ;
goto errLabel ;
}
2022-09-10 12:54:17 +00:00
// read the first word - which is either a version id or the count of records in the file
// if this is a version 0 file
if ( ( rc = file : : read ( fH , version ) ) ! = kOkRC )
2021-08-15 20:02:45 +00:00
{
2022-09-10 12:54:17 +00:00
rc = cwLogError ( kReadFailRC , " Version read failed on Audio-MIDI file: '%s'. " , fn ) ;
2021-08-15 20:02:45 +00:00
goto errLabel ;
}
2022-09-10 12:54:17 +00:00
// if the version is greater than 0 then this is a version 0 file (and 'version' holds the record count.
if ( version > 0 )
{
recordN = ( unsigned ) version ;
version = 0 ;
}
else // otherwise the second word in the file holds the size of the file
{
if ( ( rc = file : : read ( fH , recordN ) ) ! = kOkRC )
{
rc = cwLogError ( kReadFailRC , " Header read failed on Audio-MIDI file: '%s'. " , fn ) ;
goto errLabel ;
}
}
// if the output msg array was not allocated - then allocate it here
2023-11-19 19:06:37 +00:00
if ( msgArrayCntRef = = 0 | | msgArrayRef = = nullptr )
{
alloc_fl = true ;
msgArrayRef = mem : : allocZ < am_midi_msg_t > ( recordN ) ;
}
else // if the msg array was allocated but is too small - then decrease the count of records to be read from the file
{
if ( recordN > msgArrayCntRef )
{
cwLogWarning ( " The count of message in Audio-MIDI file '%s' reduced from %i to %i. " , fn , recordN , msgArrayCntRef ) ;
recordN = msgArrayCntRef ;
}
}
2022-09-10 12:54:17 +00:00
if ( version = = 0 )
2021-08-15 20:02:45 +00:00
{
2022-09-10 12:54:17 +00:00
// read the version 0 file into a temporary buffer then translate to am_midi_msg_t records
if ( ( rc = _am_file_read_version_0 ( fn , fH , msgArrayRef , recordN ) ) ! = kOkRC )
goto errLabel ;
}
else
{
2022-11-11 16:52:59 +00:00
fileByteN = recordN * sizeof ( am_midi_msg_t ) ;
2022-09-10 12:54:17 +00:00
if ( ( rc = file : : read ( fH , msgArrayRef , fileByteN ) ) ! = kOkRC )
{
rc = cwLogError ( kReadFailRC , " Data read failed on Audio-MIDI file: '%s'. " , fn ) ;
goto errLabel ;
}
2021-08-15 20:02:45 +00:00
}
2022-09-10 12:54:17 +00:00
if ( print_fl )
{
for ( unsigned i = 0 ; i < recordN ; + + i )
{
am_midi_msg_t * m = msgArrayRef ;
double dt = time : : elapsedSecs ( m [ 0 ] . timestamp , m [ i ] . timestamp ) ;
printf ( " %4i %4i : %6.2f %2x %2x %2x %2x : %6.2f \n " , m [ i ] . devIdx , m [ i ] . portIdx , dt , m [ i ] . ch , m [ i ] . status , m [ i ] . d0 , m [ i ] . d1 , m [ i ] . microsec / ( 1000.0 * 1000.0 ) ) ;
}
}
msgArrayCntRef = recordN ;
2021-08-15 20:02:45 +00:00
errLabel :
2022-09-10 12:54:17 +00:00
if ( rc ! = kOkRC and alloc_fl )
{
mem : : release ( msgArrayRef ) ;
msgArrayRef = nullptr ;
msgArrayCntRef = 0 ;
}
2021-08-15 20:02:45 +00:00
return rc ;
}
2022-03-20 14:27:46 +00:00
// Fill the play buffer from a previously store AM file.
2021-05-10 12:37:29 +00:00
rc_t _midi_read ( midi_record_play_t * p , const char * fn )
{
rc_t rc = kOkRC ;
unsigned n = 0 ;
2022-09-10 12:54:17 +00:00
int version ;
2021-05-10 12:37:29 +00:00
file : : handle_t fH ;
2022-09-10 12:54:17 +00:00
2021-05-10 12:37:29 +00:00
if ( ( rc = file : : open ( fH , fn , file : : kReadFl ) ) ! = kOkRC )
{
rc = cwLogError ( kOpenFailRC , " Unable to locate the file: '%s'. " , fn ) ;
goto errLabel ;
}
2022-09-10 12:54:17 +00:00
if ( ( rc = file : : read ( fH , version ) ) ! = kOkRC )
{
rc = cwLogError ( kReadFailRC , " Version read failed on Audio-MIDI file: '%s'. " , fn ) ;
goto errLabel ;
}
2021-05-10 12:37:29 +00:00
if ( ( rc = file : : read ( fH , n ) ) ! = kOkRC )
{
rc = cwLogError ( kReadFailRC , " Header read failed on Audio-MIDI file: '%s'. " , fn ) ;
goto errLabel ;
}
if ( n > p - > msgArrayN )
{
cwLogWarning ( " The count of message in Audio-MIDI file '%s' reduced from %i to %i. " , fn , n , p - > msgArrayN ) ;
n = p - > msgArrayN ;
}
if ( ( rc = file : : read ( fH , p - > msgArray , n * sizeof ( am_midi_msg_t ) ) ) ! = kOkRC )
{
rc = cwLogError ( kReadFailRC , " Data read failed on Audio-MIDI file: '%s'. " , fn ) ;
goto errLabel ;
}
2022-03-20 14:27:46 +00:00
p - > msgArrayInIdx = n ;
2021-05-10 12:37:29 +00:00
cwLogInfo ( " Read %i from '%s'. " , n , fn ) ;
errLabel :
2022-03-20 14:27:46 +00:00
file : : close ( fH ) ;
2021-05-10 12:37:29 +00:00
return rc ;
}
2022-03-20 14:27:46 +00:00
// Write the record buffer to an AM file
2021-05-10 12:37:29 +00:00
rc_t _midi_write ( midi_record_play_t * p , const char * fn )
{
rc_t rc = kOkRC ;
file : : handle_t fH ;
2022-09-10 12:54:17 +00:00
// NOTE: version must be a small negative number to differentiate from file version that
// whose first word is the count of records in the file, rather than the version number
int version = - 1 ;
2022-03-20 14:27:46 +00:00
if ( p - > iMsgArrayInIdx = = 0 )
2021-05-10 12:37:29 +00:00
{
cwLogWarning ( " Nothing to write. " ) ;
return rc ;
}
// open the file
if ( ( rc = file : : open ( fH , fn , file : : kWriteFl ) ) ! = kOkRC )
{
rc = cwLogError ( kOpenFailRC , " Unable to create the file '%s'. " , cwStringNullGuard ( fn ) ) ;
goto errLabel ;
}
2022-09-10 12:54:17 +00:00
// write the file version
if ( ( rc = write ( fH , version ) ) ! = kOkRC )
{
rc = cwLogError ( kWriteFailRC , " Version write to '%s' failed. " , cwStringNullGuard ( fn ) ) ;
goto errLabel ;
}
2021-05-10 12:37:29 +00:00
// write the file header
2022-03-20 14:27:46 +00:00
if ( ( rc = write ( fH , p - > iMsgArrayInIdx ) ) ! = kOkRC )
2021-05-10 12:37:29 +00:00
{
rc = cwLogError ( kWriteFailRC , " Header write to '%s' failed. " , cwStringNullGuard ( fn ) ) ;
goto errLabel ;
}
// write the file data
2022-03-20 14:27:46 +00:00
if ( ( rc = write ( fH , p - > iMsgArray , sizeof ( am_midi_msg_t ) * p - > iMsgArrayInIdx ) ) ! = kOkRC )
2021-05-10 12:37:29 +00:00
{
rc = cwLogError ( kWriteFailRC , " Data write to '%s' failed. " , cwStringNullGuard ( fn ) ) ;
goto errLabel ;
}
2022-03-20 14:27:46 +00:00
errLabel :
2021-05-10 12:37:29 +00:00
file : : close ( fH ) ;
2022-03-20 14:27:46 +00:00
cwLogInfo ( " Saved %i events to '%s'. " , p - > iMsgArrayInIdx , fn ) ;
return rc ;
}
2023-11-19 19:06:37 +00:00
rc_t _write_msgs_to_csv ( const char * fn , const am_midi_msg_t * msgArray , unsigned msgArrayCnt )
2022-03-20 14:27:46 +00:00
{
2023-11-19 19:06:37 +00:00
rc_t rc = kOkRC ;
2022-03-20 14:27:46 +00:00
file : : handle_t fH ;
// open the file
if ( ( rc = file : : open ( fH , fn , file : : kWriteFl ) ) ! = kOkRC )
{
rc = cwLogError ( kOpenFailRC , " Unable to create the file '%s'. " , cwStringNullGuard ( fn ) ) ;
goto errLabel ;
}
2023-02-15 02:01:22 +00:00
file : : printf ( fH , " dev,port,microsec,id,sec,ch,status,sci_pitch,d0,d1 \n " ) ;
2022-03-20 14:27:46 +00:00
2023-11-19 19:06:37 +00:00
for ( unsigned i = 0 ; i < msgArrayCnt ; + + i )
2022-03-20 14:27:46 +00:00
{
2023-11-19 19:06:37 +00:00
const am_midi_msg_t * m = msgArray + i ;
2022-03-20 14:27:46 +00:00
2023-11-19 19:06:37 +00:00
double secs = time : : elapsedSecs ( msgArray [ 0 ] . timestamp , msgArray [ i ] . timestamp ) ;
2021-05-10 12:37:29 +00:00
2022-03-20 14:27:46 +00:00
char sciPitch [ midi : : kMidiSciPitchCharCnt + 1 ] ;
if ( m - > status = = midi : : kNoteOnMdId )
midi : : midiToSciPitch ( m - > d0 , sciPitch , midi : : kMidiSciPitchCharCnt ) ;
else
strcpy ( sciPitch , " " ) ;
if ( ( rc = file : : printf ( fH , " %3i,%3i,%8i,%3i,%8.4f,%2i,0x%2x,%5s,%3i,%3i \n " ,
m - > devIdx , m - > portIdx , m - > microsec , m - > id , secs ,
m - > ch , m - > status , sciPitch , m - > d0 , m - > d1 ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " Write failed on line:%i " , i + 1 ) ;
goto errLabel ;
}
}
2023-05-25 20:02:51 +00:00
errLabel :
file : : close ( fH ) ;
2023-11-19 19:06:37 +00:00
return rc ;
}
rc_t _write_csv ( midi_record_play_t * p , const char * fn )
{
rc_t rc = kOkRC ;
if ( p - > iMsgArrayInIdx = = 0 )
{
cwLogWarning ( " Nothing to write. " ) ;
return rc ;
}
if ( ( rc = _write_msgs_to_csv ( fn , p - > iMsgArray , p - > iMsgArrayInIdx ) ) ! = kOkRC )
goto errLabel ;
2023-05-25 20:02:51 +00:00
2023-11-19 19:06:37 +00:00
cwLogInfo ( " Saved %i events to '%s'. " , p - > iMsgArrayInIdx , fn ) ;
errLabel :
if ( rc ! = kOkRC )
rc = cwLogError ( rc , " Write to '%s' failed. " , fn ) ;
2023-05-25 20:02:51 +00:00
return rc ;
}
rc_t _write_sync_csv ( midi_record_play_t * p , const char * fn )
{
rc_t rc = kOkRC ;
file : : handle_t fH ;
if ( p - > iMsgArrayInIdx = = 0 )
{
cwLogWarning ( " Nothing to write. " ) ;
return rc ;
}
// open the file
if ( ( rc = file : : open ( fH , fn , file : : kWriteFl ) ) ! = kOkRC )
{
rc = cwLogError ( kOpenFailRC , " Unable to create the file '%s'. " , cwStringNullGuard ( fn ) ) ;
goto errLabel ;
}
// 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 " ) ;
for ( unsigned i = 0 ; i < p - > iMsgArrayInIdx ; + + i )
{
const am_midi_msg_t * m = p - > iMsgArray + i ;
double secs = time : : elapsedSecs ( p - > iMsgArray [ 0 ] . timestamp , p - > iMsgArray [ i ] . timestamp ) ;
// write the event line
if ( midi : : isNoteOn ( m - > status , m - > d1 ) )
{
char sciPitch [ midi : : kMidiSciPitchCharCnt + 1 ] ;
unsigned bar = 0 ;
midi : : midiToSciPitch ( m - > d0 , sciPitch , midi : : kMidiSciPitchCharCnt ) ;
rc = file : : printf ( fH , " %i,%i,%i,%i,0,%f,0.0,0.0,0,%s,,,%i,%i,%i,,,,, \n " ,
bar , i , 1 , m - > loc , secs , sciPitch , m - > status , m - > d0 , m - > d1 ) ;
}
else
{
rc = file : : printf ( fH , " ,%i,,,%i,%f,,,,,,,%i,%i,%i,,,,, \n " , i , 0 , secs , m - > status , m - > d0 , m - > d1 ) ;
}
if ( rc ! = kOkRC )
{
rc = cwLogError ( rc , " Write failed on line:%i " , i + 1 ) ;
goto errLabel ;
}
}
2021-05-10 12:37:29 +00:00
errLabel :
2022-03-20 14:27:46 +00:00
file : : close ( fH ) ;
cwLogInfo ( " Saved %i events to '%s'. " , p - > iMsgArrayInIdx , fn ) ;
2021-05-10 12:37:29 +00:00
return rc ;
}
2023-05-25 20:02:51 +00:00
2023-11-26 20:23:36 +00:00
rc_t _write_midi_meta_file ( const char * meta_fn , const char * player_name , const char * take_label , unsigned sess_numb , unsigned take_numb , unsigned beg_loc , unsigned end_loc )
2023-11-19 19:06:37 +00:00
{
rc_t rc = kOkRC ;
const unsigned bufCharN = 256 ;
char buf [ bufCharN + 1 ] ;
int bN ;
2023-11-26 20:23:36 +00:00
if ( ( bN = snprintf ( buf , bufCharN , " { player_name: \" %s \" , take_label: \" %s \" , session_number: %i, take_number: %i, beg_loc: %i, end_loc: %i, skip_score_follow_fl: false } " , player_name , take_label , sess_numb , take_numb , beg_loc , end_loc ) ) = = 0 )
2023-11-19 19:06:37 +00:00
{
rc = cwLogError ( kOpFailRC , " The meta data buffer formation failed. " ) ;
goto errLabel ;
}
if ( ( rc = file : : fnWrite ( meta_fn , buf , bN ) ) ! = kOkRC )
goto errLabel ;
errLabel :
if ( rc ! = kOkRC )
rc = cwLogError ( rc , " MIDI meta file write failed on '%s'. " , cwStringNullGuard ( meta_fn ) ) ;
return rc ;
}
2021-05-10 12:37:29 +00:00
2021-08-15 20:02:45 +00:00
rc_t _midi_file_write ( const char * fn , const am_midi_msg_t * msgArray , unsigned msgArrayCnt )
{
rc_t rc = kOkRC ;
const unsigned midiFileTrackCnt = 1 ;
const unsigned midiFileTicksPerQN = 192 ;
const unsigned midiFileTempoBpm = 120 ;
const unsigned midiFileTrkIdx = 0 ;
file : : handle_t fH ;
midi : : file : : handle_t mfH ;
time : : spec_t t0 ;
if ( msgArrayCnt = = 0 )
{
cwLogWarning ( " Nothing to write. " ) ;
return rc ;
}
if ( ( rc = midi : : file : : create ( mfH , midiFileTrackCnt , midiFileTicksPerQN ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " MIDI file create failed. File:'%s' " , cwStringNullGuard ( fn ) ) ;
goto errLabel ;
}
if ( ( rc = midi : : file : : insertTrackTempoMsg ( mfH , midiFileTrkIdx , 0 , midiFileTempoBpm ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " MIDI file tempo message insert failed. File:'%s' " , cwStringNullGuard ( fn ) ) ;
goto errLabel ;
}
t0 = msgArray [ 0 ] . timestamp ;
for ( unsigned i = 0 ; i < msgArrayCnt ; + + i )
{
double secs = time : : elapsedMicros ( t0 , msgArray [ i ] . timestamp ) / 1000000.0 ;
unsigned atick = secs * midiFileTicksPerQN * midiFileTempoBpm / 60.0 ;
if ( ( rc = insertTrackChMsg ( mfH , midiFileTrkIdx , atick , msgArray [ i ] . ch + msgArray [ i ] . status , msgArray [ i ] . d0 , msgArray [ i ] . d1 ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " MIDI file message insert failed. File: '%s'. " , cwStringNullGuard ( fn ) ) ;
goto errLabel ;
}
}
if ( ( rc = midi : : file : : write ( mfH , fn ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " MIDI file write failed on '%s'. " , cwStringNullGuard ( fn ) ) ;
goto errLabel ;
}
if ( ( rc = midi : : file : : close ( mfH ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " MIDI file close failed on '%s'. " , cwStringNullGuard ( fn ) ) ;
goto errLabel ;
}
errLabel :
return rc ;
}
2021-05-10 12:37:29 +00:00
void _print_midi_msg ( const am_midi_msg_t * mm )
{
2022-12-22 20:52:49 +00:00
printf ( " %i %i : %10i : %2i 0x%02x 0x%02x 0x%02x %i \n " , mm - > devIdx , mm - > portIdx , mm - > microsec , mm - > ch , mm - > status , mm - > d0 , mm - > d1 , mm - > chordNoteCnt ) ;
2021-05-10 12:37:29 +00:00
}
void _report_midi ( midi_record_play_t * p )
{
2023-03-20 12:54:11 +00:00
for ( unsigned i = 0 ; i < p - > midiDevN ; + + i )
{
printf ( " vel table: %s:%s \n " , cwStringNullGuard ( p - > midiDevA [ i ] . midiOutDevLabel ) , cwStringNullGuard ( p - > midiDevA [ i ] . midiOutPortLabel ) ) ;
for ( unsigned j = 0 ; j < p - > midiDevA [ i ] . velTableN ; + + j )
printf ( " %i " , ( unsigned ) p - > midiDevA [ i ] . velTableArray [ j ] ) ;
printf ( " \n " ) ;
}
2022-10-15 19:07:06 +00:00
printf ( " omsg cnt:%i \n " , p - > msgArrayInIdx ) ;
2021-05-10 12:37:29 +00:00
for ( unsigned i = 0 ; i < p - > msgArrayInIdx ; + + i )
{
am_midi_msg_t * mm = p - > msgArray + i ;
_print_midi_msg ( mm ) ;
}
2022-10-15 19:07:06 +00:00
printf ( " imsg cnt:%i \n " , p - > iMsgArrayInIdx ) ;
for ( unsigned i = 0 ; i < p - > iMsgArrayInIdx ; + + i )
{
am_midi_msg_t * mm = p - > iMsgArray + i ;
_print_midi_msg ( mm ) ;
}
2021-05-10 12:37:29 +00:00
}
2022-10-15 13:26:35 +00:00
rc_t _write_vel_histogram ( midi_record_play_t * p )
{
const char * fname = " /home/kevin/temp/vel_histogram.txt " ;
rc_t rc = kOkRC ;
if ( ! p - > velHistogramEnableFl )
return rc ;
file : : handle_t h ;
if ( ( rc = file : : open ( h , fname , file : : kWriteFl ) ) ! = kOkRC )
return rc ;
for ( unsigned i = 0 ; i < p - > midiDevN ; + + i )
if ( p - > midiDevA [ i ] . enableFl )
{
for ( unsigned j = 0 ; j < midi : : kMidiVelCnt ; + + j )
if ( ( rc = file : : printf ( h , " %i, " , p - > midiDevA [ i ] . velHistogram [ j ] ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " Histogram output file (%s) write failed. " , fname ) ;
goto errLabel ;
}
file : : printf ( h , " \n " ) ;
}
errLabel :
file : : close ( h ) ;
return rc ;
}
2022-10-15 19:07:06 +00:00
// Fill the play buffer (msgArray) from the record buffer (iMsgArray)
void _iMsgArray_to_msgArray ( midi_record_play_t * p )
{
if ( p - > msgArrayN < p - > iMsgArrayN )
{
mem : : resize ( p - > msgArray , p - > iMsgArrayN , mem : : kZeroAllFl ) ;
p - > msgArrayN = p - > iMsgArrayN ;
}
p - > msgArrayOutIdx = 0 ;
p - > msgArrayInIdx = p - > iMsgArrayInIdx ;
memcpy ( p - > msgArray , p - > iMsgArray , p - > iMsgArrayInIdx * sizeof ( am_midi_msg_t ) ) ;
}
2021-05-10 12:37:29 +00:00
rc_t _stop ( midi_record_play_t * p )
{
rc_t rc = kOkRC ;
2023-01-21 16:38:24 +00:00
if ( p - > startedFl )
2021-05-10 12:37:29 +00:00
{
2023-01-21 16:38:24 +00:00
p - > startedFl = false ;
time : : spec_t t1 ;
time : : get ( t1 ) ;
2021-05-10 12:37:29 +00:00
2023-01-21 16:38:24 +00:00
_write_vel_histogram ( p ) ;
// if we were recording
if ( p - > recordFl )
2021-05-10 12:37:29 +00:00
{
2022-10-15 19:07:06 +00:00
2023-01-21 16:38:24 +00:00
// set the 'microsec' value for each MIDI msg as an offset from the first message[]
for ( unsigned i = 0 ; i < p - > iMsgArrayInIdx ; + + i )
{
p - > iMsgArray [ i ] . microsec = time : : elapsedMicros ( p - > iMsgArray [ 0 ] . timestamp , p - > iMsgArray [ i ] . timestamp ) ;
}
// copy the recorded messages from the input buffer to the output buffer
_iMsgArray_to_msgArray ( p ) ;
2022-10-15 19:07:06 +00:00
2023-01-21 16:38:24 +00:00
cwLogInfo ( " MIDI messages recorded: %i " , p - > msgArrayInIdx ) ;
2021-05-10 12:37:29 +00:00
2023-01-21 16:38:24 +00:00
}
else
2023-06-27 21:44:23 +00:00
{
2023-01-21 16:38:24 +00:00
io : : timerStop ( p - > ioH , io : : timerIdToIndex ( p - > ioH , kMidiRecordPlayTimerId ) ) ;
2021-05-10 12:37:29 +00:00
2023-06-27 21:44:23 +00:00
2023-01-21 16:38:24 +00:00
// TODO:
// BUG BUG BUG: should work for all channels
2021-05-10 12:37:29 +00:00
2023-01-21 16:38:24 +00:00
// all notes off
_transmit_ctl ( p , 0 , 121 , 0 , 0 ) ; // reset all controllers
_transmit_ctl ( p , 0 , 123 , 0 , 0 ) ; // all notes off
_transmit_ctl ( p , 0 , 0 , 0 , 0 ) ; // switch to bank 0
2021-11-01 14:13:48 +00:00
2023-01-21 16:38:24 +00:00
}
2022-05-21 13:26:23 +00:00
2023-01-21 16:38:24 +00:00
_midi_state_clear ( p ) ;
2023-01-14 18:38:18 +00:00
2023-01-21 16:38:24 +00:00
if ( p - > cb ! = nullptr )
2023-11-26 20:23:36 +00:00
p - > cb ( p - > cb_arg , kPlayerStoppedActionId , kInvalidId , t1 , kInvalidId , nullptr , 0 , 0 , 0 , 0 ) ;
2023-01-21 16:38:24 +00:00
}
2023-01-14 18:38:18 +00:00
2021-05-10 12:37:29 +00:00
return rc ;
}
2022-03-20 14:27:46 +00:00
2021-11-01 14:13:48 +00:00
rc_t _midi_receive ( midi_record_play_t * p , const io : : midi_msg_t & m )
2021-05-10 12:37:29 +00:00
{
rc_t rc = kOkRC ;
const midi : : packet_t * pkt = m . pkt ;
// for each midi msg
for ( unsigned j = 0 ; j < pkt - > msgCnt ; + + j )
{
// if this is a sys-ex msg
if ( pkt - > msgArray = = NULL )
{
2022-10-15 19:07:06 +00:00
cwLogError ( kNotImplementedRC , " Sys-ex recording not implemented. " ) ;
2021-05-10 12:37:29 +00:00
}
else // this is a triple
{
2021-11-01 14:13:48 +00:00
//if( !midi::isPedal(pkt->msgArray[j].status,pkt->msgArray[j].d0) )
2023-05-21 16:40:23 +00:00
// printf("IN: st:%i rec:%i : 0x%x 0x%x 0x%x\n", p->startedFl, p->recordFl, pkt->msgArray[j].status, pkt->msgArray[j].d0, pkt->msgArray[j].d1 );
2021-11-01 14:13:48 +00:00
2022-03-20 14:27:46 +00:00
if ( ( p - > recordFl | | p - > logInFl ) & & p - > startedFl )
2021-05-10 12:37:29 +00:00
{
// verify that space exists in the record buffer
2022-03-20 14:27:46 +00:00
if ( p - > iMsgArrayInIdx > = p - > iMsgArrayN )
2021-05-10 12:37:29 +00:00
{
_stop ( p ) ;
2022-03-20 14:27:46 +00:00
rc = cwLogError ( kBufTooSmallRC , " MIDI message record buffer is full. % messages. " , p - > iMsgArrayN ) ;
2021-05-10 12:37:29 +00:00
goto errLabel ;
}
else
{
// copy the msg into the record buffer
midi : : msg_t * mm = pkt - > msgArray + j ;
if ( midi : : isChStatus ( mm - > status ) )
{
2023-05-25 20:02:51 +00:00
//printf("R:%i %i %i\n",mm->status, mm->d0, mm->d1);
2024-02-14 15:56:44 +00:00
const am_midi_msg_t * am = _midi_store ( p , pkt - > devIdx , pkt - > portIdx , mm - > timeStamp , mm - > ch , mm - > status , mm - > d0 , mm - > d1 ) ;
2021-11-01 14:13:48 +00:00
2022-03-20 14:27:46 +00:00
if ( p - > thruFl & & am ! = nullptr )
_transmit_msg ( p , am , false ) ;
2021-05-10 12:37:29 +00:00
}
}
}
}
}
errLabel :
return rc ;
}
2022-03-20 14:27:46 +00:00
2021-05-10 12:37:29 +00:00
rc_t _timer_callback ( midi_record_play_t * p , io : : timer_msg_t & m )
2023-01-21 16:38:24 +00:00
{
rc_t rc = kOkRC ;
2021-05-10 12:37:29 +00:00
2023-01-21 16:38:24 +00:00
// if the MIDI player is started and in 'play' mode and msg remain to be played
if ( p - > startedFl & & ( p - > recordFl = = false ) & & ( p - > msgArrayOutIdx < p - > msgArrayInIdx ) )
{
time : : spec_t t ;
time : : get ( t ) ;
2021-05-10 12:37:29 +00:00
2023-01-21 16:38:24 +00:00
unsigned cur_time_us = time : : elapsedMicros ( p - > play_time , t ) ;
2021-05-10 12:37:29 +00:00
2023-01-21 16:38:24 +00:00
if ( p - > halfPedalFl )
_half_pedal_update ( p , cur_time_us ) ;
else
while ( p - > msgArray [ p - > msgArrayOutIdx ] . microsec < = cur_time_us )
{
2023-05-01 01:20:25 +00:00
2023-01-21 16:38:24 +00:00
am_midi_msg_t * mm = p - > msgArray + p - > msgArrayOutIdx ;
2021-05-10 12:37:29 +00:00
2023-01-21 16:38:24 +00:00
//_print_midi_msg(mm);
2021-05-10 12:37:29 +00:00
2023-01-21 16:38:24 +00:00
_transmit_msg ( p , mm ) ;
2022-03-20 14:27:46 +00:00
2023-01-21 16:38:24 +00:00
_set_midi_msg_next_play_index ( p , p - > msgArrayOutIdx + 1 ) ;
2021-11-01 14:13:48 +00:00
2023-01-21 16:38:24 +00:00
// if all MIDI messages have been played
if ( p - > msgArrayOutIdx > = p - > msgArrayInIdx )
{
_stop ( p ) ;
break ;
2021-08-15 20:02:45 +00:00
}
2023-01-21 16:38:24 +00:00
}
2023-03-20 12:54:11 +00:00
2023-06-27 21:44:23 +00:00
if ( p - > msgArrayOutIdx < p - > msgArrayInIdx )
2023-03-20 12:54:11 +00:00
{
2023-06-27 21:44:23 +00:00
// the next callback should happen at now() + (next_msg_us - cur_us)
time : : advanceMicros ( t , p - > msgArray [ p - > msgArrayOutIdx ] . microsec - cur_time_us ) ;
io : : timerSetNextTime ( p - > ioH , p - > midi_timer_index , t ) ;
2023-03-20 12:54:11 +00:00
}
}
2023-06-27 21:44:23 +00:00
2023-03-20 12:54:11 +00:00
return rc ;
}
2023-06-27 21:44:23 +00:00
2023-11-19 20:01:32 +00:00
rc_t _get_take_number ( const char * dir , unsigned & take_numb_ref )
{
rc_t rc = kOkRC ;
take_numb_ref = - 1 ;
const char * suffix ;
if ( ( suffix = lastMatchChar ( dir , ' _ ' ) ) = = nullptr )
{
rc = kSyntaxErrorRC ;
goto errLabel ;
}
if ( sscanf ( suffix + 1 , " %i " , & take_numb_ref ) ! = 1 )
{
rc = kSyntaxErrorRC ;
goto errLabel ;
}
errLabel :
if ( rc ! = kOkRC )
rc = cwLogError ( rc , " The suffix of the directory name containing the 'am' file is not an underscore separated integer. (%s). " , cwStringNullGuard ( dir ) ) ;
return rc ;
}
2021-05-10 12:37:29 +00:00
}
}
2023-06-27 21:44:23 +00:00
2023-03-20 12:54:11 +00:00
cw : : rc_t cw : : midi_record_play : : create ( handle_t & hRef ,
io : : handle_t ioH ,
const object_t & cfg ,
const char * velTableFname ,
event_callback_t cb ,
void * cb_arg )
2021-05-10 12:37:29 +00:00
{
2022-12-12 17:21:45 +00:00
bool asyncFl = true ;
2021-05-10 12:37:29 +00:00
midi_record_play_t * p = nullptr ;
rc_t rc ;
2023-03-20 12:54:11 +00:00
object_t * velTblCfg = nullptr ;
2021-05-10 12:37:29 +00:00
if ( ( rc = destroy ( hRef ) ) ! = kOkRC )
return rc ;
p = mem : : allocZ < midi_record_play_t > ( ) ;
2023-01-31 00:23:42 +00:00
p - > ioH = ioH ; // p->ioH is used in _destory() so initialize it here
2023-03-20 12:54:11 +00:00
// parse the cfg
2021-05-10 12:37:29 +00:00
if ( ( rc = _parseCfg ( p , cfg ) ) ! = kOkRC )
goto errLabel ;
2023-06-27 21:44:23 +00:00
p - > midi_timer_index = kInvalidIdx ;
2021-11-01 14:13:48 +00:00
p - > cb = cb ;
p - > cb_arg = cb_arg ;
2022-03-20 14:27:46 +00:00
p - > halfPedalState = kHalfPedalDone ;
p - > halfPedalNextUs = 0 ;
p - > halfPedalNoteDelayUs = 100 * 1000 ;
p - > halfPedalNoteDurUs = 1000 * 1000 ;
p - > halfPedalUpDelayUs = 1000 * 1000 ;
p - > halfPedalDownDelayUs = 1000 * 1000 ;
p - > halfPedalMidiPitch = 64 ;
p - > halfPedalMidiNoteVel = 64 ;
p - > halfPedalMidiPedalVel = 127 ;
2022-12-22 20:52:49 +00:00
p - > velHistogramEnableFl = true ;
2022-01-22 14:42:21 +00:00
2022-05-14 16:37:11 +00:00
for ( unsigned i = 0 ; i < p - > midiDevN ; + + i )
2021-05-10 12:37:29 +00:00
{
2022-01-22 14:42:21 +00:00
midi_device_t * dev = p - > midiDevA + i ;
2022-05-14 16:37:11 +00:00
if ( ! p - > midiDevA [ i ] . enableFl )
continue ;
2022-01-22 14:42:21 +00:00
if ( ( dev - > midiOutDevIdx = io : : midiDeviceIndex ( p - > ioH , dev - > midiOutDevLabel ) ) = = kInvalidIdx )
{
rc = cwLogError ( kInvalidArgRC , " The MIDI output device: '%s' was not found. " , cwStringNullGuard ( dev - > midiOutDevLabel ) ) ;
goto errLabel ;
}
if ( ( dev - > midiOutPortIdx = io : : midiDevicePortIndex ( p - > ioH , dev - > midiOutDevIdx , false , dev - > midiOutPortLabel ) ) = = kInvalidIdx )
{
rc = cwLogError ( kInvalidArgRC , " The MIDI output port: '%s' was not found. " , cwStringNullGuard ( dev - > midiOutPortLabel ) ) ;
goto errLabel ;
}
printf ( " %s %s : %i %i \n " , dev - > midiOutDevLabel , dev - > midiOutPortLabel , dev - > midiOutDevIdx , dev - > midiOutPortIdx ) ;
2021-05-10 12:37:29 +00:00
}
2023-06-27 21:44:23 +00:00
2021-05-10 12:37:29 +00:00
// create the MIDI playback timer
2022-12-12 17:21:45 +00:00
if ( ( rc = timerCreate ( p - > ioH , TIMER_LABEL , kMidiRecordPlayTimerId , p - > midi_timer_period_micro_sec , asyncFl ) ) ! = kOkRC )
2021-05-10 12:37:29 +00:00
{
cwLogError ( rc , " Audio-MIDI timer create failed. " ) ;
goto errLabel ;
}
2023-06-27 21:44:23 +00:00
if ( ( p - > midi_timer_index = io : : timerLabelToIndex ( p - > ioH , TIMER_LABEL ) ) = = kInvalidIdx )
{
cwLogError ( rc , " Audio-MIDI timer index access failed. " ) ;
goto errLabel ;
}
2023-01-14 18:38:18 +00:00
_midi_state_clear ( p ) ;
2021-05-10 12:37:29 +00:00
errLabel :
if ( rc = = kOkRC )
hRef . set ( p ) ;
else
_destroy ( p ) ;
2023-03-20 12:54:11 +00:00
if ( velTblCfg ! = nullptr )
velTblCfg - > free ( ) ;
2021-05-10 12:37:29 +00:00
return rc ;
}
cw : : rc_t cw : : midi_record_play : : destroy ( handle_t & hRef )
{
rc_t rc = kOkRC ;
if ( ! hRef . isValid ( ) )
return kOkRC ;
midi_record_play_t * p = _handleToPtr ( hRef ) ;
if ( ( rc = _destroy ( p ) ) ! = kOkRC )
return rc ;
hRef . clear ( ) ;
return rc ;
}
2021-11-14 16:52:24 +00:00
cw : : rc_t cw : : midi_record_play : : start ( handle_t h , bool rewindFl , const time : : spec_t * end_play_event_timestamp )
2021-05-10 12:37:29 +00:00
{
midi_record_play_t * p = _handleToPtr ( h ) ;
p - > startedFl = true ;
2022-10-15 13:26:35 +00:00
if ( p - > velHistogramEnableFl )
for ( unsigned i = 0 ; i < p - > midiDevN ; + + i )
if ( p - > midiDevA [ i ] . enableFl )
memset ( p - > midiDevA [ i ] . velHistogram , 0 , sizeof ( p - > midiDevA [ i ] . velHistogram ) ) ;
2021-11-14 16:52:24 +00:00
// set the end play time
if ( end_play_event_timestamp = = nullptr or time : : isZero ( * end_play_event_timestamp ) )
time : : setZero ( p - > end_play_event_timestamp ) ;
else
2022-05-14 14:22:29 +00:00
{
2021-11-14 16:52:24 +00:00
p - > end_play_event_timestamp = * end_play_event_timestamp ;
2023-04-11 12:36:04 +00:00
//p->all_off_timestamp = *end_play_event_timestamp;
//time::advanceMs( p->all_off_timestamp, p->all_off_delay_ms); }
2023-05-01 01:20:25 +00:00
}
2021-11-14 16:52:24 +00:00
2021-05-10 12:37:29 +00:00
time : : get ( p - > start_time ) ;
2022-03-20 14:27:46 +00:00
if ( p - > recordFl | | p - > logInFl or p - > logOutFl )
2021-05-10 12:37:29 +00:00
{
_set_midi_msg_next_index ( p , 0 ) ;
}
2022-03-20 14:27:46 +00:00
if ( ! p - > recordFl )
2021-05-10 12:37:29 +00:00
{
time : : get ( p - > play_time ) ;
2021-11-01 14:13:48 +00:00
if ( rewindFl )
_set_midi_msg_next_play_index ( p , 0 ) ;
else
{
2023-06-27 21:44:23 +00:00
io : : timerSetNextTime ( p - > ioH , p - > midi_timer_index , p - > play_time ) ;
2021-11-01 14:13:48 +00:00
// Set the begin play time back by the time offset of the current output event.
// This will cause that event to be played back immediately.
time : : subtractMicros ( p - > play_time , p - > msgArray [ p - > msgArrayOutIdx ] . microsec ) ;
2023-06-27 21:44:23 +00:00
2021-11-01 14:13:48 +00:00
}
2022-03-20 14:27:46 +00:00
if ( p - > halfPedalFl )
{
p - > halfPedalNextUs = 0 ;
p - > halfPedalState = kWaitForBegin ;
}
2023-04-11 12:36:04 +00:00
2021-11-01 14:13:48 +00:00
io : : timerStart ( p - > ioH , io : : timerIdToIndex ( p - > ioH , kMidiRecordPlayTimerId ) ) ;
2021-05-10 12:37:29 +00:00
}
2023-04-11 12:36:04 +00:00
2021-05-10 12:37:29 +00:00
return kOkRC ;
}
cw : : rc_t cw : : midi_record_play : : stop ( handle_t h )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return _stop ( p ) ;
}
bool cw : : midi_record_play : : is_started ( handle_t h )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return p - > startedFl ;
}
cw : : rc_t cw : : midi_record_play : : clear ( handle_t h )
{
rc_t rc = kOkRC ;
midi_record_play_t * p = _handleToPtr ( h ) ;
_set_midi_msg_next_index ( p , 0 ) ;
return rc ;
}
cw : : rc_t cw : : midi_record_play : : set_record_state ( handle_t h , bool record_fl )
{
rc_t rc = kOkRC ;
midi_record_play_t * p = _handleToPtr ( h ) ;
p - > recordFl = record_fl ;
return rc ;
}
bool cw : : midi_record_play : : record_state ( handle_t h )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return p - > recordFl ;
}
2022-11-11 19:04:44 +00:00
cw : : rc_t cw : : midi_record_play : : set_mute_state ( handle_t h , bool mute_fl )
{
rc_t rc = kOkRC ;
midi_record_play_t * p = _handleToPtr ( h ) ;
p - > muteFl = mute_fl ;
return rc ;
}
bool cw : : midi_record_play : : mute_state ( handle_t h )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return p - > muteFl ;
}
2021-05-10 12:37:29 +00:00
cw : : rc_t cw : : midi_record_play : : set_thru_state ( handle_t h , bool thru_fl )
{
rc_t rc = kOkRC ;
midi_record_play_t * p = _handleToPtr ( h ) ;
p - > thruFl = thru_fl ;
return rc ;
}
bool cw : : midi_record_play : : thru_state ( handle_t h )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return p - > thruFl ;
}
cw : : rc_t cw : : midi_record_play : : save ( handle_t h , const char * fn )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return _midi_write ( p , fn ) ;
}
2022-03-20 14:27:46 +00:00
cw : : rc_t cw : : midi_record_play : : save_csv ( handle_t h , const char * fn )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return _write_csv ( p , fn ) ;
}
2023-05-25 20:02:51 +00:00
cw : : rc_t cw : : midi_record_play : : save_synced_csv ( handle_t h , const char * fn , const score_follower : : ssf_note_on_t * syncA , unsigned syncN )
{
rc_t rc = kOkRC ;
midi_record_play_t * p = _handleToPtr ( h ) ;
for ( unsigned i = 0 ; i < p - > iMsgArrayInIdx ; + + i )
{
const am_midi_msg_t * m = p - > iMsgArray + i ;
if ( midi : : isNoteOn ( m - > status , m - > d1 ) )
{
for ( unsigned j = 0 ; j < syncN ; + + j )
if ( syncA [ j ] . muid = = p - > iMsgArray [ i ] . id )
{
if ( p - > iMsgArray [ i ] . d0 ! = syncA [ j ] . pitch )
{
rc = cwLogError ( kInvalidStateRC , " MIDI sync failed. Pitch mismatch. i:%i j:%i : %i %i " , i , j , p - > iMsgArray [ i ] . d0 ! = syncA [ j ] . pitch ) ;
//goto errLabel;
continue ;
}
p - > iMsgArray [ i ] . loc = syncA [ j ] . loc ;
}
}
}
if ( ( rc = _write_sync_csv ( p , fn ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " MIDI sync'd CSV write failed. " ) ;
goto errLabel ;
}
errLabel :
return rc ;
}
2023-05-21 16:40:23 +00:00
cw : : rc_t cw : : midi_record_play : : write_svg ( handle_t h , const char * fn )
{
rc_t rc ;
svg_midi : : handle_t svgH ;
midi_record_play_t * p = _handleToPtr ( h ) ;
if ( ( rc = svg_midi : : create ( svgH ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " svg_midi object create failed. " ) ;
goto errLabel ;
}
for ( unsigned i = 0 ; i < p - > iMsgArrayInIdx ; + + i )
{
const am_midi_msg_t * m = p - > iMsgArray + i ;
double sec = time : : specToSeconds ( m - > timestamp ) ;
if ( ( rc = svg_midi : : setMidiMsg ( svgH , sec , m - > id , m - > ch , m - > status , m - > d0 , m - > d1 ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " svg_midi set midi msg failed. " ) ;
goto errLabel ;
}
}
errLabel :
svg_midi : : destroy ( svgH ) ;
return rc ;
}
2021-05-10 12:37:29 +00:00
cw : : rc_t cw : : midi_record_play : : open ( handle_t h , const char * fn )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return _midi_read ( p , fn ) ;
}
2021-11-01 14:13:48 +00:00
cw : : rc_t cw : : midi_record_play : : load ( handle_t h , const midi_msg_t * msg , unsigned msg_count )
{
rc_t rc = kOkRC ;
midi_record_play_t * p = _handleToPtr ( h ) ;
2023-01-14 22:17:19 +00:00
unsigned extraMs = 0 ;
unsigned curMicros = 0 ;
2021-11-01 14:13:48 +00:00
if ( msg_count > p - > msgArrayN )
{
mem : : release ( p - > msgArray ) ;
p - > msgArray = mem : : allocZ < am_midi_msg_t > ( msg_count ) ;
p - > msgArrayN = msg_count ;
}
for ( unsigned i = 0 ; i < msg_count ; + + i )
{
2023-01-14 22:17:19 +00:00
if ( i > 0 )
{
unsigned deltaMicros = time : : elapsedMicros ( msg [ i - 1 ] . timestamp , msg [ i ] . timestamp ) ;
curMicros + = deltaMicros + ( extraMs * 1000 ) ;
}
2021-11-01 14:13:48 +00:00
p - > msgArray [ i ] . id = msg [ i ] . id ;
p - > msgArray [ i ] . timestamp = msg [ i ] . timestamp ;
2022-05-06 20:03:43 +00:00
p - > msgArray [ i ] . loc = msg [ i ] . loc ;
2023-11-26 20:23:36 +00:00
p - > msgArray [ i ] . arg = msg [ i ] . arg ;
2021-11-01 14:13:48 +00:00
p - > msgArray [ i ] . ch = msg [ i ] . ch ;
p - > msgArray [ i ] . status = msg [ i ] . status ;
p - > msgArray [ i ] . d0 = msg [ i ] . d0 ;
p - > msgArray [ i ] . d1 = msg [ i ] . d1 ;
2022-01-22 14:42:21 +00:00
p - > msgArray [ i ] . devIdx = kInvalidIdx ;
p - > msgArray [ i ] . portIdx = kInvalidIdx ;
2023-01-14 22:17:19 +00:00
//p->msgArray[i].microsec = time::elapsedMicros(p->msgArray[0].timestamp,p->msgArray[i].timestamp);
p - > msgArray [ i ] . microsec = curMicros ;
2022-03-20 14:27:46 +00:00
2023-01-14 22:17:19 +00:00
//hprintf("%i %i\n",curMicros,time::elapsedMicros(p->msgArray[0].timestamp,p->msgArray[i].timestamp) );
extraMs = 0 ;
if ( midi : : isSustainPedalUp ( msg [ i ] . status , msg [ i ] . d0 , msg [ i ] . d1 ) )
extraMs = p - > minDamperDownMs ;
2021-11-01 14:13:48 +00:00
}
p - > msgArrayInIdx = msg_count ;
p - > msgArrayOutIdx = 0 ;
2022-12-22 20:52:49 +00:00
_set_chord_note_count ( p ) ;
2021-11-01 14:13:48 +00:00
return rc ;
}
2023-06-27 21:44:23 +00:00
unsigned cw : : midi_record_play : : label_to_device_index ( handle_t h , const char * devLabel )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return _label_to_device_index ( p , devLabel ) ;
}
const char * cw : : midi_record_play : : device_index_to_label ( handle_t h , unsigned devIdx )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
2024-03-25 14:46:45 +00:00
return devIdx < p - > midiDevN ? p - > midiDevA [ devIdx ] . label : nullptr ;
2023-06-27 21:44:23 +00:00
}
2023-01-14 18:38:18 +00:00
cw : : rc_t cw : : midi_record_play : : seek ( handle_t h , time : : spec_t seek_timestamp )
{
rc_t rc = kOkRC ;
midi_record_play_t * p = _handleToPtr ( h ) ;
2023-01-21 16:38:24 +00:00
// clear the MIDI state
_midi_state_clear ( p ) ;
2023-01-14 18:38:18 +00:00
// supress MIDI transmission during the seek
2023-06-27 21:44:23 +00:00
p - > supressMidiXmitFl = true ;
2023-01-14 18:38:18 +00:00
// iterate throught the MIDI msg array
for ( unsigned i = 0 ; i < p - > msgArrayInIdx ; + + i )
{
am_midi_msg_t * mm = p - > msgArray + i ;
2021-11-01 14:13:48 +00:00
2023-06-27 21:44:23 +00:00
// if this msg is at or after the seek target
2023-01-14 18:38:18 +00:00
if ( time : : isLTE ( seek_timestamp , mm - > timestamp ) )
{
p - > msgArrayOutIdx = i ;
break ;
}
// transmit the msg so that the dev._midi_state is updated,
// but 'supressMidiXmitFl' is set so no MIDI will actually
// be transmitted
_transmit_msg ( p , mm , false ) ;
}
p - > supressMidiXmitFl = false ;
_midi_state_xmit_pedals ( p ) ;
return rc ;
}
2021-11-01 14:13:48 +00:00
2023-02-26 18:40:49 +00:00
unsigned cw : : midi_record_play : : elapsed_micros ( handle_t h )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
time : : spec_t t ;
time : : get ( t ) ;
return time : : elapsedMicros ( p - > start_time , t ) ;
}
2021-05-10 12:37:29 +00:00
unsigned cw : : midi_record_play : : event_count ( handle_t h )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return p - > msgArrayInIdx ;
}
unsigned cw : : midi_record_play : : event_index ( handle_t h )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
2022-03-20 14:27:46 +00:00
return p - > recordFl ? p - > iMsgArrayInIdx : p - > msgArrayOutIdx ;
2021-05-10 12:37:29 +00:00
}
2023-05-25 20:02:51 +00:00
unsigned cw : : midi_record_play : : last_store_index ( handle_t h )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return p - > recordFl ? p - > lastStoreIndex : kInvalidIdx ;
}
2022-05-06 20:03:43 +00:00
unsigned cw : : midi_record_play : : event_loc ( handle_t h )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
2024-03-25 14:46:45 +00:00
if ( ! p - > recordFl & & p - > msgArrayOutIdx < p - > msgArrayN )
2022-05-06 20:03:43 +00:00
return p - > msgArray [ p - > msgArrayOutIdx ] . loc ;
return kInvalidId ;
}
2021-05-10 12:37:29 +00:00
cw : : rc_t cw : : midi_record_play : : exec ( handle_t h , const io : : msg_t & m )
{
rc_t rc = kOkRC ;
midi_record_play_t * p = _handleToPtr ( h ) ;
switch ( m . tid )
{
2023-01-21 16:38:24 +00:00
case io : : kTimerTId :
if ( m . u . timer ! = nullptr )
rc = _timer_callback ( p , * m . u . timer ) ;
break ;
2021-05-10 12:37:29 +00:00
2023-01-21 16:38:24 +00:00
case io : : kMidiTId :
if ( m . u . midi ! = nullptr )
_midi_receive ( p , * m . u . midi ) ;
break ;
2021-05-10 12:37:29 +00:00
2023-01-21 16:38:24 +00:00
default :
rc = kOkRC ;
2021-05-10 12:37:29 +00:00
}
return rc ;
}
2022-01-22 14:42:21 +00:00
unsigned cw : : midi_record_play : : device_count ( handle_t h )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return p - > midiDevN ;
}
bool cw : : midi_record_play : : is_device_enabled ( handle_t h , unsigned devIdx )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
bool fl = false ;
if ( devIdx > = p - > midiDevN )
cwLogError ( kInvalidArgRC , " The MIDI record-play device index '%i' is invalid. " , devIdx ) ;
else
fl = p - > midiDevA [ devIdx ] . enableFl ;
return fl ;
}
2021-08-15 20:02:45 +00:00
2022-01-22 14:42:21 +00:00
void cw : : midi_record_play : : enable_device ( handle_t h , unsigned devIdx , bool enableFl )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
if ( devIdx > = p - > midiDevN )
cwLogError ( kInvalidArgRC , " The MIDI record-play device index '%i' is invalid. " , devIdx ) ;
else
{
p - > midiDevA [ devIdx ] . enableFl = enableFl ;
2023-01-21 16:38:24 +00:00
//printf("Enable: %i = %i\n",devIdx,enableFl);
2022-01-22 14:42:21 +00:00
}
}
2021-08-15 20:02:45 +00:00
2022-10-26 12:47:54 +00:00
cw : : rc_t cw : : midi_record_play : : send_midi_msg ( handle_t h , unsigned devIdx , uint8_t ch , uint8_t status , uint8_t d0 , uint8_t d1 )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
if ( devIdx > = p - > midiDevN )
return cwLogError ( kInvalidArgRC , " The MIDI record-play device index '%i' is invalid. " , devIdx ) ;
if ( p - > midiDevA [ devIdx ] . enableFl )
io : : midiDeviceSend ( p - > ioH , p - > midiDevA [ devIdx ] . midiOutDevIdx , p - > midiDevA [ devIdx ] . midiOutPortIdx , status + ch , d0 , d1 ) ;
return kOkRC ;
}
2022-03-20 14:27:46 +00:00
void cw : : midi_record_play : : half_pedal_params ( handle_t h , unsigned noteDelayMs , unsigned pitch , unsigned vel , unsigned pedal_vel , unsigned noteDurMs , unsigned downDelayMs )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
p - > halfPedalNoteDelayUs = noteDelayMs * 1000 ;
p - > halfPedalNoteDurUs = noteDurMs * 1000 ;
p - > halfPedalDownDelayUs = downDelayMs * 1000 ;
p - > halfPedalMidiPitch = pitch ;
p - > halfPedalMidiNoteVel = vel ;
p - > halfPedalMidiPedalVel = pedal_vel ;
}
2023-11-19 19:06:37 +00:00
cw : : rc_t cw : : midi_record_play : : am_to_midi_file ( const char * am_filename , const char * midi_filename , const char * csv_filename )
2021-08-15 20:02:45 +00:00
{
rc_t rc = kOkRC ;
unsigned msgArrayCnt = 0 ;
am_midi_msg_t * msgArray = nullptr ;
2022-10-01 23:00:33 +00:00
if ( ! filesys : : isFile ( am_filename ) )
2023-01-21 16:38:24 +00:00
{
cwLogError ( kOpenFailRC , " The AM file '%s' does not exist. " , am_filename ) ;
goto errLabel ;
}
2022-10-01 23:00:33 +00:00
2021-08-15 20:02:45 +00:00
if ( ( rc = _am_file_read ( am_filename , msgArrayCnt , msgArray ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " Unable to read AM file '%s'. " , cwStringNullGuard ( am_filename ) ) ;
goto errLabel ;
}
2023-11-19 19:06:37 +00:00
if ( midi_filename ! = nullptr )
if ( ( rc = _midi_file_write ( midi_filename , msgArray , msgArrayCnt ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " Unable to write AM file '%s' to '%s'. " , cwStringNullGuard ( am_filename ) , cwStringNullGuard ( midi_filename ) ) ;
goto errLabel ;
}
if ( csv_filename ! = nullptr )
if ( ( rc = _write_msgs_to_csv ( csv_filename , msgArray , msgArrayCnt ) ) ! = kOkRC )
{
rc = cwLogError ( rc , " Unable to write AM file '%s' to '%s'. " , cwStringNullGuard ( am_filename ) , cwStringNullGuard ( csv_filename ) ) ;
goto errLabel ;
}
2021-08-15 20:02:45 +00:00
errLabel :
mem : : release ( msgArray ) ;
return rc ;
}
2023-11-26 20:23:36 +00:00
cw : : rc_t cw : : midi_record_play : : am_to_midi_dir ( const char * player_name , const char * prefix_label , unsigned sess_numb , unsigned beg_loc , unsigned end_loc , const char * inDir )
2021-08-15 20:02:45 +00:00
{
rc_t rc = kOkRC ;
filesys : : dirEntry_t * dirEntryArray = nullptr ;
unsigned dirEntryCnt = 0 ;
2023-11-26 20:23:36 +00:00
char * take_label = nullptr ;
2023-11-19 20:55:57 +00:00
if ( ! filesys : : isDir ( inDir ) )
{
rc = cwLogError ( kInvalidArgRC , " The directory '%s' does not exist. " , cwStringNullGuard ( inDir ) ) ;
goto errLabel ;
}
2021-08-15 20:02:45 +00:00
2022-09-10 12:54:17 +00:00
if ( ( dirEntryArray = filesys : : dirEntries ( inDir , filesys : : kDirFsFl | filesys : : kFullPathFsFl , & dirEntryCnt ) ) = = nullptr )
2021-08-15 20:02:45 +00:00
goto errLabel ;
2022-09-10 12:54:17 +00:00
for ( unsigned i = 0 ; i < dirEntryCnt and rc = = kOkRC ; + + i )
2021-08-15 20:02:45 +00:00
{
2023-11-19 20:01:32 +00:00
unsigned take_numb ;
2023-11-26 20:23:36 +00:00
char * am_fn = filesys : : makeFn ( dirEntryArray [ i ] . name , " midi " , " am " , NULL ) ;
char * midi_fn = filesys : : makeFn ( dirEntryArray [ i ] . name , " midi " , " mid " , NULL ) ;
char * csv_fn = filesys : : makeFn ( dirEntryArray [ i ] . name , " midi " , " csv " , NULL ) ;
char * meta_fn = filesys : : makeFn ( dirEntryArray [ i ] . name , " meta " , " cfg " , NULL ) ;
2023-11-19 20:01:32 +00:00
if ( ( rc = _get_take_number ( dirEntryArray [ i ] . name , take_numb ) ) ! = kOkRC )
goto errLabel ;
2022-09-10 12:54:17 +00:00
2023-11-26 20:23:36 +00:00
take_label = mem : : printf ( take_label , " %s_%i " , prefix_label , take_numb ) ;
2022-09-10 12:54:17 +00:00
cwLogInfo ( " 0x%x AM:%s MIDI:%s " , dirEntryArray [ i ] . flags , dirEntryArray [ i ] . name , midi_fn ) ;
2023-11-19 19:06:37 +00:00
if ( ( rc = am_to_midi_file ( am_fn , midi_fn , csv_fn ) ) ! = kOkRC )
goto errLabel ;
2022-09-10 12:54:17 +00:00
2023-11-26 20:23:36 +00:00
if ( ( rc = _write_midi_meta_file ( meta_fn , player_name , take_label , sess_numb , take_numb , beg_loc , end_loc ) ) ! = kOkRC )
2023-11-19 19:06:37 +00:00
goto errLabel ;
2023-11-26 20:23:36 +00:00
2022-09-10 12:54:17 +00:00
mem : : release ( am_fn ) ;
mem : : release ( midi_fn ) ;
2023-11-19 20:01:32 +00:00
mem : : release ( csv_fn ) ;
mem : : release ( meta_fn ) ;
2022-09-10 12:54:17 +00:00
2021-08-15 20:02:45 +00:00
}
errLabel :
mem : : release ( dirEntryArray ) ;
2023-11-26 20:23:36 +00:00
mem : : release ( take_label ) ;
2023-11-19 20:01:32 +00:00
if ( rc ! = kOkRC )
rc = cwLogError ( rc , " AM to MIDI conversion failed on '%s'. " , inDir ) ;
2021-08-15 20:02:45 +00:00
return rc ;
}
cw : : rc_t cw : : midi_record_play : : am_to_midi_file ( const object_t * cfg )
{
rc_t rc = kOkRC ;
2023-11-19 19:06:37 +00:00
2023-11-19 20:55:57 +00:00
// verify that the cfg is a list
if ( cfg = = nullptr | | cfg - > is_list ( ) = = false )
2021-08-15 20:02:45 +00:00
{
2023-11-19 20:55:57 +00:00
rc = cwLogError ( kInvalidArgRC , " AM to MIDI file: invalid cfg. " ) ;
2021-08-15 20:02:45 +00:00
goto errLabel ;
}
2023-11-19 20:55:57 +00:00
// for each job specified in the list
for ( unsigned i = 0 ; i < cfg - > child_count ( ) ; + + i )
2021-08-15 20:02:45 +00:00
{
2023-11-26 20:23:36 +00:00
const object_t * job_cfg = nullptr ;
bool enable_fl = false ;
const char * prefix_label = nullptr ;
const char * player_name = nullptr ;
unsigned sess_numb = 0 ;
unsigned beg_loc = kInvalidId ;
unsigned end_loc = kInvalidId ;
const char * dir = nullptr ;
2023-11-19 20:55:57 +00:00
// verify that each job spec is a dict
if ( ( job_cfg = cfg - > child_ele ( i ) ) = = nullptr | | job_cfg - > is_dict ( ) = = false )
{
rc = cwLogError ( kSyntaxErrorRC , " The AM to MIDI job spec. at index %i is invalid. " , i ) ;
goto errLabel ;
}
2021-08-15 20:02:45 +00:00
2023-11-19 20:55:57 +00:00
// read the job spec
2023-11-26 20:23:36 +00:00
if ( ( rc = job_cfg - > getv ( " enable_fl " , enable_fl ,
" player_name " , player_name ,
" prefix_label " , prefix_label ,
" sess_numb " , sess_numb ,
" beg_loc " , beg_loc ,
" end_loc " , end_loc ,
" dir " , dir ) ) ! = kOkRC )
2023-11-19 20:55:57 +00:00
{
rc = cwLogError ( rc , " AM to MIDI file: Unable to parse input arg's at index %i " , i ) ;
goto errLabel ;
}
if ( enable_fl )
2023-11-26 20:23:36 +00:00
{
if ( ( rc = am_to_midi_dir ( player_name , prefix_label , sess_numb , beg_loc , end_loc , dir ) ) ! = kOkRC )
2023-11-19 20:55:57 +00:00
{
2023-11-26 20:23:36 +00:00
rc = cwLogError ( rc , " AM to MIDI file conversion on directory:'%s' failed. " , cwStringNullGuard ( dir ) ) ;
goto errLabel ;
2023-11-19 20:55:57 +00:00
}
2023-11-26 20:23:36 +00:00
}
2021-08-15 20:02:45 +00:00
}
errLabel :
2023-11-19 20:55:57 +00:00
if ( rc ! = kOkRC )
rc = cwLogError ( rc , " AM to MIDI file conversion failed. " ) ;
2021-08-15 20:02:45 +00:00
return rc ;
}
2022-10-15 19:07:06 +00:00
void cw : : midi_record_play : : report ( handle_t h )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
_report_midi ( p ) ;
}
2023-03-17 22:16:25 +00:00
unsigned cw : : midi_record_play : : dev_count ( handle_t h )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return p - > midiDevN ;
}
2023-06-27 21:44:23 +00:00
cw : : rc_t cw : : midi_record_play : : vel_table_disable ( handle_t h , unsigned devIdx )
{
2023-12-30 17:40:33 +00:00
rc_t rc = kOkRC ;
midi_record_play_t * p = _handleToPtr ( h ) ;
unsigned begDevIdx = devIdx = = kInvalidIdx ? 0 : devIdx ;
unsigned endDevIdx = devIdx = = kInvalidIdx ? p - > midiDevN : begDevIdx + 1 ;
if ( devIdx ! = kInvalidIdx & & devIdx > p - > midiDevN )
2023-06-27 21:44:23 +00:00
{
2023-12-30 17:40:33 +00:00
rc = cwLogError ( kInvalidArgRC , " The device index '%i'is invalid. " , devIdx ) ;
goto errLabel ;
2023-06-27 21:44:23 +00:00
}
2023-12-30 17:40:33 +00:00
for ( unsigned i = begDevIdx ; i < endDevIdx ; + + i )
2023-06-27 21:44:23 +00:00
{
2023-12-30 17:40:33 +00:00
mem : : release ( p - > midiDevA [ i ] . velTableArray ) ;
p - > midiDevA [ i ] . velTableN = 0 ;
2023-06-27 21:44:23 +00:00
}
2023-12-30 17:40:33 +00:00
2023-06-27 21:44:23 +00:00
errLabel :
return rc ;
}
2023-03-17 22:16:25 +00:00
unsigned cw : : midi_record_play : : vel_table_count ( handle_t h , unsigned devIdx )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return p - > midiDevA [ devIdx ] . velTableN ;
}
const uint8_t * cw : : midi_record_play : : vel_table ( handle_t h , unsigned devIdx )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
return p - > midiDevA [ devIdx ] . velTableArray ;
}
cw : : rc_t cw : : midi_record_play : : vel_table_set ( handle_t h , unsigned devIdx , const uint8_t * tbl , unsigned tblN )
{
midi_record_play_t * p = _handleToPtr ( h ) ;
midi_device_t * dev = p - > midiDevA + devIdx ;
if ( tblN ! = dev - > velTableN )
{
dev - > velTableArray = mem : : resize < uint8_t > ( dev - > velTableArray , tblN ) ;
dev - > velTableN = tblN ;
}
2023-12-03 16:24:11 +00:00
printf ( " Apply Vel Table to %s : " , cwStringNullGuard ( p - > midiDevA [ devIdx ] . label ) ) ;
2023-03-17 22:16:25 +00:00
for ( unsigned i = 0 ; i < tblN ; + + i )
2023-12-03 16:24:11 +00:00
{
2023-03-17 22:16:25 +00:00
dev - > velTableArray [ i ] = tbl [ i ] ;
2023-12-03 16:24:11 +00:00
printf ( " %i->%i " , i , tbl [ i ] ) ;
}
printf ( " \n " ) ;
2023-03-17 22:16:25 +00:00
return kOkRC ;
}
2023-06-27 21:44:23 +00:00