diff --git a/scripts/autokey.py b/scripts/autokey.py index 94000ea..95c8bb8 100644 --- a/scripts/autokey.py +++ b/scripts/autokey.py @@ -187,6 +187,19 @@ def line_starts_with(x: str) -> bool: print(intf.version()) continue + elif line_equals(':humanizer'): + # Print humanizer level + print(intf.get_humanizer_level()) + continue + + elif line_starts_with(':humanizer'): + # Set humanizer level + try: + intf.set_humanizer_level(float(line[10:])) + except ValueError: + print('Invalid level?') + continue + elif line_starts_with(':'): # Unknown command? print('Unknown command?') diff --git a/scripts/superkey/interface.py b/scripts/superkey/interface.py index 687c193..e59af4b 100644 --- a/scripts/superkey/interface.py +++ b/scripts/superkey/interface.py @@ -170,6 +170,13 @@ def get_buzzer_frequency(self) -> int: self.__send_packet(MessageID.REQUEST_GET_BUZZER_FREQUENCY) return self.__check_reply(' float: + """ + Sends the `REQUEST_GET_HUMANIZER_LEVEL` command. Returns the current humanizer level as a fraction. + """ + self.__send_packet(MessageID.REQUEST_GET_HUMANIZER_LEVEL) + return self.__check_reply(' bool: """ Sends the `REQUEST_GET_INVERT_PADDLES` command. Returns whether or not the paddles are inverted. @@ -289,6 +296,13 @@ def set_buzzer_frequency(self, frequency: int): self.__send_packet(MessageID.REQUEST_SET_BUZZER_FREQUENCY, struct.pack(' #include #include #include @@ -66,6 +67,7 @@ enum static bool s_keyed = false; /**< Is the keyer hardware currently keyed? */ static bool s_panicked = false; /**< Was the keyer panic activated? */ static bool s_trainer_mode = false; /**< Is trainer mode on? */ +static float s_humanizer_level = KEYER_HUMANIZER_OFF;/**< Humanizer level. */ static state_t s_state = STATE_OFF; /**< Currently active keyer state. */ @@ -188,6 +190,12 @@ static bool get_keyed( void ); */ static state_t get_next_state( void ); +/** + * @fn humanize_delay( tick_t ) + * @brief Applies a random variation to the specified delay. + */ +static tick_t humanize_delay( tick_t delay ); + /** * @fn set_keyed( bool ) * @brief Sets whether the keyer hardware is keying or not. @@ -253,6 +261,13 @@ size_t keyer_autokey_str_ex( char const * str, keyer_autokey_flag_field_t flags } /* keyer_autokey_str() */ +float keyer_get_humanizer_level( void ) +{ + return( s_humanizer_level ); + +} /* keyer_get_humanizer_level() */ + + bool keyer_get_on( void ) { return( get_keyed() ); @@ -287,6 +302,7 @@ void keyer_init( void ) s_keyed = false; s_panicked = false; s_trainer_mode = false; + s_humanizer_level = KEYER_HUMANIZER_OFF; s_state = STATE_OFF; s_el = WPM_ELEMENT_NONE; s_lockout_el = WPM_ELEMENT_NONE; @@ -316,6 +332,13 @@ void keyer_panic( void ) } /* keyer_panic() */ +void keyer_set_humanizer_level( float level ) +{ + s_humanizer_level = clamp( level, KEYER_HUMANIZER_LEVEL_MIN, KEYER_HUMANIZER_LEVEL_MAX ); + +} /* keyer_set_humanizer_level() */ + + void keyer_set_paddle_invert( bool invert ) { config_t config; @@ -877,9 +900,11 @@ static void do_state_autokey( tick_t tick, bool new_state ) if( wpm_element_is_keyed( s_el ) ) { s_lockout_el = s_el; - s_el_stop_tick = tick + s_ticks[ s_el ]; + s_el_stop_tick = tick + + humanize_delay( s_ticks[ s_el ] ); s_el_stop_tick_vld = true; - s_el_start_tick = tick + s_ticks[ s_el ] + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ]; + s_el_start_tick = tick + + humanize_delay( s_ticks[ s_el ] + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ] ); s_el_start_tick_vld = true; set_keyed( true ); } @@ -887,11 +912,12 @@ static void do_state_autokey( tick_t tick, bool new_state ) { s_el_stop_tick = 0; s_el_stop_tick_vld = false; - s_el_start_tick = tick + s_ticks[ s_el ] - - ( prev_lockout_el_was_keyed ? - s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ] : 0 ) - - ( prev_el_was_letter_space ? - ( s_ticks[ WPM_ELEMENT_LETTER_SPACE ] - s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ] ) : 0 ); + s_el_start_tick = tick + + humanize_delay( s_ticks[ s_el ] + - ( prev_lockout_el_was_keyed ? + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ] : 0 ) + - ( prev_el_was_letter_space ? + ( s_ticks[ WPM_ELEMENT_LETTER_SPACE ] - s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ] ) : 0 ) ); s_el_start_tick_vld = true; } } @@ -913,9 +939,11 @@ static void do_state_dashes( tick_t tick, bool new_state ) // Activate keyer hardware s_el = WPM_ELEMENT_DASH; s_lockout_el = s_el; - s_el_stop_tick = tick + s_ticks[ WPM_ELEMENT_DASH ]; + s_el_stop_tick = tick + + humanize_delay( s_ticks[ WPM_ELEMENT_DASH ] ); s_el_stop_tick_vld = true; - s_el_start_tick = tick + s_ticks[ WPM_ELEMENT_DASH ] + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ]; + s_el_start_tick = tick + + humanize_delay( s_ticks[ WPM_ELEMENT_DASH ] + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ] ); s_el_start_tick_vld = true; set_keyed( true ); } @@ -937,9 +965,11 @@ static void do_state_dots( tick_t tick, bool new_state ) // Activate keyer hardware s_el = WPM_ELEMENT_DOT; s_lockout_el = s_el; - s_el_stop_tick = tick + s_ticks[ WPM_ELEMENT_DOT ]; + s_el_stop_tick = tick + + humanize_delay( s_ticks[ WPM_ELEMENT_DOT ] ); s_el_stop_tick_vld = true; - s_el_start_tick = tick + s_ticks[ WPM_ELEMENT_DOT ] + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ]; + s_el_start_tick = tick + + humanize_delay( s_ticks[ WPM_ELEMENT_DOT ] + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ] ); s_el_start_tick_vld = true; set_keyed( true ); } @@ -961,9 +991,11 @@ static void do_state_interleaved( tick_t tick, bool new_state ) // Activate keyer hardware s_el = ( s_lockout_el == WPM_ELEMENT_DOT ? WPM_ELEMENT_DASH : WPM_ELEMENT_DOT ); s_lockout_el = s_el; - s_el_stop_tick = tick + s_ticks[ s_el ]; + s_el_stop_tick = tick + + humanize_delay( s_ticks[ s_el ] ); s_el_stop_tick_vld = true; - s_el_start_tick = tick + s_ticks[ s_el ] + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ]; + s_el_start_tick = tick + + humanize_delay( s_ticks[ s_el ] + s_ticks[ WPM_ELEMENT_ELEMENT_SPACE ] ); s_el_start_tick_vld = true; set_keyed( true ); } @@ -1113,6 +1145,20 @@ static state_t get_next_state( void ) } /* get_next_state() */ +static tick_t humanize_delay( tick_t delay ) +{ + // Early check to avoid expensive floating point operations if not needed + if( s_humanizer_level == KEYER_HUMANIZER_OFF ) + return( delay ); + + float rand = ( float )random() / ( float )RANDOM_MAX; + tick_t offset = ( tick_t )roundf( ( rand * s_humanizer_level ) * ( 0.5f * s_ticks[ WPM_ELEMENT_DOT ] ) ); + + return( delay + offset ); + +} /* humanize_delay() */ + + static void set_keyed( bool keyed ) { s_keyed = keyed; diff --git a/src/main/application/keyer.h b/src/main/application/keyer.h index ef19c2b..667e686 100644 --- a/src/main/application/keyer.h +++ b/src/main/application/keyer.h @@ -29,7 +29,25 @@ * @def KEYER_AUTOKEY_FLAG_NONE * @brief Value for `keyer_autokey_flag_field_t` indicating that no flags are selected. */ -#define KEYER_AUTOKEY_FLAG_NONE ( 0 ) +#define KEYER_AUTOKEY_FLAG_NONE 0 + +/** + * @def KEYER_HUMANIZER_LEVEL_MIN + * @brief Minimum humanizer level (unitless fraction). + */ +#define KEYER_HUMANIZER_LEVEL_MIN 0.0f + +/** + * @def KEYER_HUMANIZER_LEVEL_MAX + * @brief Maximum humanizer level (unitless fraction). + */ +#define KEYER_HUMANIZER_LEVEL_MAX 1.0f + +/** + * @def KEYER_HUMANIZER_OFF + * @brief Humanizer level value which disables the humanizer (unitless fraction). + */ +#define KEYER_HUMANIZER_OFF KEYER_HUMANIZER_LEVEL_MIN /* ----------------------------------------------------- TYPES ------------------------------------------------------ */ @@ -123,6 +141,12 @@ size_t keyer_autokey_str( char const * str ); */ size_t keyer_autokey_str_ex( char const * str, keyer_autokey_flag_field_t flags ); +/** + * @fn keyer_get_humanizer_level( void ) + * @brief Returns the current humanizer level. + */ +float keyer_get_humanizer_level( void ); + /** * @fn keyer_get_on( void ) * @brief Returns `true` if the keyer is currently commanding the radio to transmit. @@ -167,6 +191,13 @@ void keyer_init( void ); */ void keyer_panic( void ); +/** + * @fn keyer_set_humanizer_level( float ) + * @brief Sets the current humanizer level. + * @note The argument will be clamped to the range [`KEYER_HUMANIZER_MIN`, `KEYER_HUMANIZER_MAX`]. + */ +void keyer_set_humanizer_level( float humanizer ); + /** * @fn keyer_set_paddle_invert( bool ) * @brief Enables or disables the "invert paddles" setting. If set to `true`, the right paddle will emit dots and the