diff --git a/veejay-current/veejay-server/libveejay/liblavplayvj.c b/veejay-current/veejay-server/libveejay/liblavplayvj.c index b23ee77f..7a7b5c01 100644 --- a/veejay-current/veejay-server/libveejay/liblavplayvj.c +++ b/veejay-current/veejay-server/libveejay/liblavplayvj.c @@ -477,8 +477,7 @@ static inline double vj_runtime_tempo_follow_correction(video_playback_setup *se return 1.0; mode = atomic_load_int(&settings->audio_sync.mode); - if(mode != VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW && - mode != VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE) + if(mode != VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW) return 1.0; if(!vj_audio_sync_is_enabled(&settings->audio_sync)) @@ -510,13 +509,15 @@ static inline double vj_runtime_tempo_follow_correction(video_playback_setup *se return correction; } #endif -static inline double vj_runtime_target_time_s(video_playback_setup *settings, long long frame) +static inline double vj_runtime_effective_spvf(video_playback_setup *settings) { - double epoch_s = atomic_load_double(&settings->fps_epoch_s); - long long epoch_frame = atomic_load_long_long(&settings->fps_epoch_frame); - double spvf = settings->spvf; + double spvf; - if (spvf <= 0.0) + if(!settings) + return 1.0 / 25.0; + + spvf = settings->spvf; + if(spvf <= 0.0) spvf = 1.0 / 25.0; #ifdef HAVE_JACK @@ -527,6 +528,18 @@ static inline double vj_runtime_target_time_s(video_playback_setup *settings, lo } #endif + if(spvf <= 0.0) + spvf = 1.0 / 25.0; + + return spvf; +} + +static inline double vj_runtime_target_time_s(video_playback_setup *settings, long long frame) +{ + double epoch_s = atomic_load_double(&settings->fps_epoch_s); + long long epoch_frame = atomic_load_long_long(&settings->fps_epoch_frame); + double spvf = vj_runtime_effective_spvf(settings); + if (epoch_s <= 0.0) { double anchor = atomic_load_double(&settings->audio_start_offset); epoch_s = (anchor > 0.0) ? anchor : monotonic_now_s(); @@ -561,19 +574,10 @@ static void vj_runtime_reanchor_clock(veejay_t *info, master_frame = atomic_load_long_long(&settings->master_frame_num); master_s = vj_runtime_master_clock_now_s(info, &using_audio_clock); - anchor_frame = requested_frame; - if(using_audio_clock && master_frame >= 0) - anchor_frame = master_frame; + anchor_frame = (master_frame >= 0) ? master_frame : requested_frame; atomic_store_double(&settings->fps_epoch_s, master_s); atomic_store_long_long(&settings->fps_epoch_frame, anchor_frame); - - veejay_msg(VEEJAY_MSG_DEBUG, - "[CLOCK] transport clock re-anchored at frame %lld requested=%lld reason=%s domain=%s", - anchor_frame, - requested_frame, - reason ? reason : "transport-discontinuity", - using_audio_clock ? "jack-audio" : "monotonic"); } static void vj_runtime_update_frame_fps(veejay_t *info, float fps) @@ -1612,6 +1616,32 @@ static inline void safe_join(pthread_t *t, const char *name) } } +static void veejay_wake_playback_waiters(veejay_t *info) +{ + if (!info || !info->settings) + return; + + video_playback_setup *settings = (video_playback_setup*) info->settings; + + atomic_store_int(&settings->first_audio_frame_ready, 1); + atomic_store_int(&info->audio_running, 0); + +#ifdef HAVE_JACK + vj_audio_beat_request_stop(&settings->audio_beat); + vj_audio_sync_request_stop(&settings->audio_sync); +#endif + + pthread_mutex_lock(&settings->mutex); + pthread_cond_broadcast(&settings->producer_wait_cv); + pthread_cond_broadcast(&settings->renderer_wait_cv); + pthread_cond_broadcast(&settings->data_ready_cv); + pthread_mutex_unlock(&settings->mutex); + + pthread_mutex_lock(&settings->start_mutex); + pthread_cond_broadcast(&settings->start_cond); + pthread_mutex_unlock(&settings->start_mutex); +} + void veejay_busy(veejay_t * info) { if (!info || !info->settings) { @@ -1621,10 +1651,8 @@ void veejay_busy(veejay_t * info) video_playback_setup *settings = (video_playback_setup*)(info->settings); veejay_msg(VEEJAY_MSG_DEBUG, "Waiting for threads to finish..."); -#ifdef HAVE_JACK - vj_audio_beat_request_stop(&settings->audio_beat); - vj_audio_sync_request_stop(&settings->audio_sync); -#endif + veejay_change_state(info, LAVPLAY_STATE_STOP); + veejay_wake_playback_waiters(info); safe_join(&settings->renderer_thread, "Renderer"); safe_join(&settings->producer_thread, "Producer"); @@ -1639,7 +1667,8 @@ void veejay_busy(veejay_t * info) void veejay_quit(veejay_t * info) { - veejay_change_state(info, LAVPLAY_STATE_STOP); + veejay_change_state(info, LAVPLAY_STATE_STOP); + veejay_wake_playback_waiters(info); } @@ -1867,8 +1896,6 @@ void veejay_set_framerate( veejay_t *info , float fps ) vj_audio_sync_set_track_align_video_fps(&settings->audio_sync, veejay_track_align_media_fps(info)); #endif - - veejay_msg(VEEJAY_MSG_INFO, "Playback engine is now playing at %.2f FPS", fps); } @@ -2397,6 +2424,22 @@ void veejay_stop_sampling(veejay_t * info) info->current_edit_list = info->edit_list; } +static int veejay_cond_wait_stop_poll(video_playback_setup *settings, pthread_cond_t *cv) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + ts.tv_nsec += 50000000L; + if (ts.tv_nsec >= 1000000000L) { + ts.tv_nsec -= 1000000000L; + ts.tv_sec++; + } + + pthread_cond_timedwait(cv, &settings->mutex, &ts); + + return atomic_load_int(&settings->state) == LAVPLAY_STATE_STOP; +} + VJFrame *veejay_video_queue_reserve_buffer(veejay_t *info) { video_playback_setup *settings = info->settings; @@ -2416,7 +2459,10 @@ VJFrame *veejay_video_queue_reserve_buffer(veejay_t *info) pthread_mutex_unlock(&settings->mutex); return NULL; } - pthread_cond_wait(&settings->producer_wait_cv, &settings->mutex); + if (veejay_cond_wait_stop_poll(settings, &settings->producer_wait_cv)) { + pthread_mutex_unlock(&settings->mutex); + return NULL; + } } settings->states[idx] = BUFFER_RESERVED; @@ -2458,8 +2504,10 @@ VJFrame *veejay_video_queue_get_frame(veejay_t *info) pthread_mutex_unlock(&settings->mutex); return NULL; } - // wait is here - pthread_cond_wait(&settings->renderer_wait_cv, &settings->mutex); + if (veejay_cond_wait_stop_poll(settings, &settings->renderer_wait_cv)) { + pthread_mutex_unlock(&settings->mutex); + return NULL; + } } settings->states[idx] = BUFFER_IN_RENDER; @@ -2673,7 +2721,13 @@ static char *veejay_pipe_append_audio_beat_status(veejay_t *info, char *ptr) ptr = vj_sprintf(ptr, ok ? AB_POS(snap.sample_rate) : 0); /* 53 */ ptr = vj_sprintf(ptr, ok ? AB_POS(snap.hit_seq) : 0); /* 54 */ ptr = vj_sprintf(ptr, audio_mute); /* 55 */ - ptr = vj_sprintf(ptr, record_audio_source); + ptr = vj_sprintf(ptr, record_audio_source); /* 56 */ + { + int action = ok ? vj_audio_beat_get_action(&info->settings->audio_beat) : 0; + if(action < 0 || action > 4) + action = 0; + ptr = vj_sprintf(ptr, action); /* 57 */ + } #undef AB_POS #undef AB_AGE #undef AB_BPM10 @@ -2697,7 +2751,8 @@ static char *veejay_pipe_append_audio_beat_status(veejay_t *info, char *ptr) ptr = vj_sprintf(ptr, 0); /* 53 sample_rate */ ptr = vj_sprintf(ptr, 0); /* 54 hit_seq */ ptr = vj_sprintf(ptr, 1); /* 55 muted */ - ptr = vj_sprintf(ptr, record_audio_source); + ptr = vj_sprintf(ptr, record_audio_source); /* 56 */ + ptr = vj_sprintf(ptr, 0); /* 57 action */ #endif return ptr; @@ -2727,32 +2782,31 @@ static char *veejay_pipe_append_audio_sync_status(veejay_t *info, char *ptr) #define AS_CORR100(v_) \ ((int)((((v_) < 0.0) ? 0.0 : (((v_) > 9.999) ? 9.999 : (v_))) * 100.0) + 0.5) - ptr = vj_sprintf(ptr, ok ? snap.enabled : 0); /* 57 */ - ptr = vj_sprintf(ptr, ok ? snap.open : 0); /* 58 */ - ptr = vj_sprintf(ptr, ok ? snap.running : 0); /* 59 */ - ptr = vj_sprintf(ptr, ok ? snap.mode : 0); /* 60 */ - ptr = vj_sprintf(ptr, ok ? snap.source : 0); /* 61 */ - ptr = vj_sprintf(ptr, ok ? AS_POS(snap.channels) : 0); /* 62 */ - ptr = vj_sprintf(ptr, ok ? AS_POS(snap.sample_rate) : 0); /* 63 */ - ptr = vj_sprintf(ptr, ok ? AS_PCT(snap.level) : 0); /* 64 */ - ptr = vj_sprintf(ptr, ok ? AS_PCT(snap.transient) : 0); /* 65 */ - ptr = vj_sprintf(ptr, ok ? AS_BPM10(snap.bpm) : 0); /* 66 */ - ptr = vj_sprintf(ptr, ok ? AS_PCT(snap.beat_phase) : 0); /* 67 */ - ptr = vj_sprintf(ptr, ok ? AS_PCT(snap.confidence) : 0); /* 68 */ - ptr = vj_sprintf(ptr, ok ? (((snap.mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || - snap.mode == VJ_AUDIO_SYNC_MODE_MONITOR) && snap.open) ? 1 : 0) : 0); /* 69 external playback active */ - ptr = vj_sprintf(ptr, ok ? AS_RATIO1000(snap.bridge_ratio) : 1000); /* 70 */ - ptr = vj_sprintf(ptr, ok ? AS_CORR100(snap.bridge_correction) : 100); /* 71 */ - ptr = vj_sprintf(ptr, ok ? snap.target_mode : 0); /* 72 */ - ptr = vj_sprintf(ptr, ok ? AS_BPM10(snap.target_bpm) : 0); /* 73 */ - ptr = vj_sprintf(ptr, ok ? AS_PCT(snap.target_confidence) : 0); /* 74 */ - ptr = vj_sprintf(ptr, ok ? AS_POS(snap.max_correction_pct) : 0); /* 75 */ - ptr = vj_sprintf(ptr, ok ? AS_POS(snap.bridge_state) : 0); /* 76 */ - ptr = vj_sprintf(ptr, ok ? AS_POS(snap.track_align_locked) : 0); /* 77 */ - ptr = vj_sprintf(ptr, ok ? snap.track_align_offset_ms : 0); /* 78 */ - ptr = vj_sprintf(ptr, ok ? AS_POS(snap.track_align_confidence_pct) : 0); /* 79 */ - ptr = vj_sprintf(ptr, ok ? snap.track_align_correction_ppm : 0); /* 80 */ - ptr = vj_sprintf(ptr, ok ? AS_POS(snap.track_align_state) : 0); /* 81 */ + ptr = vj_sprintf(ptr, ok ? snap.enabled : 0); /* 58 */ + ptr = vj_sprintf(ptr, ok ? snap.open : 0); /* 59 */ + ptr = vj_sprintf(ptr, ok ? snap.running : 0); /* 60 */ + ptr = vj_sprintf(ptr, ok ? snap.mode : 0); /* 61 */ + ptr = vj_sprintf(ptr, ok ? snap.source : 0); /* 62 */ + ptr = vj_sprintf(ptr, ok ? AS_POS(snap.channels) : 0); /* 63 */ + ptr = vj_sprintf(ptr, ok ? AS_POS(snap.sample_rate) : 0); /* 64 */ + ptr = vj_sprintf(ptr, ok ? AS_PCT(snap.level) : 0); /* 65 */ + ptr = vj_sprintf(ptr, ok ? AS_PCT(snap.transient) : 0); /* 66 */ + ptr = vj_sprintf(ptr, ok ? AS_BPM10(snap.bpm) : 0); /* 67 */ + ptr = vj_sprintf(ptr, ok ? AS_PCT(snap.beat_phase) : 0); /* 68 */ + ptr = vj_sprintf(ptr, ok ? AS_PCT(snap.confidence) : 0); /* 69 */ + ptr = vj_sprintf(ptr, ok ? ((snap.mode != VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW && vj_audio_sync_mode_uses_external_playback(snap.mode) && snap.open) ? 1 : 0) : 0); /* 70 external playback active */ + ptr = vj_sprintf(ptr, ok ? AS_RATIO1000(snap.bridge_ratio) : 1000); /* 71 */ + ptr = vj_sprintf(ptr, ok ? AS_CORR100(snap.bridge_correction) : 100); /* 72 */ + ptr = vj_sprintf(ptr, ok ? snap.target_mode : 0); /* 73 */ + ptr = vj_sprintf(ptr, ok ? AS_BPM10(snap.target_bpm) : 0); /* 74 */ + ptr = vj_sprintf(ptr, ok ? AS_PCT(snap.target_confidence) : 0); /* 75 */ + ptr = vj_sprintf(ptr, ok ? AS_POS(snap.max_correction_pct) : 0); /* 76 */ + ptr = vj_sprintf(ptr, ok ? AS_POS(snap.bridge_state) : 0); /* 77 */ + ptr = vj_sprintf(ptr, ok ? AS_POS(snap.track_align_locked) : 0); /* 78 */ + ptr = vj_sprintf(ptr, ok ? snap.track_align_offset_ms : 0); /* 79 */ + ptr = vj_sprintf(ptr, ok ? AS_POS(snap.track_align_confidence_pct) : 0); /* 80 */ + ptr = vj_sprintf(ptr, ok ? snap.track_align_correction_ppm : 0); /* 81 */ + ptr = vj_sprintf(ptr, ok ? AS_POS(snap.track_align_state) : 0); /* 82 */ #undef AS_CORR100 #undef AS_RATIO1000 @@ -2819,21 +2873,21 @@ static char *veejay_pipe_append_chain_entry_status(veejay_t *info, char *ptr) else if(num_params > VJ_STATUS_CHAIN_ENTRY_PARAMETERS) num_params = VJ_STATUS_CHAIN_ENTRY_PARAMETERS; - ptr = vj_sprintf(ptr, effect_id); /* 82 */ - ptr = vj_sprintf(ptr, is_video); /* 83 */ - ptr = vj_sprintf(ptr, num_params); /* 84 */ - ptr = vj_sprintf(ptr, entry->kf_type); /* 85 */ - ptr = vj_sprintf(ptr, entry->kf_status); /* 86 */ - ptr = vj_sprintf(ptr, 0); /* 87 transition enabled */ - ptr = vj_sprintf(ptr, 0); /* 88 transition loop */ - ptr = vj_sprintf(ptr, entry->source_type); /* 89 */ - ptr = vj_sprintf(ptr, entry->channel); /* 90 */ - ptr = vj_sprintf(ptr, entry->e_flag); /* 91 */ - ptr = vj_sprintf(ptr, entry->beat_flag); /* 92 */ - ptr = vj_sprintf(ptr, entry->is_rendering); /* 93 */ + ptr = vj_sprintf(ptr, effect_id); /* 83 */ + ptr = vj_sprintf(ptr, is_video); /* 84 */ + ptr = vj_sprintf(ptr, num_params); /* 85 */ + ptr = vj_sprintf(ptr, entry->kf_type); /* 86 */ + ptr = vj_sprintf(ptr, entry->kf_status); /* 87 */ + ptr = vj_sprintf(ptr, 0); /* 88 transition enabled */ + ptr = vj_sprintf(ptr, 0); /* 89 transition loop */ + ptr = vj_sprintf(ptr, entry->source_type); /* 90 */ + ptr = vj_sprintf(ptr, entry->channel); /* 91 */ + ptr = vj_sprintf(ptr, entry->e_flag); /* 92 */ + ptr = vj_sprintf(ptr, entry->beat_flag); /* 93 */ + ptr = vj_sprintf(ptr, entry->is_rendering); /* 94 */ for(int i = 0; i < VJ_STATUS_CHAIN_ENTRY_PARAMETERS; i++) - ptr = vj_sprintf(ptr, (i < num_params) ? entry->arg[i] : 0); /* 94..109 */ + ptr = vj_sprintf(ptr, (i < num_params) ? entry->arg[i] : 0); /* 95..110 */ return ptr; } @@ -2992,8 +3046,8 @@ static void veejay_pipe_write_status(veejay_t * info) { if(!status_packet_warned) { veejay_msg(VEEJAY_MSG_WARNING, - "Status packet base mismatch: got %d fields, expected %d; padding fixed 110-token packet", - base_tokens, VJ_STATUS_BASE_TOKENS); + "Status packet base mismatch: got %d fields, expected %d; padding fixed %d-token packet", + base_tokens, VJ_STATUS_BASE_TOKENS, VIMS_STATUS_TOKENS); status_packet_warned = 1; } @@ -3200,6 +3254,10 @@ void veejay_handle_signal(int sig, siginfo_t *si, void *unused) case SIGINT: case SIGQUIT: veejay_change_state(info, LAVPLAY_STATE_STOP); + if (info && info->settings) { + atomic_store_int(&info->audio_running, 0); + atomic_store_int(&info->settings->first_audio_frame_ready, 1); + } break; case SIGSEGV: @@ -3614,7 +3672,7 @@ void *veejay_display_renderer_thread(void *arg) } double audio_master = atomic_load_double(&settings->audio_master_s); - double spvf = settings->spvf; + double spvf = vj_runtime_effective_spvf(settings); double target_time_s = vj_runtime_target_time_s(settings, frame->frame_num); double delay_s = target_time_s - audio_master; @@ -3723,11 +3781,14 @@ int veejay_open(veejay_t * info) int i; pthread_mutex_init(&(settings->control_mutex), NULL); - + pthread_mutex_init(&(settings->start_mutex), NULL); + pthread_mutex_init(&settings->mutex, NULL); - pthread_cond_init(&settings->producer_wait_cv, NULL); + pthread_cond_init(&settings->producer_wait_cv, &attr); pthread_cond_init(&settings->renderer_wait_cv, &attr); - pthread_cond_init(&settings->data_ready_cv, &attr); + pthread_cond_init(&settings->data_ready_cv, &attr); + pthread_cond_init(&settings->start_cond, &attr); + pthread_condattr_destroy(&attr); settings->producer_index = 0; settings->renderer_index = 0; @@ -3804,15 +3865,7 @@ int veejay_close(veejay_t *info) int success = 1; veejay_change_state_save(info, LAVPLAY_STATE_STOP); -#ifdef HAVE_JACK - vj_audio_beat_request_stop(&settings->audio_beat); - vj_audio_sync_request_stop(&settings->audio_sync); -#endif - - pthread_mutex_lock(&settings->mutex); - pthread_cond_broadcast(&settings->producer_wait_cv); - pthread_cond_broadcast(&settings->renderer_wait_cv); - pthread_mutex_unlock(&settings->mutex); + veejay_wake_playback_waiters(info); safe_join(&settings->renderer_thread, "Renderer"); safe_join(&settings->producer_thread, "Producer"); @@ -3824,8 +3877,12 @@ int veejay_close(veejay_t *info) #endif pthread_mutex_destroy(&settings->mutex); + pthread_mutex_destroy(&settings->control_mutex); + pthread_mutex_destroy(&settings->start_mutex); pthread_cond_destroy(&settings->producer_wait_cv); pthread_cond_destroy(&settings->renderer_wait_cv); + pthread_cond_destroy(&settings->data_ready_cv); + pthread_cond_destroy(&settings->start_cond); for (int i = 0; i < VIDEO_QUEUE_LEN; i++) if (settings->buffers[i]) veejay_free_frame_buffer(settings->buffers[i]); @@ -4746,7 +4803,7 @@ static inline double vj_audio_producer_pace_sleep_s(long client_rate, settings, budget); } -static inline int vj_audio_is_external_jack_monitor_source( +static inline int vj_audio_uses_external_audio_playback_source( video_playback_setup *settings, int audio_source, int sync_mode, @@ -4757,9 +4814,10 @@ static inline int vj_audio_is_external_jack_monitor_source( if(!settings || !sync_enabled) return 0; - if(sync_mode != VJ_AUDIO_SYNC_MODE_MONITOR && - sync_mode != VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE && - sync_mode != VJ_AUDIO_SYNC_MODE_TRACK_ALIGN) + if(sync_mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW) + return 0; + + if(!vj_audio_sync_mode_uses_external_playback(sync_mode)) return 0; source = atomic_load_int(&settings->audio_sync.source); @@ -4798,7 +4856,7 @@ static int vj_tempo_bridge_reverse_should_reanchor( if(!vj_audio_sync_is_enabled(&settings->audio_sync)) return 0; - if(!vj_audio_is_external_jack_monitor_source( + if(!vj_audio_uses_external_audio_playback_source( settings, atomic_load_int(&settings->record_audio_source), sync_mode, @@ -4896,7 +4954,7 @@ static inline int vj_audio_should_pace_direct_jack_monitor( if(!settings) return 0; - external_monitor = vj_audio_is_external_jack_monitor_source( + external_monitor = vj_audio_uses_external_audio_playback_source( settings, audio_source, sync_mode, sync_enabled ); @@ -4907,7 +4965,10 @@ static inline int vj_audio_should_pace_direct_jack_monitor( mute = atomic_load_int(&settings->audio_mute); external_tape = external_monitor && !mute && - (sync_mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || !neutral); + vj_audio_sync_mode_uses_transport_driven_playback(sync_mode) && + (sync_mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || + sync_mode == VJ_AUDIO_SYNC_MODE_MONITOR_TRICKPLAY || + !neutral); if(external_tape_source) *external_tape_source = external_tape; @@ -5380,7 +5441,7 @@ void *veejay_audio_producer_thread(void *arg) sfd_dbg ); int external_monitor_audio = - vj_audio_is_external_jack_monitor_source( + vj_audio_uses_external_audio_playback_source( settings, audio_source_dbg, audio_sync_mode_dbg, @@ -5388,7 +5449,9 @@ void *veejay_audio_producer_thread(void *arg) ); external_tape_source_dbg = external_monitor_audio && + vj_audio_sync_mode_uses_transport_driven_playback(audio_sync_mode_dbg) && (audio_sync_mode_dbg == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || + audio_sync_mode_dbg == VJ_AUDIO_SYNC_MODE_MONITOR_TRICKPLAY || !external_tape_neutral_dbg); int external_live_source_dbg = external_monitor_audio && !external_tape_source_dbg; @@ -5883,13 +5946,16 @@ void *veejay_audio_producer_thread(void *arg) } else { #endif - double spvf = settings->spvf; + double spvf = vj_runtime_effective_spvf(settings); const double next_frame_target = vj_runtime_target_time_s(settings, (long long)(loop_count + 1)); double now = monotonic_now_s(); double remaining_s = next_frame_target - now; if (remaining_s > 0.0) { + if (remaining_s > spvf) + remaining_s = spvf; + usleep_hybrid((long long)(remaining_s * 1e6), settings); } @@ -5993,7 +6059,8 @@ static void veejay_producer_thread_audio_startup(veejay_t *info) return; } - while (!atomic_load_int(&info->audio_running)) + while (!atomic_load_int(&info->audio_running) && + atomic_load_int(&settings->state) != LAVPLAY_STATE_STOP) usleep_accurate(200,settings); } @@ -6060,9 +6127,9 @@ static void veejay_audio_sync_thread_startup(veejay_t *info) static int veejay_audio_sync_mode_is_playback(int mode) { - return mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || - mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN || - mode == VJ_AUDIO_SYNC_MODE_MONITOR; + if(mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW) + return 0; + return vj_audio_sync_mode_uses_external_playback(mode); } static int veejay_audio_threads_disabled(video_playback_setup *settings) @@ -6237,7 +6304,8 @@ int veejay_audio_sync_set_mode_control(veejay_t *info, int mode) vj_audio_sync_set_mode(&settings->audio_sync, mode); if(mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN || - mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW) + mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW || + mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE) vj_audio_sync_set_target_mode(&settings->audio_sync, VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP); @@ -6430,12 +6498,7 @@ static void veejay_audio_beat_prepare_sync_source(veejay_t *info) current_channels = atomic_load_int(&settings->audio_sync.input_channels_request); record_source = atomic_load_int(&settings->record_audio_source); - external_mode = - (mode == VJ_AUDIO_SYNC_MODE_LIVE_EXTERNAL || - mode == VJ_AUDIO_SYNC_MODE_MONITOR || - mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || - mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN || - mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW); + external_mode = vj_audio_sync_mode_is_audio_sync_family(mode); use_push_source = 0; if(record_source == VJ_RECORD_AUDIO_SOURCE_ORIGINAL) @@ -6737,27 +6800,39 @@ static void *veejay_producer_thread_loop(void *ptr) sigaddset(&mask, SIGPIPE); pthread_sigmask(SIG_BLOCK, &mask, NULL); - while (atomic_load_int(&settings->warmup_active)) { + while (atomic_load_int(&settings->warmup_active) && + atomic_load_int(&settings->state) != LAVPLAY_STATE_STOP) { usleep_accurate(100, settings); } + if (atomic_load_int(&settings->state) == LAVPLAY_STATE_STOP) + pthread_exit(NULL); + atomic_store_long_long(&settings->current_frame_num, -1); atomic_store_int(&settings->audio_mode, AUDIO_MODE_SILENCE_FILL); if(info->audio) { #ifdef HAVE_JACK veejay_msg(VEEJAY_MSG_DEBUG, "[PRODUCER] waiting for audio anchor"); - while (atomic_load_double(&settings->audio_start_offset) <= 0.0) { + while (atomic_load_double(&settings->audio_start_offset) <= 0.0 && + atomic_load_int(&settings->state) != LAVPLAY_STATE_STOP) { usleep_accurate(100, settings); } + + if (atomic_load_int(&settings->state) == LAVPLAY_STATE_STOP) + pthread_exit(NULL); #endif } veejay_msg(VEEJAY_MSG_DEBUG, "[PRODUCER] waiting for first audio frame ready"); - while (atomic_load_int(&settings->first_audio_frame_ready) == 0) { + while (atomic_load_int(&settings->first_audio_frame_ready) == 0 && + atomic_load_int(&settings->state) != LAVPLAY_STATE_STOP) { usleep_accurate(100, settings); } + if (atomic_load_int(&settings->state) == LAVPLAY_STATE_STOP) + pthread_exit(NULL); + veejay_msg(VEEJAY_MSG_DEBUG, "[PRODUCER] Wait for playback to reach anchor"); veejay_set_speed(info, 1, 0); @@ -6790,7 +6865,8 @@ static void *veejay_producer_thread_loop(void *ptr) int beat_action = vj_audio_beat_get_action(&settings->audio_beat); if(beat_action == VJ_AUDIO_BEAT_ACTION_FREEZE || - beat_action == VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX) + beat_action == VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX || + beat_action == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) { vj_audio_beat_consume(info, &settings->audio_beat); } @@ -7377,10 +7453,14 @@ int veejay_main(veejay_t *info) veejay_producer_thread_audio_startup(info); - while (atomic_load_int(&settings->first_audio_frame_ready) == 0) { + while (atomic_load_int(&settings->first_audio_frame_ready) == 0 && + atomic_load_int(&settings->state) != LAVPLAY_STATE_STOP) { usleep_accurate(5000, settings); } + if (atomic_load_int(&settings->state) == LAVPLAY_STATE_STOP) + return 0; + Welcome(info); return 1; @@ -7985,10 +8065,18 @@ int veejay_audio_sync_set_external_wav(veejay_t *info, const char *path, int loo mode > VJ_AUDIO_SYNC_MODE_MAX) { veejay_msg(VEEJAY_MSG_WARNING, - "[AUDIO-SYNC][WAV] invalid mode=%d, forcing external-analysis(%d)", + "[AUDIO-SYNC][WAV] invalid mode=%d, forcing clean monitor(%d)", mode, - VJ_AUDIO_SYNC_MODE_LIVE_EXTERNAL); - mode = VJ_AUDIO_SYNC_MODE_LIVE_EXTERNAL; + VJ_AUDIO_SYNC_MODE_MONITOR); + mode = VJ_AUDIO_SYNC_MODE_MONITOR; + } + + if(!vj_audio_sync_mode_supports_wav_master(mode)) { + veejay_msg(VEEJAY_MSG_WARNING, + "[AUDIO-SYNC][WAV] mode=%d does not support a WAV master, forcing clean monitor(%d)", + mode, + VJ_AUDIO_SYNC_MODE_MONITOR); + mode = VJ_AUDIO_SYNC_MODE_MONITOR; } loop = loop ? 1 : 0; diff --git a/veejay-current/veejay-server/libveejay/vj-audio-beat.c b/veejay-current/veejay-server/libveejay/vj-audio-beat.c index 2065905e..5a979815 100644 --- a/veejay-current/veejay-server/libveejay/vj-audio-beat.c +++ b/veejay-current/veejay-server/libveejay/vj-audio-beat.c @@ -35,14 +35,43 @@ #include #include #include +#include +#include #include #include #include +#include +#include #include #include #include "vj-audio-beat.h" extern int veejay_set_speed(veejay_t *v, int speed, int force_seek); +extern int veejay_set_frame(veejay_t *info, long framenum); +extern void veejay_set_framerate(veejay_t *info, float fps); + +static volatile int ab_transport_command_depth = 0; + +static int ab_set_speed_from_beat(veejay_t *v, int speed, int force_seek) +{ + int r; + + __sync_add_and_fetch(&ab_transport_command_depth, 1); + r = veejay_set_speed(v, speed, force_seek); + __sync_sub_and_fetch(&ab_transport_command_depth, 1); + + return r; +} + +int vj_audio_beat_transport_is_internal(vj_audio_beat_shared_t *s) +{ + (void)s; + return __sync_fetch_and_add(&ab_transport_command_depth, 0) > 0; +} + +#ifndef VJ_AUDIO_BEAT_ACTION_BREAK_BEAT +#define VJ_AUDIO_BEAT_ACTION_BREAK_BEAT 4 +#endif #ifndef VJ_BEAT_SOFT_UNSET #define VJ_BEAT_SOFT_UNSET INT_MIN @@ -144,21 +173,10 @@ enum AB_HIT_KICK = 1, AB_HIT_SNARE = 2, AB_HIT_HAT = 3, - AB_HIT_FULL = 4 + AB_HIT_FULL = 4, + AB_HIT_SCRATCH = 5 }; -static const char *ab_hit_kind_name(int kind) -{ - switch(kind) - { - case AB_HIT_KICK: return "kick"; - case AB_HIT_SNARE: return "snare"; - case AB_HIT_HAT: return "hat"; - case AB_HIT_FULL: return "full"; - default: return "none"; - } -} - static int ab_classify_hit(double kick_score, double snare_score, double hat_score) { if(kick_score >= 0.38 && @@ -189,6 +207,11 @@ static inline int ab_is_body_hit(int hit_kind) hit_kind == AB_HIT_FULL; } +static inline int ab_is_scratch_hit(int hit_kind) +{ + return hit_kind == AB_HIT_SCRATCH; +} + static inline double ab_bound_period_ms(double period_ms) { if(period_ms < (double)VEEJAY_AUDIO_BEAT_MIN_PERIOD_MS) @@ -330,6 +353,12 @@ typedef struct double last_kick_score; double last_snare_score; double last_hat_score; + double last_scratch_score; + double scratch_env; + double scratch_slow; + double scratch_burst_env; + double scratch_last_amount; + int scratch_dir; int last_hit_kind; int blocks_seen; @@ -440,6 +469,9 @@ typedef struct int release_ms; int hold_ms; int priority; + sample_eff_chain *entry_ptr; + int curve_owned; + int curve_mixable; float mod_value; float raw_value; float climax_value; @@ -488,6 +520,95 @@ static volatile int ab_kick_q15 = 0; static volatile int ab_snare_q15 = 0; static volatile int ab_hat_q15 = 0; static volatile int ab_last_hit_kind = AB_HIT_NONE; +static volatile int ab_scratch_q15 = 0; +static volatile int ab_scratch_velocity_q15 = 0; +static volatile int ab_scratch_burst_q15 = 0; +static volatile int ab_scratch_dir = 1; + +typedef struct +{ + int active; + int anchor_valid; + long long anchor_frame; + int repeat_count; + int direction; + int saved_speed; + int fallback_active; + int fallback_dir; + int local_loop_active; + long local_loop_until_ms; + float saved_fps; + float base_fps; + float current_fps; + float target_fps; + float burst_fps; + long fps_last_ms; + long fps_write_last_ms; + long burst_until_ms; + long long loop_lo; + long long loop_hi; + int anchor_scene_id; + long long anchor_cut_frame; + int last_hit_seq; + long last_hit_ms; + float music_groove; + float music_phrase; + float music_climax; + long music_last_ms; + int music_last_hit_seq; + long last_forward_jump_ms; + long last_transport_action_ms; + float tempo_drive; + long tempo_last_hit_ms; + long tempo_prev_interval_ms; + float rhythm_interval_ema_ms; + float rhythm_power_ema; + float rhythm_density; + float rhythm_regularity; + float rhythm_accent; + float rhythm_accel; +} ab_breakbeat_state_t; + +static ab_breakbeat_state_t ab_breakbeat_state; +static volatile long ab_breakbeat_user_override_until_ms = 0; +static void ab_breakbeat_reset_state(void); + +#ifndef AB_BREAKBEAT_USER_PAUSE_OVERRIDE +#define AB_BREAKBEAT_USER_PAUSE_OVERRIDE LONG_MAX +#endif + +#ifndef AB_BREAKBEAT_HIT_QUEUE_SIZE +#define AB_BREAKBEAT_HIT_QUEUE_SIZE 32 +#endif + +typedef struct +{ + int valid; + int seq; + long hit_ms; + long publish_ms; + long block_ms; + float scratch_amount; + float scratch_velocity; + float scratch_burst; + int scratch_dir; + vj_audio_beat_snapshot_t snap; +} ab_breakbeat_hit_event_t; + +static ab_breakbeat_hit_event_t ab_breakbeat_hit_queue[AB_BREAKBEAT_HIT_QUEUE_SIZE]; +static volatile int ab_breakbeat_hit_queue_read = 0; +static volatile int ab_breakbeat_hit_queue_write = 0; +static volatile int ab_breakbeat_hit_queue_lock = 0; + +int vj_audio_beat_get_snapshot(vj_audio_beat_shared_t *s, vj_audio_beat_snapshot_t *dst); +static void ab_breakbeat_hit_queue_clear(void); +static void ab_breakbeat_hit_queue_push(vj_audio_beat_shared_t *s, + const vj_audio_beat_thread_t *t, + int seq, + long hit_ms, + long publish_ms, + long block_ms); +static int ab_breakbeat_hit_queue_pop_after(int consumed_seq, ab_breakbeat_hit_event_t *dst); #ifdef VEEJAY_AUDIO_BEAT_AUTO_DEBUG typedef struct @@ -561,10 +682,14 @@ static inline void ab_publish_l_cached(volatile long *p, long *cache, long v) } } -static inline int ab_publish_transient_q8(double onset_score) +static inline int ab_publish_transient_q8(double transient_norm) { - int v = (int)(onset_score * 256.0); - return v < 0 ? 0 : (v > 65535 ? 65535 : v); + if(transient_norm < 0.0) + transient_norm = 0.0; + else if(transient_norm > 1.0) + transient_norm = 1.0; + + return (int)(transient_norm * 256.0 + 0.5); } static inline int ab_add_i(volatile int *p, int v) @@ -691,6 +816,27 @@ static inline double ab_clampd(double v, double lo, double hi) return v < lo ? lo : (v > hi ? hi : v); } + +static inline double ab_soft_ratio_unit(double env, double slow, double knee, double gain) +{ + double ratio; + double x; + + if(slow < 0.000001) + slow = 0.000001; + + ratio = env / slow; + x = (ratio - knee) * gain; + + if(x <= 0.0) + return 0.0; + + x = x / (1.0 + x); + x = ab_clampd(x, 0.0, 1.0); + + return x * x * (3.0 - 2.0 * x); +} + static inline void ab_ewstat_update(double *mean, double *var, double x, double alpha) { double d; @@ -800,6 +946,208 @@ static inline float ab_from_q15(int v) return (float)v * (1.0f / 32767.0f); } + +static inline void ab_breakbeat_hit_queue_lock_enter(void) +{ +#if defined(__GNUC__) || defined(__clang__) + while(__atomic_exchange_n(&ab_breakbeat_hit_queue_lock, 1, __ATOMIC_ACQUIRE) != 0) +#else + while(__sync_lock_test_and_set(&ab_breakbeat_hit_queue_lock, 1) != 0) +#endif + { + sched_yield(); + } +} + +static inline void ab_breakbeat_hit_queue_lock_leave(void) +{ +#if defined(__GNUC__) || defined(__clang__) + __atomic_store_n(&ab_breakbeat_hit_queue_lock, 0, __ATOMIC_RELEASE); +#else + __sync_lock_release(&ab_breakbeat_hit_queue_lock); +#endif +} + +static void ab_breakbeat_hit_queue_clear(void) +{ + ab_breakbeat_hit_queue_lock_enter(); + memset(ab_breakbeat_hit_queue, 0, sizeof(ab_breakbeat_hit_queue)); + ab_breakbeat_hit_queue_read = 0; + ab_breakbeat_hit_queue_write = 0; + ab_breakbeat_hit_queue_lock_leave(); +} + +static void ab_breakbeat_hit_queue_push(vj_audio_beat_shared_t *s, + const vj_audio_beat_thread_t *t, + int seq, + long hit_ms, + long publish_ms, + long block_ms) +{ + ab_breakbeat_hit_event_t ev; + int w; + int n; + + if(!s || !t || seq <= 0 || ab_load_i(&s->action_mode) != VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) + return; + + memset(&ev, 0, sizeof(ev)); + ev.valid = 1; + ev.seq = seq; + ev.hit_ms = hit_ms > 0 ? hit_ms : publish_ms; + ev.publish_ms = publish_ms > 0 ? publish_ms : ev.hit_ms; + ev.block_ms = block_ms > 0 ? block_ms : 0; + + if(!vj_audio_beat_get_snapshot(s, &ev.snap)) + memset(&ev.snap, 0, sizeof(ev.snap)); + + ev.snap.hit_seq = seq; + ev.snap.last_hit_ms = ev.hit_ms; + ev.snap.beat_age_ms = ev.publish_ms >= ev.hit_ms ? ev.publish_ms - ev.hit_ms : 0; + ev.snap.hit_kind = t->last_hit_kind; + ev.snap.level = ab_from_q15(t->pub_level_q15 == INT_MIN ? 0 : t->pub_level_q15); + ev.snap.envelope = ab_from_q15(t->pub_envelope_q15 == INT_MIN ? 0 : t->pub_envelope_q15); + ev.snap.flux = ab_from_q15(t->pub_flux_q15 == INT_MIN ? 0 : t->pub_flux_q15); + ev.snap.kick = ab_from_q15(ab_load_i(&ab_kick_q15)); + ev.snap.snare = ab_from_q15(ab_load_i(&ab_snare_q15)); + ev.snap.hat = ab_from_q15(ab_load_i(&ab_hat_q15)); + + ev.scratch_amount = ab_from_q15(ab_load_i(&ab_scratch_q15)); + ev.scratch_velocity = ab_from_q15(ab_load_i(&ab_scratch_velocity_q15)); + ev.scratch_burst = ab_from_q15(ab_load_i(&ab_scratch_burst_q15)); + ev.scratch_dir = ab_load_i(&ab_scratch_dir) < 0 ? -1 : 1; + + if(ev.snap.hit_kind == AB_HIT_SCRATCH) + { + ev.snap.transient = ev.scratch_burst; + ev.snap.flux = ev.scratch_velocity; + ev.snap.beat_density = ev.scratch_amount; + ev.snap.beat_pulse = ev.scratch_burst; + ev.snap.beat_gate = ev.scratch_amount > 0.18f ? 1.0f : 0.0f; + } + + ab_breakbeat_hit_queue_lock_enter(); + + w = ab_breakbeat_hit_queue_write; + n = w + 1; + if(n >= AB_BREAKBEAT_HIT_QUEUE_SIZE) + n = 0; + + if(n == ab_breakbeat_hit_queue_read) + { + int r = ab_breakbeat_hit_queue_read + 1; + if(r >= AB_BREAKBEAT_HIT_QUEUE_SIZE) + r = 0; + ab_breakbeat_hit_queue_read = r; + } + + ab_breakbeat_hit_queue[w] = ev; + ab_breakbeat_hit_queue_write = n; + + ab_breakbeat_hit_queue_lock_leave(); +} + +static int ab_breakbeat_hit_queue_pop_after(int consumed_seq, ab_breakbeat_hit_event_t *dst) +{ + int found = 0; + + if(!dst) + return 0; + + ab_breakbeat_hit_queue_lock_enter(); + + while(ab_breakbeat_hit_queue_read != ab_breakbeat_hit_queue_write) + { + int r = ab_breakbeat_hit_queue_read; + ab_breakbeat_hit_event_t ev = ab_breakbeat_hit_queue[r]; + + r++; + if(r >= AB_BREAKBEAT_HIT_QUEUE_SIZE) + r = 0; + ab_breakbeat_hit_queue_read = r; + + if(ev.valid && ev.seq > consumed_seq) + { + *dst = ev; + found = 1; + break; + } + } + + ab_breakbeat_hit_queue_lock_leave(); + return found; +} + + +static inline long ab_effective_snapshot_window_ms(int configured_ms, float bpm, int hit_kind, int gate) +{ + double value; + double period; + double fraction; + double target; + double tempo_weight; + long lo; + long hi; + + if(gate) + { + if(configured_ms < 10) + configured_ms = 10; + else if(configured_ms > 1000) + configured_ms = 1000; + + lo = 24L; + hi = 360L; + } + else + { + if(configured_ms < 20) + configured_ms = 20; + else if(configured_ms > 2000) + configured_ms = 2000; + + lo = 55L; + hi = 900L; + } + + value = (double)configured_ms; + + if(bpm > 1.0f) + { + period = 60000.0 / (double)bpm; + period = ab_bound_period_ms(period); + + if(gate) + fraction = 0.18; + else + fraction = 0.42; + + if(hit_kind == AB_HIT_HAT) + fraction *= 0.72; + else if(hit_kind == AB_HIT_SNARE) + fraction *= 0.92; + else if(hit_kind == AB_HIT_FULL) + fraction *= 1.08; + + target = period * fraction; + + if(target < (double)lo) + target = (double)lo; + else if(target > (double)hi) + target = (double)hi; + + tempo_weight = bpm < 85.0f ? 0.74 : (bpm > 165.0f ? 0.62 : 0.68); + value = value * (1.0 - tempo_weight) + target * tempo_weight; + } + + if(value < (double)lo) + value = (double)lo; + else if(value > (double)hi) + value = (double)hi; + + return (long)(value + 0.5); +} + static inline double ab_sample_s16(const uint8_t *p) { int16_t v = (int16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8)); @@ -827,6 +1175,9 @@ static const char *ab_action_name(int action) case VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX: return "freeze+auto-fx"; + case VJ_AUDIO_BEAT_ACTION_BREAK_BEAT: + return "break-beat"; + default: return "unknown"; } @@ -935,6 +1286,9 @@ void vj_audio_beat_init(vj_audio_beat_shared_t *s, int input_channels) ab_auto_resume_guard_active = 0; ab_auto_debug_last_apply_ms = 0; memset(ab_auto_targets, 0, sizeof(ab_auto_targets)); + memset(&ab_breakbeat_state, 0, sizeof(ab_breakbeat_state)); + ab_breakbeat_hit_queue_clear(); + ab_store_l(&ab_breakbeat_user_override_until_ms, 0); ab_store_i(&s->level_q15, 0); ab_store_i(&s->envelope_q15, 0); @@ -951,6 +1305,10 @@ void vj_audio_beat_init(vj_audio_beat_shared_t *s, int input_channels) ab_store_i(&ab_kick_q15, 0); ab_store_i(&ab_snare_q15, 0); ab_store_i(&ab_hat_q15, 0); + ab_store_i(&ab_scratch_q15, 0); + ab_store_i(&ab_scratch_velocity_q15, 0); + ab_store_i(&ab_scratch_burst_q15, 0); + ab_store_i(&ab_scratch_dir, 1); ab_store_i(&ab_last_hit_kind, AB_HIT_NONE); ab_store_i(&s->initialized, 1); @@ -1010,6 +1368,12 @@ static void ab_thread_reset(vj_audio_beat_thread_t *t) t->last_kick_score = 0.0; t->last_snare_score = 0.0; t->last_hat_score = 0.0; + t->last_scratch_score = 0.0; + t->scratch_env = 0.0; + t->scratch_slow = 0.0; + t->scratch_burst_env = 0.0; + t->scratch_last_amount = 0.0; + t->scratch_dir = 1; t->last_hit_kind = AB_HIT_NONE; t->filter_sample_rate = 0; t->filter_low_alpha = 0.0f; @@ -1065,6 +1429,10 @@ static void ab_clear_published_control(vj_audio_beat_shared_t *s) ab_store_i(&ab_kick_q15, 0); ab_store_i(&ab_snare_q15, 0); ab_store_i(&ab_hat_q15, 0); + ab_store_i(&ab_scratch_q15, 0); + ab_store_i(&ab_scratch_velocity_q15, 0); + ab_store_i(&ab_scratch_burst_q15, 0); + ab_store_i(&ab_scratch_dir, 1); ab_store_i(&ab_last_hit_kind, AB_HIT_NONE); ab_store_i(&s->beat_toggle_q15, 0); ab_store_i(&s->bpm_q8, 0); @@ -1072,66 +1440,6 @@ static void ab_clear_published_control(vj_audio_beat_shared_t *s) ab_store_l(&s->hold_until_ms, 0); } -static void ab_clear_detector_memory(vj_audio_beat_thread_t *t, long cooldown_anchor_ms) -{ - if(!t) - return; - - t->fast_energy = 0.0; - t->slow_energy = 0.0; - t->fast_flux = 0.0; - t->slow_flux = 0.0; - t->envelope = 0.0; - t->last_sample = 0.0; - t->last_log_energy = 0.0; - t->rise_mean = 0.0; - t->rise_var = 0.010; - t->flux_mean = 0.0; - t->flux_var = 0.010; - t->onset_mean = 0.0; - t->onset_var = 0.250; - t->onset_smooth = 0.0; - t->band_low_lp = 0.0; - t->band_mid_lp = 0.0; - t->band_low_env = 0.0; - t->band_mid_env = 0.0; - t->band_high_env = 0.0; - t->band_low_slow = 0.000001; - t->band_mid_slow = 0.000001; - t->band_high_slow = 0.000001; - t->last_hit_ms = cooldown_anchor_ms; - t->tempo_last_hit_ms = 0; - t->blocks_seen = 0; - t->last_interval_ms = 0; - t->tempo_fast_count = 0; - t->tempo_slow_count = 0; - t->last_accept_score = 0.0; - t->last_accept_level = 0.0; - t->last_kick_score = 0.0; - t->last_snare_score = 0.0; - t->last_hat_score = 0.0; - t->last_hit_kind = AB_HIT_NONE; - ab_thread_publish_cache_reset(t); -#ifdef VEEJAY_AUDIO_BEAT_DEBUG - t->dbg_energy = 0.0; - t->dbg_flux = 0.0; - t->dbg_block_level = 0.0; - t->dbg_level = 0.0; - t->dbg_envelope = 0.0; - t->dbg_bass = 0.0; - t->dbg_mid = 0.0; - t->dbg_high = 0.0; - t->dbg_energy_ratio = 0.0; - t->dbg_flux_ratio = 0.0; - t->dbg_score = 0.0; - t->dbg_min_hit_level = 0.0; - t->dbg_min_hit_flux = 0.0; - t->dbg_audible = 0; - t->dbg_transient_present = 0; - t->dbg_hit_candidate = 0; -#endif -} - static int ab_thread_prepare_buffer(vj_audio_beat_thread_t *t) { int min_size; @@ -1898,7 +2206,9 @@ static int ab_analyse_block(vj_audio_beat_shared_t *s, vj_audio_beat_thread_t *t double flux_z; double onset_z; double onset_threshold_z; +#ifdef VEEJAY_AUDIO_BEAT_DEBUG double onset_score; +#endif double level; double transient_norm; double flux_norm; @@ -1935,11 +2245,22 @@ static int ab_analyse_block(vj_audio_beat_shared_t *s, vj_audio_beat_thread_t *t double level_floor; double envelope_floor; double broadband_norm; + double scratch_tone; + double scratch_raw; + double scratch_amount; + double scratch_velocity; + double scratch_burst; + double scratch_rise; + int scratch_candidate; + int scratch_dir; + int breakbeat_mode; int dense_onset; int dense_body_onset; int kick_onset; int snare_onset; +#if defined(VEEJAY_AUDIO_BEAT_DEBUG) || defined(VEEJAY_AUDIO_BEAT_TRACE_REJECTS) const char *reject_reason; +#endif if(!s || !t || !data || bytes <= 0 || t->bytes_per_frame <= 0 || t->channels <= 0) return 0; @@ -2065,13 +2386,9 @@ static int ab_analyse_block(vj_audio_beat_shared_t *s, vj_audio_beat_thread_t *t t->band_mid_slow = t->band_mid_slow * 0.992 + t->band_mid_env * 0.008; t->band_high_slow = t->band_high_slow * 0.992 + t->band_high_env * 0.008; - bass_norm = t->band_low_env / (t->band_low_slow + 0.000001); - mid_norm = t->band_mid_env / (t->band_mid_slow + 0.000001); - high_norm = t->band_high_env / (t->band_high_slow + 0.000001); - - bass_norm = ab_clampd((bass_norm - 1.00) * 1.35, 0.0, 1.0); - mid_norm = ab_clampd((mid_norm - 1.00) * 1.35, 0.0, 1.0); - high_norm = ab_clampd((high_norm - 1.00) * 1.35, 0.0, 1.0); + bass_norm = ab_soft_ratio_unit(t->band_low_env, t->band_low_slow, 1.055, 1.08); + mid_norm = ab_soft_ratio_unit(t->band_mid_env, t->band_mid_slow, 1.070, 1.02); + high_norm = ab_soft_ratio_unit(t->band_high_env, t->band_high_slow, 1.085, 0.98); band_balance = ab_clampd((high_norm - bass_norm + 1.0) * 0.5, 0.0, 1.0); @@ -2147,7 +2464,9 @@ static int ab_analyse_block(vj_audio_beat_shared_t *s, vj_audio_beat_thread_t *t onset_z = (rise_z * 0.45) + (flux_z * 0.55); onset_threshold_z = ab_threshold_to_sigma(threshold); +#ifdef VEEJAY_AUDIO_BEAT_DEBUG onset_score = onset_z * 100.0; +#endif threshold_norm = ((double)threshold - 30.0) / 370.0; @@ -2258,6 +2577,60 @@ static int ab_analyse_block(vj_audio_beat_shared_t *s, vj_audio_beat_thread_t *t broadband_norm = ab_clampd(broadband_norm, 0.0, 1.0); + scratch_tone = + mid_norm * 0.30 + + high_norm * 0.38 + + flux_norm * 0.22 + + flux_boost * 0.14 + + broadband_norm * 0.12 - + bass_norm * 0.16; + + if(scratch_tone < 0.0) + scratch_tone = 0.0; + else if(scratch_tone > 1.0) + scratch_tone = 1.0; + + scratch_raw = + scratch_tone * 0.50 + + flux_norm * 0.20 + + flux_boost * 0.14 + + transient_norm * 0.08 + + level_floor * 0.08; + + scratch_raw = ab_clampd(scratch_raw, 0.0, 1.0); + + t->scratch_env = scratch_raw > t->scratch_env + ? (t->scratch_env * 0.38 + scratch_raw * 0.62) + : (t->scratch_env * 0.86 + scratch_raw * 0.14); + + t->scratch_slow = t->scratch_slow * 0.988 + t->scratch_env * 0.012; + + scratch_rise = t->scratch_env - t->scratch_slow; + if(scratch_rise < 0.0) + scratch_rise = 0.0; + + scratch_burst = scratch_rise * 2.10 + flux_z * 0.055 + flux_ratio_abs * 0.015; + scratch_burst = ab_clampd(scratch_burst, 0.0, 1.0); + + t->scratch_burst_env = scratch_burst > t->scratch_burst_env + ? (t->scratch_burst_env * 0.42 + scratch_burst * 0.58) + : (t->scratch_burst_env * 0.82 + scratch_burst * 0.18); + + scratch_amount = ab_clampd(t->scratch_env * 0.72 + t->scratch_burst_env * 0.28, 0.0, 1.0); + scratch_velocity = ab_clampd((flux_norm * 0.46 + flux_boost * 0.22 + high_norm * 0.18 + mid_norm * 0.10 + scratch_burst * 0.18), 0.0, 1.0); + + if(scratch_amount < 0.10 && t->scratch_last_amount < 0.12) + t->scratch_dir = 1; + else if(scratch_amount > t->scratch_last_amount + 0.035 && + (scratch_velocity > 0.34 || scratch_burst > 0.22)) + t->scratch_dir = -t->scratch_dir; + if(t->scratch_dir == 0) + t->scratch_dir = 1; + scratch_dir = t->scratch_dir; + t->scratch_last_amount = scratch_amount; + + breakbeat_mode = ab_load_i(&s->action_mode) == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT; + dense_onset = audible && ( @@ -2320,6 +2693,19 @@ static int ab_analyse_block(vj_audio_beat_shared_t *s, vj_audio_beat_thread_t *t (mid_norm + high_norm) >= 0.58 && (flux_z >= 0.18 || rise_z >= 0.12); + scratch_candidate = + breakbeat_mode && + settled && + audible && + scratch_amount >= 0.46 && + scratch_velocity >= 0.38 && + t->scratch_burst_env >= 0.24 && + (mid_norm + high_norm + flux_norm) >= 0.66 && + (scratch_amount >= 0.52 || t->scratch_burst_env >= 0.40 || scratch_burst >= 0.42) && + (flux_z >= 0.34 || flux_ratio_abs >= 1.18 || t->scratch_burst_env >= 0.40) && + !(kick_onset && kick_score > scratch_amount * 1.10) && + !(snare_onset && snare_score > scratch_amount * 1.12); + /* * Dense/compressed material is useful as a continuous body/density * lane, but it must not become a machine-gun beat source. Promote @@ -2419,11 +2805,17 @@ static int ab_analyse_block(vj_audio_beat_shared_t *s, vj_audio_beat_thread_t *t band_balance = 0.0; transient_norm = 0.0; flux_norm = 0.0; +#ifdef VEEJAY_AUDIO_BEAT_DEBUG onset_score = 0.0; +#endif kick_score = 0.0; snare_score = 0.0; hat_score = 0.0; broadband_norm = 0.0; + scratch_amount = 0.0; + scratch_velocity = 0.0; + scratch_burst = 0.0; + scratch_candidate = 0; dense_onset = 0; dense_body_onset = 0; kick_onset = 0; @@ -2441,8 +2833,12 @@ static int ab_analyse_block(vj_audio_beat_shared_t *s, vj_audio_beat_thread_t *t ab_publish_i_cached(&ab_kick_q15, &t->pub_kick_q15, ab_q15(kick_score)); ab_publish_i_cached(&ab_snare_q15, &t->pub_snare_q15, ab_q15(snare_score)); ab_publish_i_cached(&ab_hat_q15, &t->pub_hat_q15, ab_q15(hat_score)); + ab_store_i(&ab_scratch_q15, ab_q15(scratch_amount)); + ab_store_i(&ab_scratch_velocity_q15, ab_q15(scratch_velocity)); + ab_store_i(&ab_scratch_burst_q15, ab_q15(scratch_burst)); + ab_store_i(&ab_scratch_dir, scratch_dir < 0 ? -1 : 1); - transient_q8 = ab_publish_transient_q8(onset_score); + transient_q8 = ab_publish_transient_q8(transient_norm); ab_publish_i_cached(&s->transient_norm_q15, &t->pub_transient_norm_q15, ab_q15(transient_norm)); ab_publish_i_cached(&s->transient_q8, &t->pub_transient_q8, transient_q8); @@ -2450,29 +2846,44 @@ static int ab_analyse_block(vj_audio_beat_shared_t *s, vj_audio_beat_thread_t *t hit_candidate = settled && first_hit_ready && audible && - coherent_onset && - (absolute_onset || tempo_onset || dense_body_onset || onset_z >= onset_threshold_z); + ((coherent_onset && + (absolute_onset || tempo_onset || dense_body_onset || onset_z >= onset_threshold_z)) || + scratch_candidate); t->last_kick_score = kick_score; t->last_snare_score = snare_score; t->last_hat_score = hat_score; - t->last_hit_kind = hit_candidate - ? ab_classify_hit(kick_score, snare_score, hat_score) - : AB_HIT_NONE; + t->last_scratch_score = scratch_amount; - if(hit_candidate && dense_body_onset && t->last_hit_kind == AB_HIT_NONE) - t->last_hit_kind = AB_HIT_FULL; + if(hit_candidate && scratch_candidate && + scratch_amount >= kick_score * 1.04 && + scratch_amount >= snare_score * 1.02 && + scratch_amount >= hat_score * 0.92) + { + t->last_hit_kind = AB_HIT_SCRATCH; + } + else + { + t->last_hit_kind = hit_candidate + ? ab_classify_hit(kick_score, snare_score, hat_score) + : AB_HIT_NONE; + if(hit_candidate && dense_body_onset && t->last_hit_kind == AB_HIT_NONE) + t->last_hit_kind = AB_HIT_FULL; + } + +#if defined(VEEJAY_AUDIO_BEAT_DEBUG) || defined(VEEJAY_AUDIO_BEAT_TRACE_REJECTS) reject_reason = !armed ? "arming" : (!settled ? "settling" : (!first_hit_ready ? "first-hit-level" : (!audible ? "silence" : - (!coherent_onset ? "coherence" : - (!absolute_onset && !tempo_onset && !dense_body_onset && onset_z < onset_threshold_z ? "novelty" : "score"))))); + (!coherent_onset && !scratch_candidate ? "coherence" : + (!scratch_candidate && !absolute_onset && !tempo_onset && !dense_body_onset && onset_z < onset_threshold_z ? "novelty" : "score"))))); +#endif stat_alpha = !settled ? 0.055 : (hit_candidate ? 0.0015 : - ((absolute_onset || tempo_onset || dense_body_onset) ? 0.0060 : 0.018)); + ((absolute_onset || tempo_onset || dense_body_onset || scratch_candidate) ? 0.0060 : 0.018)); ab_ewstat_update(&t->rise_mean, &t->rise_var, energy_rise, stat_alpha); ab_ewstat_update(&t->flux_mean, &t->flux_var, flux_feature, stat_alpha); @@ -2597,6 +3008,7 @@ static int ab_analyse_block(vj_audio_beat_shared_t *s, vj_audio_beat_thread_t *t static long ab_effective_cooldown_ms(vj_audio_beat_shared_t *s, const vj_audio_beat_thread_t *t) { long configured; + int action; if(!s) return 180L; @@ -2608,6 +3020,113 @@ static long ab_effective_cooldown_ms(vj_audio_beat_shared_t *s, const vj_audio_b else if(configured > 2000L) configured = 2000L; + action = ab_load_i(&s->action_mode); + + if(action == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) + { + long user_bias = configured; + long musical = user_bias; + int hit_kind = t ? t->last_hit_kind : AB_HIT_NONE; + double hit_confidence = 0.0; + + if(t) + { + hit_confidence = t->last_kick_score; + + if(t->last_snare_score > hit_confidence) + hit_confidence = t->last_snare_score; + + if(t->last_hat_score > hit_confidence) + hit_confidence = t->last_hat_score; + } + + if(hit_kind == AB_HIT_SCRATCH) + { + configured = (long)(36.0 + (1.0 - ab_clampd(t ? t->last_scratch_score : 0.0, 0.0, 1.0)) * 44.0 + 0.5); + if(configured < 28L) + configured = 28L; + else if(configured > 96L) + configured = 96L; + return configured; + } + + if(t && + t->beat_period_ms >= (double)VEEJAY_AUDIO_BEAT_MIN_PERIOD_MS && + t->beat_period_ms <= (double)VEEJAY_AUDIO_BEAT_MAX_PERIOD_MS) + { + double period = t->beat_period_ms; + double frac; + + if(hit_kind == AB_HIT_HAT) + frac = 0.105; + else if(hit_kind == AB_HIT_SNARE) + frac = 0.215; + else if(hit_kind == AB_HIT_KICK || hit_kind == AB_HIT_FULL) + frac = 0.245; + else + frac = 0.230; + + if(hit_confidence > 0.72) + frac -= 0.030; + else if(hit_confidence < 0.36) + frac += 0.045; + + musical = (long)(period * frac + 0.5); + + if(hit_kind == AB_HIT_HAT) + { + if(musical < 24L) + musical = 24L; + else if(musical > 115L) + musical = 115L; + } + else if(period <= 360.0) + { + if(musical < 42L) + musical = 42L; + else if(musical > 165L) + musical = 165L; + } + else if(period <= 760.0) + { + if(musical < 52L) + musical = 52L; + else if(musical > 230L) + musical = 230L; + } + else + { + if(musical < 72L) + musical = 72L; + else if(musical > 360L) + musical = 360L; + } + + configured = (long)((double)musical * 0.72 + (double)user_bias * 0.28 + 0.5); + } + else + { + configured = (long)((double)configured * 0.56 + 0.5); + } + + if(hit_kind == AB_HIT_HAT) + { + if(configured < 24L) + configured = 24L; + else if(configured > 130L) + configured = 130L; + } + else + { + if(configured < 40L) + configured = 40L; + else if(configured > 390L) + configured = 390L; + } + + return configured; + } + if(t && t->beat_period_ms >= (double)VEEJAY_AUDIO_BEAT_MIN_PERIOD_MS && t->beat_period_ms <= (double)VEEJAY_AUDIO_BEAT_MAX_PERIOD_MS) @@ -2655,6 +3174,9 @@ static const char *ab_update_dynamic_bpm(vj_audio_beat_shared_t *s, if(!s || !t) return reason; + if(ab_is_scratch_hit(hit_kind)) + return reason; + if(interval < VEEJAY_AUDIO_BEAT_MIN_PERIOD_MS || interval > VEEJAY_AUDIO_BEAT_MAX_PERIOD_MS) return reason; @@ -2850,6 +3372,7 @@ void *vj_audio_beat_thread(void *arg) ab_record_ring_clear(s); ab_clear_published_control(s); + ab_breakbeat_hit_queue_clear(); if(s->sync) { @@ -3042,44 +3565,81 @@ beat_process_hit: if(hit) { long cooldown = ab_effective_cooldown_ms(s, &t); + long block_ms = 0; + long hit_ms = now; long since_last; + if(t.bytes_per_frame > 0 && t.sample_rate > 0 && got > 0) + { + long frames = (long)(got / t.bytes_per_frame); + block_ms = (frames * 1000L + (long)t.sample_rate / 2L) / (long)t.sample_rate; + if(block_ms > 0) + hit_ms = now - (block_ms / 2L); + } + + if(hit_ms <= 0) + hit_ms = now; + if(t.last_hit_ms > 0 && hit_ms <= t.last_hit_ms) + hit_ms = t.last_hit_ms + 1L; + if(hit_ms > now) + hit_ms = now; + if(cooldown < 1) cooldown = 1; - since_last = t.last_hit_ms > 0 ? now - t.last_hit_ms : 2147483647L; + since_last = t.last_hit_ms > 0 ? hit_ms - t.last_hit_ms : 2147483647L; + if(since_last < 0) + since_last = 0; if(since_last >= cooldown) { - long tempo_prev = t.tempo_last_hit_ms; - long interval = tempo_prev > 0 ? now - tempo_prev : 0; + long tempo_prev; + long interval; int hit_kind = t.last_hit_kind; - int advance_tempo_anchor = tempo_prev <= 0 ? 1 : 0; - double hit_confidence = t.last_kick_score; + int advance_tempo_anchor; + int hit_seq; + int scratch_hit = hit_kind == AB_HIT_SCRATCH; + double hit_confidence = scratch_hit ? t.last_scratch_score : t.last_kick_score; - if(t.last_snare_score > hit_confidence) - hit_confidence = t.last_snare_score; + tempo_prev = t.tempo_last_hit_ms; + interval = tempo_prev > 0 ? hit_ms - tempo_prev : 0; + if(interval < 0) + interval = 0; - if(t.last_hat_score > hit_confidence) - hit_confidence = t.last_hat_score; + advance_tempo_anchor = tempo_prev <= 0 ? 1 : 0; - if(tempo_prev > 0) - (void) ab_update_dynamic_bpm(s, &t, interval, hit_kind, hit_confidence, &advance_tempo_anchor); + if(!scratch_hit) + { + if(t.last_snare_score > hit_confidence) + hit_confidence = t.last_snare_score; - if(advance_tempo_anchor) - t.tempo_last_hit_ms = now; + if(t.last_hat_score > hit_confidence) + hit_confidence = t.last_hat_score; - t.last_hit_ms = now; + if(tempo_prev > 0) + (void) ab_update_dynamic_bpm(s, &t, interval, hit_kind, hit_confidence, &advance_tempo_anchor); + + if(advance_tempo_anchor) + t.tempo_last_hit_ms = hit_ms; + } + else + { + advance_tempo_anchor = 0; + } + + t.last_hit_ms = hit_ms; t.last_accept_score = hit_confidence; t.last_accept_level = ab_from_q15(t.pub_level_q15 == INT_MIN ? 0 : t.pub_level_q15); - ab_store_l(&s->last_hit_ms, now); + ab_store_l(&s->last_hit_ms, hit_ms); ab_store_i(&ab_last_hit_kind, hit_kind); ab_add_l(&s->hits, 1); - ab_add_i(&s->hit_seq, 1); + hit_seq = ab_add_i(&s->hit_seq, 1); t.beat_toggle_state = t.beat_toggle_state > 0 ? 0 : 32767; ab_store_i(&s->beat_toggle_q15, t.beat_toggle_state); + + ab_breakbeat_hit_queue_push(s, &t, hit_seq, hit_ms, now, block_ms); } } @@ -3142,6 +3702,7 @@ int vj_audio_beat_enable(vj_audio_beat_shared_t *s) } ab_store_i(&s->consumed_seq, ab_load_i(&s->hit_seq)); + ab_breakbeat_hit_queue_clear(); ab_add_i(&s->reset_seq, 1); ab_store_i(&s->enabled, 1); @@ -3163,8 +3724,21 @@ int vj_audio_beat_disable(vj_audio_beat_shared_t *s) ab_store_i(&s->consumed_seq, ab_load_i(&s->hit_seq)); ab_clear_published_control(s); + ab_breakbeat_hit_queue_clear(); ab_record_ring_clear(s); + if(ab_breakbeat_state.active) + { + veejay_msg(VEEJAY_MSG_DEBUG, + "[AUDIO-BEAT] disable requested while break-beat owns transport; release deferred to transport-aware path"); + } + else + { + ab_breakbeat_reset_state(); + } + + ab_store_l(&ab_breakbeat_user_override_until_ms, 0); + veejay_msg(VEEJAY_MSG_INFO, "[AUDIO-BEAT] analysis disabled"); ab_log_config(s, "on disable"); return 1; @@ -3193,12 +3767,3698 @@ static void ab_resume_from_consumer(veejay_t *v, vj_audio_beat_shared_t *s) if(speed == 0) speed = 1; - veejay_set_speed(v, speed, 0); + ab_set_speed_from_beat(v, speed, 0); ab_store_i(&s->paused_by_beat, 0); ab_store_l(&s->hold_until_ms, 0); } +int vj_audio_beat_resume_if_due(veejay_t *v, vj_audio_beat_shared_t *s); + +static inline int ab_freeze_transport_uses_gate(vj_audio_beat_shared_t *s) +{ + int source = ab_sync_source(s); + + return source == VJ_AUDIO_SYNC_SOURCE_JACK || + source == VJ_AUDIO_SYNC_SOURCE_WAV_FILE; +} + +static inline long ab_freeze_window_ms(vj_audio_beat_shared_t *s, int gate) +{ + int ms = ab_load_i(&s->freeze_ms); + + (void)gate; + + if(ms < 20) + ms = 20; + else if(ms > 1000) + ms = 1000; + + return (long)ms; +} + +static int ab_freeze_pause_from_consumer(veejay_t *v, vj_audio_beat_shared_t *s) +{ + int speed; + + if(!v || !v->settings || !s) + return 0; + + speed = v->settings->current_playback_speed; + if(speed == 0) + return 0; + + ab_store_i(&s->resume_speed, speed); + v->settings->previous_playback_speed = speed; + ab_store_i(&s->paused_by_beat, 1); + ab_store_l(&s->hold_until_ms, 0); + + ab_set_speed_from_beat(v, 0, 0); + return 1; +} + +static int ab_freeze_gate_consume(veejay_t *v, vj_audio_beat_shared_t *s, + long now, int hit_seq, int consumed_seq) +{ + long hold_until; + + if(hit_seq != consumed_seq) + { + long open_ms = ab_freeze_window_ms(s, 1); + + ab_store_i(&s->consumed_seq, hit_seq); + + if(ab_load_i(&s->paused_by_beat)) + ab_resume_from_consumer(v, s); + + ab_store_l(&s->hold_until_ms, now + open_ms); + return 1; + } + + hold_until = ab_load_l(&s->hold_until_ms); + + if(hold_until > 0 && now < hold_until) + return 0; + + if(ab_load_i(&s->paused_by_beat)) + return 0; + + return ab_freeze_pause_from_consumer(v, s); +} + +static int ab_freeze_hit_pause_consume(veejay_t *v, vj_audio_beat_shared_t *s, + long now, int hit_seq, int consumed_seq) +{ + long freeze; + + if(ab_load_i(&s->paused_by_beat)) + vj_audio_beat_resume_if_due(v, s); + + if(hit_seq == consumed_seq) + return 0; + + freeze = ab_freeze_window_ms(s, 0); + ab_store_i(&s->consumed_seq, hit_seq); + + if(ab_load_i(&s->paused_by_beat)) + { + ab_store_l(&s->hold_until_ms, now + freeze); + return 1; + } + + if(v->settings->current_playback_speed != 0) + { + int speed = v->settings->current_playback_speed; + + ab_store_i(&s->resume_speed, speed); + v->settings->previous_playback_speed = speed; + ab_store_i(&s->paused_by_beat, 1); + ab_store_l(&s->hold_until_ms, now + freeze); + + ab_set_speed_from_beat(v, 0, 0); + return 1; + } + + return 0; +} + +static void ab_breakbeat_reset_state(void) +{ + memset(&ab_breakbeat_state, 0, sizeof(ab_breakbeat_state)); + ab_breakbeat_hit_queue_clear(); +} + +static inline long ab_breakbeat_param_ms(vj_audio_beat_shared_t *s, volatile int *field, int lo, int hi) +{ + int ms = ab_load_i(field); + + (void)s; + + if(ms < lo) + ms = lo; + else if(ms > hi) + ms = hi; + + return (long)ms; +} + +static inline int ab_breakbeat_current_sfd(veejay_t *v) +{ + int sfd = 1; + + if(v && v->settings) + { + sfd = atomic_load_int(&v->settings->audio_slice_len); + + if(sfd < 1) + sfd = v->settings->sfd; + + if(sfd < 1) + { + if(v->uc && v->uc->playback_mode == VJ_PLAYBACK_MODE_SAMPLE) + { + int id = v->uc->sample_id; + if(id > 0 && sample_exists(id)) + sfd = sample_get_framedup(id); + } + else if(v->sfd > 0) + sfd = v->sfd; + } + } + + if(sfd < 1) + sfd = 1; + else if(sfd > 64) + sfd = 64; + + return sfd; +} + +static inline float ab_breakbeat_sfd_scale(veejay_t *v) +{ + int sfd = ab_breakbeat_current_sfd(v); + + return sfd > 1 ? (1.0f / (float)sfd) : 1.0f; +} + +static inline int ab_breakbeat_scale_speed_for_sfd(veejay_t *v, int speed) +{ + int sfd = ab_breakbeat_current_sfd(v); + int sign; + int mag; + + if(sfd <= 1 || speed == 0) + return speed; + + sign = speed < 0 ? -1 : 1; + mag = speed < 0 ? -speed : speed; + mag = (mag + sfd - 1) / sfd; + + if(mag < 1) + mag = 1; + + return sign * mag; +} + +static inline double ab_breakbeat_video_fps(void) +{ + int q16 = ab_load_i(&ab_auto_video_fps_q16); + + if(q16 <= 0) + return 25.0; + + return (double)q16 * (1.0 / 65536.0); +} + +static inline float ab_breakbeat_runtime_fps(veejay_t *v) +{ + float fps = 0.0f; + + if(v && v->settings) + fps = v->settings->output_fps; + + if(fps <= 0.01f) + fps = (float)ab_breakbeat_video_fps(); + if(fps <= 0.01f) + fps = 25.0f; + + return fps; +} + +static inline float ab_breakbeat_base_fps(veejay_t *v) +{ + float fps = ab_breakbeat_state.base_fps; + + if(fps <= 0.01f) + fps = (float)ab_breakbeat_video_fps(); + if(fps <= 0.01f) + fps = ab_breakbeat_runtime_fps(v); + if(fps <= 0.01f) + fps = 25.0f; + + if(fps > 60.0f) + fps = 60.0f; + + fps *= ab_breakbeat_sfd_scale(v); + + if(fps < 1.0f) + fps = 1.0f; + + return fps; +} + +static inline float ab_breakbeat_clamp_fps(float fps) +{ + if(fps < 5.0f) + fps = 5.0f; + else if(fps > 240.0f) + fps = 240.0f; + + return fps; +} + +static inline float ab_breakbeat_max_effect_fps(veejay_t *v) +{ + float scale = ab_breakbeat_sfd_scale(v); + float base = ab_breakbeat_base_fps(v); + float floor_fps = 42.0f * scale; + float ceiling_fps = 96.0f * scale; + float max_fps = base * 3.35f; + + if(floor_fps < 5.0f) + floor_fps = 5.0f; + if(ceiling_fps < floor_fps) + ceiling_fps = floor_fps; + + if(max_fps < floor_fps) + max_fps = floor_fps; + else if(max_fps > ceiling_fps) + max_fps = ceiling_fps; + + return max_fps; +} + +static inline float ab_breakbeat_clamp_effect_fps(veejay_t *v, float fps) +{ + float max_fps = ab_breakbeat_max_effect_fps(v); + + if(fps < 5.0f) + fps = 5.0f; + else if(fps > max_fps) + fps = max_fps; + + return fps; +} + +static inline void ab_breakbeat_set_fps_immediate(veejay_t *v, float fps) +{ + long now; + + if(!v || !v->settings) + return; + + fps = ab_breakbeat_clamp_fps(fps); + now = ab_now_ms(); + + if(fabs((double)ab_breakbeat_runtime_fps(v) - (double)fps) >= 0.05) + veejay_set_framerate(v, fps); + + ab_breakbeat_state.current_fps = fps; + ab_breakbeat_state.target_fps = fps; + ab_breakbeat_state.fps_last_ms = now; + ab_breakbeat_state.fps_write_last_ms = now; +} + +static inline void ab_breakbeat_apply_fps(veejay_t *v, float fps) +{ + long now; + long dt; + long since_write; + float runtime; + float cur; + float diff; + float adiff; + float rate; + float step; + float base; + float runtime_delta; + + if(!v || !v->settings) + return; + + fps = ab_breakbeat_clamp_effect_fps(v, fps); + now = ab_now_ms(); + runtime = ab_breakbeat_runtime_fps(v); + + cur = ab_breakbeat_state.current_fps; + if(cur <= 0.01f) + cur = runtime; + if(cur <= 0.01f) + cur = ab_breakbeat_base_fps(v); + + if(ab_breakbeat_state.fps_write_last_ms <= 0 || + (fabs((double)runtime - (double)cur) > 8.0 && + (now - ab_breakbeat_state.fps_write_last_ms) > 120)) + cur = runtime; + + dt = ab_breakbeat_state.fps_last_ms > 0 ? now - ab_breakbeat_state.fps_last_ms : 16L; + if(dt < 1) + dt = 1; + else if(dt > 160) + dt = 160; + + diff = fps - cur; + adiff = diff < 0.0f ? -diff : diff; + ab_breakbeat_state.target_fps = fps; + + if(adiff >= 0.05f) + { + base = ab_breakbeat_base_fps(v); + rate = diff > 0.0f ? + 42.0f + adiff * 1.35f + ab_breakbeat_state.tempo_drive * 90.0f : + 20.0f + adiff * 0.82f; + + if(diff > 0.0f && fps > base * 1.35f) + rate += 28.0f + ab_breakbeat_state.tempo_drive * 48.0f; + else if(diff < 0.0f && fps < base * 0.48f) + rate += 18.0f; + + if(diff > 0.0f && cur < base * 0.58f && fps > base * 1.05f) + { + float seed = base * (0.58f + ab_breakbeat_state.tempo_drive * 0.24f); + if(seed > fps) + seed = fps; + if(seed > cur) + cur = seed; + diff = fps - cur; + adiff = diff < 0.0f ? -diff : diff; + } + + if(diff > 0.0f) + { + if(rate > 240.0f) + rate = 240.0f; + } + else if(rate > 110.0f) + { + rate = 110.0f; + } + + if(rate < 8.0f) + rate = 8.0f; + + step = rate * ((float)dt * 0.001f); + + if(step > adiff) + step = adiff; + + cur += diff > 0.0f ? step : -step; + } + else + { + cur = fps; + } + + cur = ab_breakbeat_clamp_effect_fps(v, cur); + since_write = ab_breakbeat_state.fps_write_last_ms > 0 ? now - ab_breakbeat_state.fps_write_last_ms : 1000L; + runtime_delta = (float)fabs((double)runtime - (double)cur); + + if(runtime_delta >= 0.15f && + (since_write >= 33L || runtime_delta >= 4.0f || fabs((double)fps - (double)cur) < 0.08)) + { + veejay_set_framerate(v, cur); + ab_breakbeat_state.fps_write_last_ms = now; + } + + ab_breakbeat_state.current_fps = cur; + ab_breakbeat_state.fps_last_ms = now; +} + +static inline void ab_breakbeat_restore_fps(veejay_t *v) +{ + if(ab_breakbeat_state.saved_fps > 0.01f) + ab_breakbeat_set_fps_immediate(v, ab_breakbeat_state.saved_fps); +} + +static inline int ab_breakbeat_base_speed(veejay_t *v, vj_audio_beat_shared_t *s) +{ + int speed = 0; + + if(v && v->settings) + speed = v->settings->current_playback_speed; + + if(speed == 0) + speed = ab_breakbeat_state.saved_speed; + if(speed == 0) + speed = ab_load_i(&s->resume_speed); + if(speed == 0) + speed = 1; + if(speed < 0) + speed = -speed; + if(speed < 1) + speed = 1; + + return speed; +} + +static inline int ab_breakbeat_user_pause_override_active(void) +{ + return ab_load_l(&ab_breakbeat_user_override_until_ms) == AB_BREAKBEAT_USER_PAUSE_OVERRIDE; +} + +static int ab_breakbeat_respect_user_pause(veejay_t *v, vj_audio_beat_shared_t *s, int hit_seq) +{ + if(!v || !v->settings || !s) + return 0; + + if(v->settings->current_playback_speed != 0) + return 0; + + if(ab_load_i(&s->paused_by_beat)) + return 0; + + if(hit_seq < 0) + hit_seq = ab_load_i(&s->hit_seq); + + if(ab_breakbeat_state.active) + ab_breakbeat_restore_fps(v); + + ab_breakbeat_reset_state(); + + ab_store_i(&s->paused_by_beat, 0); + ab_store_l(&s->hold_until_ms, 0); + ab_store_i(&s->resume_speed, 0); + ab_store_i(&s->consumed_seq, hit_seq); + ab_store_l(&ab_breakbeat_user_override_until_ms, AB_BREAKBEAT_USER_PAUSE_OVERRIDE); + + return 1; +} + +static int ab_breakbeat_detect_user_resume(veejay_t *v, vj_audio_beat_shared_t *s, long now, int hit_seq) +{ + int speed; + + if(!v || !v->settings || !s) + return 0; + + if(ab_load_l(&ab_breakbeat_user_override_until_ms) != AB_BREAKBEAT_USER_PAUSE_OVERRIDE) + return 0; + + speed = v->settings->current_playback_speed; + if(speed == 0) + return 0; + + if(hit_seq < 0) + hit_seq = ab_load_i(&s->hit_seq); + + ab_breakbeat_reset_state(); + ab_store_i(&s->paused_by_beat, 0); + ab_store_l(&s->hold_until_ms, 0); + ab_store_i(&s->resume_speed, speed); + ab_store_i(&s->consumed_seq, hit_seq); + ab_store_l(&ab_breakbeat_user_override_until_ms, now + 220L); + + return 1; +} + +static inline long long ab_breakbeat_current_frame(veejay_t *v) +{ + long long frame = 0; + + if(v && v->settings) + frame = atomic_load_long_long(&v->settings->current_frame_num); + + if(frame < 0) + frame = 0; + + return frame; +} + +static inline int ab_breakbeat_bounds(veejay_t *v, long long *lo, long long *hi) +{ + long long b_lo = 0; + long long b_hi = LONG_MAX; + + if(v && v->uc && v->uc->playback_mode == VJ_PLAYBACK_MODE_SAMPLE) + { + int id = v->uc->sample_id; + + if(id > 0 && sample_exists(id)) + { + long start = sample_get_startFrame(id); + long end = sample_get_endFrame(id); + + if(end >= start) + { + b_lo = (long long)start; + b_hi = (long long)end; + + if(lo) + *lo = b_lo; + if(hi) + *hi = b_hi; + + return 1; + } + } + } + + if(v && v->settings) + { + long long min_frame = atomic_load_long_long(&v->settings->min_frame_num); + long long max_frame = atomic_load_long_long(&v->settings->max_frame_num); + + if(max_frame >= min_frame) + { + b_lo = min_frame; + b_hi = max_frame; + + if(b_lo < 0) + b_lo = 0; + } + } + + if(lo) + *lo = b_lo; + if(hi) + *hi = b_hi; + + return b_hi > b_lo; +} + +static inline long long ab_breakbeat_clamp_frame(veejay_t *v, long long frame) +{ + long long lo = 0; + long long hi = LONG_MAX; + + ab_breakbeat_bounds(v, &lo, &hi); + + if(frame < lo) + frame = lo; + else if(frame > hi) + frame = hi; + + return frame; +} + +static inline int ab_breakbeat_span_frames(veejay_t *v) +{ + long long lo = 0; + long long hi = 0; + long long span; + + if(!ab_breakbeat_bounds(v, &lo, &hi)) + return 0; + + span = hi - lo + 1; + + if(span <= 0) + return 0; + if(span > INT_MAX) + return INT_MAX; + + return (int)span; +} + +static inline long long ab_breakbeat_frame_from_hit_time(veejay_t *v, + vj_audio_beat_shared_t *s, + long hit_ms, + long now) +{ + long long frame = ab_breakbeat_current_frame(v); + long delta_ms; + int speed = 1; + int dir = 1; + int mag; + double fps; + long long delta_frames; + + if(hit_ms <= 0 || now <= hit_ms) + return ab_breakbeat_clamp_frame(v, frame); + + if(v && v->settings) + speed = v->settings->current_playback_speed; + else if(ab_breakbeat_state.direction != 0) + speed = ab_breakbeat_state.direction; + else if(s) + speed = ab_load_i(&s->resume_speed); + + if(speed == 0) + return ab_breakbeat_clamp_frame(v, frame); + + if(speed < 0) + { + dir = -1; + mag = -speed; + } + else + { + dir = 1; + mag = speed; + } + + if(mag < 1) + mag = 1; + else if(mag > 16) + mag = 16; + + fps = (double)ab_breakbeat_runtime_fps(v); + if(fps < 1.0) + fps = ab_breakbeat_video_fps(); + if(fps < 1.0) + fps = 25.0; + + delta_ms = now - hit_ms; + if(delta_ms > 1000L) + delta_ms = 1000L; + + delta_frames = (long long)(((double)delta_ms * fps * (double)mag) / 1000.0 + 0.5); + + if(dir < 0) + frame += delta_frames; + else + frame -= delta_frames; + + return ab_breakbeat_clamp_frame(v, frame); +} + + +static inline long long ab_llabs(long long v) +{ + return v < 0 ? -v : v; +} + +static inline int ab_breakbeat_scene_strong(vj_scene_detect_t *sc) +{ + int score; + + if(!sc || !atomic_load_int(&sc->valid)) + return 0; + + score = atomic_load_int(&sc->cut_score_q15); + + return atomic_load_int(&sc->hard_cut) || score >= 24576; +} + +static inline int ab_breakbeat_scene_snap_window(const vj_audio_beat_snapshot_t *snap) +{ + int win = 1; + + if(snap) + { + if(snap->transient > 0.72f || snap->kick > 0.70f || snap->snare > 0.70f) + win = 2; + if(snap->hit_kind == AB_HIT_HAT && snap->high < 0.80f) + win = 1; + } + + if(ab_breakbeat_video_fps() > 50.0 && win < 2) + win = 2; + + return win; +} + +static inline long long ab_breakbeat_scene_last_cut_frame(veejay_t *v) +{ + if(!v || !v->settings || !atomic_load_int(&v->settings->scene_detect.valid)) + return -1; + + if(!ab_breakbeat_scene_strong(&v->settings->scene_detect)) + return -1; + + return atomic_load_long_long(&v->settings->scene_detect.last_cut_frame); +} + +static inline int ab_breakbeat_scene_id_for_frame(veejay_t *v, long long frame) +{ + vj_scene_detect_t *sc; + int id; + long long cut; + + if(!v || !v->settings) + return 0; + + sc = &v->settings->scene_detect; + if(!atomic_load_int(&sc->valid)) + return 0; + + id = atomic_load_int(&sc->scene_id); + cut = atomic_load_long_long(&sc->last_cut_frame); + + if(id <= 0) + return 0; + + if(cut >= 0 && frame < cut && id > 1) + id--; + + return id; +} + +static inline long long ab_breakbeat_scene_refine_frame(veejay_t *v, + long long frame, + const vj_audio_beat_snapshot_t *snap) +{ + vj_scene_detect_t *sc; + long long cut; + long long d; + int win; + + if(!v || !v->settings) + return ab_breakbeat_clamp_frame(v, frame); + + sc = &v->settings->scene_detect; + if(!ab_breakbeat_scene_strong(sc)) + return ab_breakbeat_clamp_frame(v, frame); + + cut = atomic_load_long_long(&sc->last_cut_frame); + if(cut < 0) + return ab_breakbeat_clamp_frame(v, frame); + + win = ab_breakbeat_scene_snap_window(snap); + d = ab_llabs(frame - cut); + + if(d <= (long long)win) + frame = cut; + + return ab_breakbeat_clamp_frame(v, frame); +} + +static inline long long ab_breakbeat_scene_guard_target(veejay_t *v, + long long from, + long long target, + const vj_audio_beat_snapshot_t *snap, + int forward_jump) +{ + vj_scene_detect_t *sc; + long long cut; + long long lo; + long long hi; + + (void)snap; + + if(!v || !v->settings) + return target; + + sc = &v->settings->scene_detect; + if(!ab_breakbeat_scene_strong(sc)) + return target; + + cut = atomic_load_long_long(&sc->last_cut_frame); + if(cut < 0) + return target; + + if(from <= target) + { + lo = from; + hi = target; + } + else + { + lo = target; + hi = from; + } + + if(cut <= lo || cut >= hi) + return target; + + if(forward_jump) + return cut; + + if(target >= from) + return cut; + + return cut > 0 ? cut - 1 : cut; +} + +static inline void ab_breakbeat_scene_guard_loop(veejay_t *v, + long long center, + long long *a, + long long *b) +{ + vj_scene_detect_t *sc; + long long cut; + + if(!v || !v->settings || !a || !b) + return; + + sc = &v->settings->scene_detect; + if(!ab_breakbeat_scene_strong(sc)) + return; + + cut = atomic_load_long_long(&sc->last_cut_frame); + if(cut < 0) + return; + + if(cut <= *a || cut > *b) + return; + + if(center >= cut) + *a = cut; + else + *b = cut - 1; + + if(*b <= *a) + { + *a = center; + *b = center; + } +} + +static inline int ab_breakbeat_is_sample(veejay_t *v) +{ + return v && v->uc && v->uc->playback_mode == VJ_PLAYBACK_MODE_SAMPLE; +} + +static inline int ab_breakbeat_sample_looptype(veejay_t *v) +{ + if(ab_breakbeat_is_sample(v)) + { + int id = v->uc->sample_id; + + if(id > 0 && sample_exists(id)) + return sample_get_looptype(id); + } + + return 1; +} + +static inline long long ab_breakbeat_mod_range(long long value, long long lo, long long hi) +{ + long long span = hi - lo + 1; + long long rel; + + if(span <= 1) + return lo; + + rel = (value - lo) % span; + if(rel < 0) + rel += span; + + return lo + rel; +} + +static inline long long ab_breakbeat_reflect_range(long long value, long long lo, long long hi, int *dir) +{ + long long span = hi - lo; + long long period; + long long rel; + + if(span <= 0) + return lo; + + period = span * 2; + rel = (value - lo) % period; + if(rel < 0) + rel += period; + + if(rel > span) + { + if(dir) + *dir = -*dir; + return hi - (rel - span); + } + + return lo + rel; +} + +static inline long long ab_breakbeat_project_frame(veejay_t *v, long long frame, int *dir, int *park) +{ + long long lo = 0; + long long hi = 0; + + if(park) + *park = 0; + + if(!ab_breakbeat_bounds(v, &lo, &hi)) + return frame; + + if(frame >= lo && frame <= hi) + return frame; + + if(ab_breakbeat_is_sample(v)) + { + switch(ab_breakbeat_sample_looptype(v)) + { + case 1: + return ab_breakbeat_mod_range(frame, lo, hi); + + case 2: + return ab_breakbeat_reflect_range(frame, lo, hi, dir); + + case 3: + return ab_breakbeat_mod_range(frame, lo, hi); + + default: + if(park) + *park = 1; + return frame < lo ? lo : hi; + } + } + + return frame < lo ? lo : hi; +} + +static inline float ab_breakbeat_clampf(float v) +{ + if(v < 0.0f) + return 0.0f; + if(v > 1.0f) + return 1.0f; + return v; +} + +static inline float ab_breakbeat_groove_drive(void) +{ + float v = ab_auto_groove_level; + + if(v < ab_breakbeat_state.music_groove) + v = ab_breakbeat_state.music_groove; + + return ab_breakbeat_clampf(v); +} + +static inline float ab_breakbeat_phrase_drive(void) +{ + float v = ab_auto_phrase_level; + + if(v < ab_breakbeat_state.music_phrase) + v = ab_breakbeat_state.music_phrase; + + return ab_breakbeat_clampf(v); +} + +static inline float ab_breakbeat_climax_drive(void) +{ + float auto_v = ab_auto_climax_level * 0.62f + + ab_auto_groove_level * 0.26f + + ab_auto_phrase_level * 0.12f; + float local_v = ab_breakbeat_state.music_climax * 0.68f + + ab_breakbeat_state.music_phrase * 0.22f + + ab_breakbeat_state.music_groove * 0.10f; + float v = auto_v > local_v ? auto_v : local_v; + + return ab_breakbeat_clampf(v); +} + +static inline float ab_breakbeat_tonal_bias(const vj_audio_beat_snapshot_t *snap) +{ + float body; + float tonal; + float bias; + + if(!snap) + return 0.0f; + + body = snap->kick > snap->snare ? snap->kick : snap->snare; + if(body < snap->bass * 0.58f) + body = snap->bass * 0.58f; + + tonal = snap->mid > snap->high ? snap->mid : snap->high; + if(tonal < snap->hat * 0.72f) + tonal = snap->hat * 0.72f; + + bias = (tonal - body * 1.06f) * (1.0f / 0.62f); + + return ab_breakbeat_clampf(bias); +} + +static inline float ab_breakbeat_percussive_drive(const vj_audio_beat_snapshot_t *snap, int hit_kind) +{ + float body; + float conf; + float tonal; + + if(!snap) + return 0.0f; + + body = snap->kick > snap->snare ? snap->kick : snap->snare; + + if(hit_kind == AB_HIT_FULL && body < snap->bass * 0.66f) + body = snap->bass * 0.66f; + + tonal = ab_breakbeat_tonal_bias(snap); + + conf = body * 0.54f + + snap->transient * 0.25f + + snap->beat_gate * 0.10f + + snap->flux * 0.07f + + snap->bass * 0.04f; + + if(hit_kind == AB_HIT_KICK) + conf += snap->kick * 0.13f; + else if(hit_kind == AB_HIT_SNARE) + conf += snap->snare * 0.13f; + else if(hit_kind == AB_HIT_FULL && body < 0.30f) + conf *= 0.78f; + else if(hit_kind == AB_HIT_HAT) + conf *= 0.42f + snap->transient * 0.18f; + + if(tonal > 0.18f && body < 0.40f) + conf *= 1.0f - tonal * (0.42f + (0.40f - body) * 0.55f); + + if(snap->envelope > snap->level * 1.12f && + snap->transient < 0.58f && + body < 0.36f) + conf *= 0.68f; + + if(snap->transient > 0.74f && snap->flux > 0.68f && body > 0.28f) + conf += 0.08f; + + return ab_breakbeat_clampf(conf); +} + +static inline float ab_breakbeat_velocity(const vj_audio_beat_snapshot_t *snap) +{ + float body; + float hat_drive; + float v; + + if(!snap) + return 0.0f; + + body = snap->kick > snap->snare ? snap->kick : snap->snare; + hat_drive = snap->hat * (snap->hit_kind == AB_HIT_HAT ? 0.46f : 0.20f); + + v = body * 0.42f + + snap->transient * 0.31f + + snap->flux * 0.14f + + snap->level * 0.09f + + hat_drive * 0.04f; + + if(snap->hit_kind == AB_HIT_HAT) + v *= 0.66f + snap->transient * 0.16f; + + if(v < 0.0f) + v = 0.0f; + else if(v > 1.0f) + v = 1.0f; + + return v; +} + +static inline float ab_breakbeat_activity(const vj_audio_beat_snapshot_t *snap) +{ + float activity; + + if(!snap) + return 0.0f; + + activity = snap->level; + if(activity < snap->envelope) + activity = snap->envelope; + if(activity < snap->transient) + activity = snap->transient; + if(activity < snap->flux) + activity = snap->flux; + if(activity < snap->bass) + activity = snap->bass; + if(activity < snap->mid) + activity = snap->mid; + if(activity < snap->high) + activity = snap->high; + + return activity; +} + +static inline float ab_breakbeat_pace_drive_from_interval(const vj_audio_beat_snapshot_t *snap, long interval_ms) +{ + float drive = 0.0f; + + if(snap && snap->bpm > 1.0f) + drive = ((float)snap->bpm - 70.0f) * (1.0f / 120.0f); + + if(interval_ms > 0) + { + float interval_drive = (900.0f - (float)interval_ms) * (1.0f / 900.0f); + + if(drive < interval_drive) + drive = interval_drive; + } + + if(snap && snap->bpm > 1.0f && snap->bpm <= 125.0f && drive > 0.36f) + drive = 0.36f; + + return ab_breakbeat_clampf(drive); +} + +static inline float ab_breakbeat_pace_drive(const vj_audio_beat_snapshot_t *snap) +{ + float drive; + + if(!snap) + return 0.0f; + + drive = ab_breakbeat_pace_drive_from_interval(snap, ab_breakbeat_state.tempo_prev_interval_ms); + + if(snap->beat_age_ms > 0) + { + float hold_ms = 1300.0f; + float fade_ms = 1900.0f; + + if(snap->bpm > 1.0f) + { + float period = 60000.0f / snap->bpm; + + if(period < 360.0f) + period = 360.0f; + else if(period > 1400.0f) + period = 1400.0f; + + hold_ms = period * 1.35f; + fade_ms = period * 2.20f; + } + + if((float)snap->beat_age_ms > hold_ms) + { + float k = 1.0f - (((float)snap->beat_age_ms - hold_ms) / fade_ms); + + if(k < 0.0f) + k = 0.0f; + + drive *= k; + } + } + + return ab_breakbeat_clampf(drive); +} + +static inline float ab_breakbeat_density_from_interval(long interval_ms) +{ + float density; + + if(interval_ms <= 0) + return 0.0f; + + density = (1200.0f - (float)interval_ms) * (1.0f / 950.0f); + + return ab_breakbeat_clampf(density); +} + +static inline float ab_breakbeat_regular_drive(void) +{ + return ab_breakbeat_clampf(ab_breakbeat_state.rhythm_regularity); +} + +static inline float ab_breakbeat_accent_drive(void) +{ + return ab_breakbeat_clampf(ab_breakbeat_state.rhythm_accent); +} + +static inline float ab_breakbeat_accel_drive(void) +{ + return ab_breakbeat_clampf(ab_breakbeat_state.rhythm_accel); +} + +static inline float ab_breakbeat_syncopation_drive(void) +{ + float density = ab_breakbeat_clampf(ab_breakbeat_state.rhythm_density); + float regular = ab_breakbeat_regular_drive(); + + return ab_breakbeat_clampf((1.0f - regular) * (0.38f + density * 0.62f)); +} + +static inline float ab_breakbeat_expression_drive(void) +{ + float accent = ab_breakbeat_accent_drive(); + float accel = ab_breakbeat_accel_drive(); + float sync = ab_breakbeat_syncopation_drive(); + + return ab_breakbeat_clampf(accent * 0.56f + sync * 0.26f + accel * 0.18f); +} + +static inline double ab_breakbeat_transport_period_ms(const vj_audio_beat_snapshot_t *snap) +{ + double period = 0.0; + + if(snap && snap->bpm > 1.0f) + period = 60000.0 / (double)snap->bpm; + + if(period < (double)VEEJAY_AUDIO_BEAT_MIN_PERIOD_MS || + period > (double)VEEJAY_AUDIO_BEAT_MAX_PERIOD_MS) + period = (double)ab_breakbeat_state.rhythm_interval_ema_ms; + + if(period < (double)VEEJAY_AUDIO_BEAT_MIN_PERIOD_MS || + period > (double)VEEJAY_AUDIO_BEAT_MAX_PERIOD_MS) + period = (double)ab_breakbeat_state.tempo_prev_interval_ms; + + return ab_bound_period_ms(period); +} + +static inline long ab_breakbeat_open_ms(vj_audio_beat_shared_t *s, + const vj_audio_beat_snapshot_t *snap, + int hit_kind) +{ + long configured = ab_breakbeat_param_ms(s, &s->freeze_ms, 20, 1000); + double value = (double)configured; + double period = 0.0; + + if(snap && snap->bpm > 1.0f) + period = 60000.0 / (double)snap->bpm; + + if(period < (double)VEEJAY_AUDIO_BEAT_MIN_PERIOD_MS || + period > (double)VEEJAY_AUDIO_BEAT_MAX_PERIOD_MS) + period = (double)ab_breakbeat_state.rhythm_interval_ema_ms; + + if(period < (double)VEEJAY_AUDIO_BEAT_MIN_PERIOD_MS || + period > (double)VEEJAY_AUDIO_BEAT_MAX_PERIOD_MS) + period = (double)ab_breakbeat_state.tempo_prev_interval_ms; + + if(hit_kind == AB_HIT_SCRATCH) + { + float amount = snap ? snap->beat_density : 0.0f; + float burst = snap ? snap->transient : 0.0f; + float velocity = snap ? snap->flux : 0.0f; + + if(amount < velocity) + amount = velocity; + + value = 34.0 + (double)amount * 54.0 + (double)burst * 32.0; + + if(velocity > 0.70f) + value *= 0.82; + if(burst > 0.74f) + value *= 0.78; + + if(value < 26.0) + value = 26.0; + else if(value > 132.0) + value = 132.0; + + return (long)(value + 0.5); + } + + if(period >= (double)VEEJAY_AUDIO_BEAT_MIN_PERIOD_MS && + period <= (double)VEEJAY_AUDIO_BEAT_MAX_PERIOD_MS) + { + float density = ab_breakbeat_clampf(ab_breakbeat_state.rhythm_density); + float regular = ab_breakbeat_regular_drive(); + float accent = ab_breakbeat_accent_drive(); + float accel = ab_breakbeat_accel_drive(); + float expression = ab_breakbeat_expression_drive(); + float climax = ab_breakbeat_climax_drive(); + float velocity = ab_breakbeat_velocity(snap); + double frac; + double musical; + + if(hit_kind == AB_HIT_HAT) + { + frac = 0.052 + (double)velocity * 0.018 + + (double)climax * 0.018 + (double)accel * 0.010; + } + else if(hit_kind == AB_HIT_SNARE) + { + frac = 0.145 + (double)expression * 0.052 + + (double)accent * 0.030 + (double)climax * 0.055 + + (double)velocity * 0.026; + } + else + { + frac = 0.175 + (double)expression * 0.046 + + (double)density * 0.030 + (double)climax * 0.060 + + (double)velocity * 0.036; + } + + if(regular > 0.64f && expression < 0.35f && accel < 0.34f && climax < 0.70f) + frac *= 0.84; + else if(expression > 0.58f || accel > 0.52f || climax > 0.78f) + frac *= 1.10; + + if(hit_kind == AB_HIT_HAT) + { + if(frac < 0.040) + frac = 0.040; + else if(frac > 0.125) + frac = 0.125; + } + else + { + if(frac < 0.105) + frac = 0.105; + else if(frac > 0.360) + frac = 0.360; + } + + musical = period * frac; + value = (double)configured * 0.34 + musical * 0.66; + } + + if(hit_kind == AB_HIT_HAT) + { + if(value < 22.0) + value = 22.0; + else if(value > 120.0) + value = 120.0; + } + else if(period > 760.0) + { + if(value < 70.0) + value = 70.0; + else if(value > 520.0) + value = 520.0; + } + else + { + if(value < 55.0) + value = 55.0; + else if(value > 380.0) + value = 380.0; + } + + return (long)(value + 0.5); +} + +static inline long ab_breakbeat_transport_min_gap_ms(const vj_audio_beat_snapshot_t *snap, + int hit_kind, + int repeated) +{ + double period = ab_breakbeat_transport_period_ms(snap); + float density = ab_breakbeat_clampf(ab_breakbeat_state.rhythm_density); + float regular = ab_breakbeat_regular_drive(); + float accent = ab_breakbeat_accent_drive(); + float accel = ab_breakbeat_accel_drive(); + float expression = ab_breakbeat_expression_drive(); + float climax = ab_breakbeat_climax_drive(); + float velocity = ab_breakbeat_velocity(snap); + double frac; + long gap; + + if(hit_kind == AB_HIT_SCRATCH) + { + float scratch = snap ? snap->beat_density : 0.0f; + float burst = snap ? snap->transient : 0.0f; + float v = snap ? snap->flux : velocity; + + if(scratch < v) + scratch = v; + + gap = (long)(86.0 - (double)scratch * 34.0 - (double)burst * 22.0 + 0.5); + if(repeated) + gap -= 10L; + if(gap < 28L) + gap = 28L; + else if(gap > 92L) + gap = 92L; + return gap; + } + + if(hit_kind == AB_HIT_HAT) + frac = 0.66 - (double)expression * 0.18 - (double)accel * 0.12 - + (double)climax * 0.08 - (double)velocity * 0.06; + else + frac = 0.58 - (double)density * 0.10 - (double)expression * 0.14 - + (double)accel * 0.10 - (double)climax * 0.06 - + (double)accent * 0.05; + + if(repeated) + frac -= 0.04 + (double)expression * 0.05 + (double)climax * 0.03; + + if(regular > 0.62f && expression < 0.32f && accel < 0.30f && climax < 0.70f) + frac += 0.10 + (double)(regular - 0.62f) * 0.16; + + if(hit_kind == AB_HIT_HAT && expression < 0.42f && accel < 0.38f && climax < 0.62f) + frac += 0.12; + + if(frac < 0.26) + frac = 0.26; + else if(frac > 0.82) + frac = 0.82; + + gap = (long)(period * frac + 0.5); + + if(period <= 260.0) + { + if(gap < 70) + gap = 70; + else if(gap > 210) + gap = 210; + } + else if(period <= 420.0) + { + if(gap < 90) + gap = 90; + else if(gap > 290) + gap = 290; + } + else if(period <= 760.0) + { + if(gap < 130) + gap = 130; + else if(gap > 480) + gap = 480; + } + else + { + if(gap < 180) + gap = 180; + else if(gap > 900) + gap = 900; + } + + return gap; +} + +static inline int ab_breakbeat_transport_hit_allowed(const vj_audio_beat_snapshot_t *snap, + long now, + int hit_kind, + int repeated) +{ + long last = ab_breakbeat_state.last_transport_action_ms; + long age; + long min_gap; + double period; + float expression; + float accel; + float climax; + float density; + float accent; + + if(hit_kind == AB_HIT_HAT) + { + expression = ab_breakbeat_expression_drive(); + accel = ab_breakbeat_accel_drive(); + climax = ab_breakbeat_climax_drive(); + density = ab_breakbeat_clampf(ab_breakbeat_state.rhythm_density); + + if(expression < 0.38f && accel < 0.34f && climax < 0.58f && density < 0.72f) + return 0; + } + + if(last <= 0) + { + ab_breakbeat_state.last_transport_action_ms = now; + return 1; + } + + age = now - last; + if(age < 0) + age = 0; + + min_gap = ab_breakbeat_transport_min_gap_ms(snap, hit_kind, repeated); + if(age >= min_gap) + { + ab_breakbeat_state.last_transport_action_ms = now; + return 1; + } + + if(ab_is_body_hit(hit_kind)) + { + period = ab_breakbeat_transport_period_ms(snap); + expression = ab_breakbeat_expression_drive(); + accel = ab_breakbeat_accel_drive(); + climax = ab_breakbeat_climax_drive(); + accent = ab_breakbeat_accent_drive(); + + if(age >= (long)(period * 0.31 + 0.5) && + (expression > 0.58f || accel > 0.58f || climax > 0.78f || accent > 0.74f)) + { + ab_breakbeat_state.last_transport_action_ms = now; + return 1; + } + } + + return 0; +} + +static inline uint32_t ab_breakbeat_mix_u32(uint32_t x) +{ + x ^= x >> 16; + x *= 0x7feb352du; + x ^= x >> 15; + x *= 0x846ca68bu; + x ^= x >> 16; + return x; +} + +static inline float ab_breakbeat_random_unit(const vj_audio_beat_snapshot_t *snap, long long frame, int salt) +{ + uint32_t x = (uint32_t)(snap ? snap->hit_seq : 0); + + x ^= (uint32_t)frame; + x ^= (uint32_t)(frame >> 32); + x ^= (uint32_t)(ab_breakbeat_state.repeat_count * 0x45d9f3bu); + x ^= (uint32_t)(salt * 0x27d4eb2du); + + return (float)(ab_breakbeat_mix_u32(x) & 0xffffu) * (1.0f / 65535.0f); +} + +static inline void ab_breakbeat_update_music_state(const vj_audio_beat_snapshot_t *snap, long now) +{ + float dt; + float velocity; + float activity; + float body; + float target; + float alpha; + float hit_power; + long hit_interval; + + if(!snap) + return; + + if(ab_breakbeat_state.music_last_ms <= 0) + { + ab_breakbeat_state.music_last_ms = now; + ab_breakbeat_state.music_last_hit_seq = snap->hit_seq; + } + + dt = (float)(now - ab_breakbeat_state.music_last_ms) * 0.001f; + + if(dt < 0.0f) + dt = 0.0f; + else if(dt > 0.25f) + dt = 0.25f; + + ab_breakbeat_state.music_last_ms = now; + + velocity = ab_breakbeat_velocity(snap); + activity = ab_breakbeat_activity(snap); + body = snap->kick > snap->snare ? snap->kick : snap->snare; + hit_power = velocity * 0.62f + activity * 0.24f + body * 0.14f; + + { + float pace = ab_breakbeat_pace_drive(snap); + float decay; + + if(activity < 0.025f) + decay = 0.72f; + else if(snap->beat_age_ms > 700) + decay = 0.18f + (1.0f - pace) * 0.18f; + else + decay = 0.060f + (1.0f - pace) * 0.050f; + + ab_breakbeat_state.tempo_drive -= decay * dt; + ab_breakbeat_state.rhythm_accent -= (0.85f + (activity < 0.08f ? 0.65f : 0.0f)) * dt; + ab_breakbeat_state.rhythm_accel -= 0.95f * dt; + + if(snap->beat_age_ms > 1200) + { + ab_breakbeat_state.rhythm_density -= 0.32f * dt; + ab_breakbeat_state.rhythm_regularity -= 0.24f * dt; + } + } + + target = activity * 0.44f + velocity * 0.26f + snap->beat_pulse * 0.13f; + target = (target - 0.085f) * (1.0f / 0.86f); + target = ab_breakbeat_clampf(target); + target = target * (2.0f - target); + + alpha = target > ab_breakbeat_state.music_groove ? 2.00f * dt : 1.10f * dt; + if(alpha > 0.32f) + alpha = 0.32f; + + ab_breakbeat_state.music_groove += + (target - ab_breakbeat_state.music_groove) * alpha; + + if(snap->hit_seq != ab_breakbeat_state.music_last_hit_seq) + { + float impact_raw = velocity * 0.58f + activity * 0.18f + body * 0.24f; + float impact = (impact_raw - 0.30f) * (1.0f / 0.64f); + float accent = 0.0f; + float accel = 0.0f; + float density = 0.0f; + float regularity = ab_breakbeat_state.rhythm_regularity; + float sync; + float expression; + + impact = ab_breakbeat_clampf(impact); + + hit_interval = ab_breakbeat_state.tempo_last_hit_ms > 0 ? + now - ab_breakbeat_state.tempo_last_hit_ms : 0; + + if(ab_breakbeat_state.rhythm_power_ema <= 0.001f) + ab_breakbeat_state.rhythm_power_ema = hit_power; + + accent = (hit_power - ab_breakbeat_state.rhythm_power_ema + 0.055f) * (1.0f / 0.42f); + accent = ab_breakbeat_clampf(accent) * (0.45f + impact * 0.55f); + + if(accent > ab_breakbeat_state.rhythm_accent) + ab_breakbeat_state.rhythm_accent += (accent - ab_breakbeat_state.rhythm_accent) * 0.68f; + else + ab_breakbeat_state.rhythm_accent += (accent - ab_breakbeat_state.rhythm_accent) * 0.36f; + + ab_breakbeat_state.rhythm_power_ema += + (hit_power - ab_breakbeat_state.rhythm_power_ema) * (0.080f + ab_breakbeat_state.rhythm_accent * 0.15f); + + if(hit_interval > 0) + { + float pace = ab_breakbeat_pace_drive_from_interval(snap, hit_interval); + float interval_f = (float)hit_interval; + float ema = ab_breakbeat_state.rhythm_interval_ema_ms; + float rel_err = 1.0f; + + density = ab_breakbeat_density_from_interval(hit_interval); + + if(ema <= 1.0f) + ema = interval_f; + else + { + rel_err = (float)fabs((double)(interval_f - ema)); + if(ema > 1.0f) + rel_err /= ema; + + ema += (interval_f - ema) * (0.10f + density * 0.07f); + } + + regularity = 1.0f - rel_err * (1.0f / 0.32f); + regularity = ab_breakbeat_clampf(regularity); + + if(ab_breakbeat_state.tempo_prev_interval_ms > 0) + { + float prev = (float)ab_breakbeat_state.tempo_prev_interval_ms; + float cur = interval_f; + + if(cur < prev * 0.97f) + accel = ((prev - cur) / prev) * (1.0f / 0.24f); + else if(cur > prev * 1.08f) + ab_breakbeat_state.tempo_drive -= 0.10f + ((cur - prev) / prev) * 0.08f; + } + + accel = ab_breakbeat_clampf(accel); + + ab_breakbeat_state.rhythm_interval_ema_ms = ema; + ab_breakbeat_state.rhythm_density += (density - ab_breakbeat_state.rhythm_density) * 0.24f; + ab_breakbeat_state.rhythm_regularity += (regularity - ab_breakbeat_state.rhythm_regularity) * 0.24f; + + if(accel > ab_breakbeat_state.rhythm_accel) + ab_breakbeat_state.rhythm_accel += (accel - ab_breakbeat_state.rhythm_accel) * 0.72f; + else + ab_breakbeat_state.rhythm_accel += (accel - ab_breakbeat_state.rhythm_accel) * 0.32f; + + sync = ab_breakbeat_syncopation_drive(); + expression = ab_breakbeat_expression_drive(); + + { + float tempo_target = hit_power * (density * 0.20f + accel * 0.42f + expression * 0.20f + pace * 0.10f); + + ab_breakbeat_state.tempo_drive += + hit_power * (0.020f + density * 0.045f + accel * 0.30f + expression * 0.085f + pace * 0.030f); + + if(ab_breakbeat_state.tempo_drive < tempo_target) + { + float attack = 0.12f + accel * 0.34f + expression * 0.18f + density * 0.06f; + + if(attack > 0.58f) + attack = 0.58f; + + ab_breakbeat_state.tempo_drive += + (tempo_target - ab_breakbeat_state.tempo_drive) * attack; + } + } + + if(hit_power > 0.72f && expression > 0.22f) + ab_breakbeat_state.tempo_drive += 0.018f; + + ab_breakbeat_state.tempo_prev_interval_ms = hit_interval; + } + else + { + sync = ab_breakbeat_syncopation_drive(); + expression = ab_breakbeat_expression_drive(); + } + + ab_breakbeat_state.tempo_last_hit_ms = now; + + sync = ab_breakbeat_syncopation_drive(); + expression = ab_breakbeat_expression_drive(); + + if(ab_is_body_hit(snap->hit_kind)) + { + float phrase_inc = impact * (0.010f + expression * 0.064f + sync * 0.016f) + + snap->beat_pulse * 0.005f; + + if(ab_breakbeat_state.rhythm_regularity > 0.66f && expression < 0.24f) + phrase_inc *= 0.36f; + + ab_breakbeat_state.music_phrase += phrase_inc; + + if(impact > 0.10f && + (expression > 0.20f || sync > 0.30f || ab_breakbeat_state.music_phrase > 0.58f)) + { + float climax_inc = impact * (0.006f + expression * 0.058f + sync * 0.012f) + + ab_breakbeat_state.music_phrase * expression * 0.006f; + + if(ab_breakbeat_state.rhythm_regularity > 0.70f && expression < 0.32f) + climax_inc *= 0.42f; + + ab_breakbeat_state.music_climax += climax_inc; + } + } + else if(snap->hit_kind == AB_HIT_HAT) + { + ab_breakbeat_state.music_phrase += impact * (0.003f + expression * 0.014f); + + if(impact > 0.62f && expression > 0.48f) + ab_breakbeat_state.music_climax += impact * expression * 0.010f; + } + + ab_breakbeat_state.music_last_hit_seq = snap->hit_seq; + } + + target = ab_breakbeat_state.music_groove * 0.38f + activity * 0.085f + + ab_breakbeat_expression_drive() * 0.10f; + alpha = target > ab_breakbeat_state.music_phrase ? 0.25f * dt : 0.18f * dt; + if(alpha > 0.10f) + alpha = 0.10f; + + ab_breakbeat_state.music_phrase += + (target - ab_breakbeat_state.music_phrase) * alpha; + + ab_breakbeat_state.music_phrase -= (0.026f + (activity < 0.10f ? 0.070f : 0.0f)) * dt; + + target = velocity * 0.24f + activity * 0.17f + + ab_breakbeat_state.music_groove * 0.10f + + ab_breakbeat_state.music_phrase * 0.22f + + ab_breakbeat_expression_drive() * 0.25f; + target = (target - 0.30f) * (1.0f / 0.70f); + target = ab_breakbeat_clampf(target) * 0.82f; + + alpha = target > ab_breakbeat_state.music_climax ? 0.46f * dt : 0.42f * dt; + if(alpha > 0.16f) + alpha = 0.16f; + + ab_breakbeat_state.music_climax += + (target - ab_breakbeat_state.music_climax) * alpha; + + if(activity < 0.025f) + ab_breakbeat_state.music_climax -= 0.24f * dt; + else if(snap->beat_age_ms > 900) + ab_breakbeat_state.music_climax -= 0.075f * dt; + else + ab_breakbeat_state.music_climax -= 0.020f * dt; + + { + float regular = ab_breakbeat_regular_drive(); + float expression = ab_breakbeat_expression_drive(); + float density = ab_breakbeat_clampf(ab_breakbeat_state.rhythm_density); + + if(regular > 0.55f && expression < 0.34f && ab_breakbeat_state.tempo_drive < 0.74f) + { + float gate = (regular - 0.55f) * (1.0f / 0.45f); + float phrase_cap = 0.40f + activity * 0.10f + density * 0.16f + expression * 0.36f; + float climax_cap = 0.24f + velocity * 0.08f + density * 0.10f + expression * 0.42f; + float pull = dt * (0.80f + gate * 1.35f); + + if(pull > 0.22f) + pull = 0.22f; + + if(ab_breakbeat_state.music_phrase > phrase_cap) + ab_breakbeat_state.music_phrase += (phrase_cap - ab_breakbeat_state.music_phrase) * pull; + if(ab_breakbeat_state.music_climax > climax_cap) + ab_breakbeat_state.music_climax += (climax_cap - ab_breakbeat_state.music_climax) * pull; + } + } + + ab_breakbeat_state.music_groove = ab_breakbeat_clampf(ab_breakbeat_state.music_groove); + ab_breakbeat_state.music_phrase = ab_breakbeat_clampf(ab_breakbeat_state.music_phrase); + ab_breakbeat_state.music_climax = ab_breakbeat_clampf(ab_breakbeat_state.music_climax); + ab_breakbeat_state.tempo_drive = ab_breakbeat_clampf(ab_breakbeat_state.tempo_drive); + ab_breakbeat_state.rhythm_density = ab_breakbeat_clampf(ab_breakbeat_state.rhythm_density); + ab_breakbeat_state.rhythm_regularity = ab_breakbeat_clampf(ab_breakbeat_state.rhythm_regularity); + ab_breakbeat_state.rhythm_accent = ab_breakbeat_clampf(ab_breakbeat_state.rhythm_accent); + ab_breakbeat_state.rhythm_accel = ab_breakbeat_clampf(ab_breakbeat_state.rhythm_accel); +} + +static inline void ab_breakbeat_clear_local_loop(void) +{ + ab_breakbeat_state.local_loop_active = 0; + ab_breakbeat_state.local_loop_until_ms = 0; + ab_breakbeat_state.loop_lo = 0; + ab_breakbeat_state.loop_hi = 0; +} + +static inline int ab_breakbeat_should_use_local_loop(const vj_audio_beat_snapshot_t *snap, int repeated) +{ + float climax = ab_breakbeat_climax_drive(); + float phrase = ab_breakbeat_phrase_drive(); + float velocity = ab_breakbeat_velocity(snap); + float r; + + if(!snap || snap->hit_kind == AB_HIT_HAT) + return 0; + + if(snap->hit_kind == AB_HIT_SCRATCH) + return repeated || snap->beat_density > 0.52f || snap->flux > 0.58f || snap->transient > 0.60f; + + if(snap->hit_kind == AB_HIT_SNARE) + { + if(phrase > 0.66f && climax > 0.74f && velocity > 0.70f) + { + r = ab_breakbeat_random_unit(snap, ab_breakbeat_state.anchor_frame, 71); + return r < (repeated ? 0.20f : 0.11f); + } + } + + if(phrase > 0.78f && climax > 0.88f && velocity > 0.82f) + { + r = ab_breakbeat_random_unit(snap, ab_breakbeat_state.anchor_frame, 73); + return r < 0.055f; + } + + return 0; +} + +static inline int ab_breakbeat_should_forward_jump(veejay_t *v, const vj_audio_beat_snapshot_t *snap, long now) +{ + float climax = ab_breakbeat_climax_drive(); + float phrase = ab_breakbeat_phrase_drive(); + float groove = ab_breakbeat_groove_drive(); + float velocity = ab_breakbeat_velocity(snap); + float activity = ab_breakbeat_activity(snap); + int span = ab_breakbeat_span_frames(v); + int chance; + long min_gap; + float r; + + if(!snap || !ab_is_body_hit(snap->hit_kind) || span < 96) + return 0; + + if(activity < 0.060f || velocity < 0.32f) + return 0; + + { + float regular = ab_breakbeat_regular_drive(); + float expression = ab_breakbeat_expression_drive(); + + if(regular > 0.62f && expression < 0.38f && + !(phrase > 0.78f && climax > 0.82f && velocity > 0.86f && activity > 0.72f)) + return 0; + } + + if(phrase < 0.32f && climax < 0.34f) + return 0; + + min_gap = 900L; + if(snap->bpm > 1.0f) + { + double period = 60000.0 / (double)snap->bpm; + double spacing; + + if(period < 220.0) + period = 220.0; + else if(period > 1400.0) + period = 1400.0; + + spacing = 3.40 - (double)climax * 1.55 - (double)phrase * 0.45; + + if(spacing < 1.55) + spacing = 1.55; + else if(spacing > 3.40) + spacing = 3.40; + + min_gap = (long)(period * spacing + 0.5); + } + + if(ab_breakbeat_state.last_forward_jump_ms > 0 && + (now - ab_breakbeat_state.last_forward_jump_ms) < min_gap) + return 0; + + chance = -190 + + (int)(phrase * 430.0f) + + (int)(climax * 560.0f) + + (int)(groove * 60.0f) + + (int)(velocity * 70.0f) + + (int)(ab_breakbeat_expression_drive() * 250.0f) + + (int)(ab_breakbeat_accel_drive() * 120.0f); + + if(phrase > 0.46f && climax > 0.36f) + chance += 120; + if(phrase > 0.60f && climax > 0.52f) + chance += 150; + if(climax > 0.72f) + chance += 130; + + if(snap->hit_kind == AB_HIT_SNARE) + chance -= 85; + if(snap->bpm > 185.0f) + chance -= 45; + + { + float regular = ab_breakbeat_regular_drive(); + float expression = ab_breakbeat_expression_drive(); + + if(regular > 0.0f) + chance -= (int)(regular * (1.0f - expression * 0.65f) * 230.0f); + } + + if(chance < 180) + return 0; + if(chance > 790) + chance = 790; + + r = ab_breakbeat_random_unit(snap, ab_breakbeat_state.anchor_frame, 19); + return ((int)(r * 1000.0f)) < chance; +} + +static inline int ab_breakbeat_forward_jump_frames(veejay_t *v, vj_audio_beat_shared_t *s, const vj_audio_beat_snapshot_t *snap, int slice_frames) +{ + int span = ab_breakbeat_span_frames(v); + long pulse_ms = ab_breakbeat_param_ms(s, &s->pulse_ms, 20, 2000); + float climax = ab_breakbeat_climax_drive(); + float phrase = ab_breakbeat_phrase_drive(); + float velocity = ab_breakbeat_velocity(snap); + float r = ab_breakbeat_random_unit(snap, ab_breakbeat_current_frame(v), 37); + double pulse = (double)pulse_ms * (1.0 / 2000.0); + double frac; + int jump; + int min_jump; + int cap; + + if(span <= 1) + return slice_frames > 0 ? slice_frames : 1; + + frac = 0.140 + pulse * 0.090 + + (double)phrase * 0.300 + + (double)climax * 0.330 + + (double)velocity * 0.090 + + (double)r * 0.180; + + if(phrase > 0.62f && climax > 0.55f) + frac += 0.080; + + if(frac > 0.86) + frac = 0.86; + + jump = (int)((double)span * frac + 0.5); + min_jump = (int)((double)span * (0.18 + (double)phrase * 0.12 + (double)climax * 0.10) + 0.5); + + if(min_jump < slice_frames * 5) + min_jump = slice_frames * 5; + if(min_jump < 48) + min_jump = 48; + + cap = (int)((double)span * 0.90 + 0.5); + if(cap < min_jump) + cap = span - 1; + if(cap < 1) + cap = 1; + + if(jump < min_jump) + jump = min_jump; + if(jump > cap) + jump = cap; + + return jump; +} + +static inline void ab_breakbeat_apply_transport(veejay_t *v, int speed) +{ + vj_audio_beat_shared_t *s; + int beat_owned_pause; + + if(!v || !v->settings) + return; + + s = &v->settings->audio_beat; + beat_owned_pause = ab_load_i(&s->paused_by_beat); + + if(v->settings->current_playback_speed == 0 && speed != 0) + { + if(!beat_owned_pause) + { + ab_store_l(&ab_breakbeat_user_override_until_ms, AB_BREAKBEAT_USER_PAUSE_OVERRIDE); + ab_breakbeat_hit_queue_clear(); + ab_store_i(&s->consumed_seq, ab_load_i(&s->hit_seq)); + return; + } + + veejay_msg(VEEJAY_MSG_DEBUG, + "[AUDIO-BEAT][BREAK] taking over beat-owned pause requested speed=%d hit=%d consumed=%d hold=%ld", + speed, + ab_load_i(&s->hit_seq), + ab_load_i(&s->consumed_seq), + ab_load_l(&s->hold_until_ms)); + } + + if(v->settings->current_playback_speed != speed) + ab_set_speed_from_beat(v, speed, 0); + + if(speed == 0) + ab_store_i(&s->paused_by_beat, 1); + else if(beat_owned_pause) + ab_store_i(&s->paused_by_beat, 0); +} + +static int ab_breakbeat_resume_owned_pause(veejay_t *v, vj_audio_beat_shared_t *s, + long now, int hit_seq) +{ + int speed; + + (void)now; + + if(!v || !v->settings || !s) + return 0; + + if(ab_breakbeat_state.active) + return 0; + + if(!ab_load_i(&s->paused_by_beat)) + return 0; + + if(v->settings->current_playback_speed != 0) + return 0; + + speed = ab_load_i(&s->resume_speed); + if(speed == 0) + speed = v->settings->previous_playback_speed; + if(speed == 0) + speed = 1; + + ab_breakbeat_reset_state(); + ab_store_i(&s->resume_speed, speed); + ab_store_l(&s->hold_until_ms, 0); + ab_store_i(&s->consumed_seq, hit_seq >= 0 ? hit_seq : ab_load_i(&s->hit_seq)); + ab_store_l(&ab_breakbeat_user_override_until_ms, 0); + + ab_breakbeat_apply_transport(v, speed); + ab_store_i(&s->paused_by_beat, 0); + + return 1; +} +static inline int ab_breakbeat_anchor_radius(int slice_frames) +{ + int radius = slice_frames; + + if(radius < 6) + radius = 6; + else if(radius > 72) + radius = 72; + + return radius; +} + +static inline int ab_breakbeat_local_radius(int slice_frames, const vj_audio_beat_snapshot_t *snap) +{ + float climax = ab_breakbeat_climax_drive(); + float velocity = ab_breakbeat_velocity(snap); + int radius = 10 + (slice_frames / 10) + (int)(velocity * 22.0f) + (int)(climax * 26.0f); + + if(snap && snap->hit_kind == AB_HIT_HAT) + { + radius = 2 + (int)(snap->hat * 5.0f) + (int)(snap->high * 4.0f); + if(radius < 2) + radius = 2; + else if(radius > 12) + radius = 12; + return radius; + } + + if(snap && snap->hit_kind == AB_HIT_SCRATCH) + { + radius = 1 + (int)(snap->beat_density * 5.0f) + (int)(snap->flux * 4.0f); + if(radius < 1) + radius = 1; + else if(radius > 10) + radius = 10; + return radius; + } + + if(radius < 8) + radius = 8; + else if(radius > 96) + radius = 96; + + return radius; +} + +static inline void ab_breakbeat_set_local_loop(veejay_t *v, long long center, int radius) +{ + long long lo = 0; + long long hi = 0; + long long a; + long long b; + + if(!ab_breakbeat_bounds(v, &lo, &hi)) + { + ab_breakbeat_state.loop_lo = center - radius; + ab_breakbeat_state.loop_hi = center + radius; + ab_breakbeat_state.local_loop_active = 1; + return; + } + + if(radius < 2) + radius = 2; + + a = center - (long long)radius; + b = center + (long long)radius; + + if(a < lo) + a = lo; + if(b > hi) + b = hi; + + if(b <= a) + { + a = center; + b = center; + if(a > lo) + a--; + if(b < hi) + b++; + } + + ab_breakbeat_scene_guard_loop(v, center, &a, &b); + + ab_breakbeat_state.loop_lo = a; + ab_breakbeat_state.loop_hi = b; + ab_breakbeat_state.local_loop_active = 1; +} + +static inline int ab_breakbeat_slice_frames(veejay_t *v, vj_audio_beat_shared_t *s, const vj_audio_beat_snapshot_t *snap) +{ + long pulse_ms = ab_breakbeat_param_ms(s, &s->pulse_ms, 20, 2000); + double fps = (double)ab_breakbeat_base_fps(v); + float climax = ab_breakbeat_climax_drive(); + float velocity = ab_breakbeat_velocity(snap); + double boost = 1.0 + (double)climax * 0.85 + (double)velocity * 0.62; + int frames; + + if(snap) + { + boost += (double)snap->transient * 0.42 + + (double)snap->flux * 0.25 + + (double)snap->bass * 0.18; + + if(snap->hit_kind == AB_HIT_KICK) + boost += (double)snap->kick * 0.48; + else if(snap->hit_kind == AB_HIT_SNARE) + boost += (double)snap->snare * 0.44 + (double)snap->mid * 0.20; + else if(snap->hit_kind == AB_HIT_FULL) + boost += (double)(snap->kick > snap->snare ? snap->kick : snap->snare) * 0.34; + else if(snap->hit_kind == AB_HIT_HAT) + boost = 0.45 + + (double)snap->hat * 0.62 + + (double)snap->high * 0.24 + + (double)climax * 0.20; + else if(snap->hit_kind == AB_HIT_SCRATCH) + boost = 0.30 + + (double)snap->beat_density * 0.58 + + (double)snap->flux * 0.46 + + (double)snap->transient * 0.36; + } + + if(boost < 0.38) + boost = 0.38; + else if(boost > 3.80) + boost = 3.80; + + frames = (int)(((double)pulse_ms * fps * boost) / 1000.0 + 0.5); + + if(snap && snap->hit_kind == AB_HIT_HAT) + { + if(frames < 1) + frames = 1; + else if(frames > 22) + frames = 22; + } + else if(snap && snap->hit_kind == AB_HIT_SCRATCH) + { + if(frames < 1) + frames = 1; + else if(frames > 18) + frames = 18; + } + else + { + int lo = climax > 0.65f || velocity > 0.80f ? 8 : 4; + int hi = climax > 0.70f ? 520 : 320; + + if(frames < lo) + frames = lo; + else if(frames > hi) + frames = hi; + } + + return frames; +} + +static inline int ab_breakbeat_bound_slice_frames(veejay_t *v, vj_audio_beat_shared_t *s, int frames, const vj_audio_beat_snapshot_t *snap) +{ + int span = ab_breakbeat_span_frames(v); + int cap; + + if(span <= 0) + return frames; + + if(snap && snap->hit_kind == AB_HIT_SCRATCH) + { + cap = span / 24; + if(cap < 2) + cap = 2; + else if(cap > 24) + cap = 24; + + if(frames > cap) + frames = cap; + if(frames < 1) + frames = 1; + return frames; + } + + if(snap && snap->hit_kind == AB_HIT_HAT) + { + cap = span / 18; + if(cap < 2) + cap = 2; + else if(cap > 28) + cap = 28; + } + else + { + long pulse_ms = ab_breakbeat_param_ms(s, &s->pulse_ms, 20, 2000); + float climax = ab_breakbeat_climax_drive(); + float velocity = ab_breakbeat_velocity(snap); + double pulse = (double)pulse_ms / 2000.0; + double drama = 0.010 + pulse * 0.15 + (double)climax * 0.14 + (double)velocity * 0.060; + int dramatic; + + if(drama > 0.42) + drama = 0.42; + + dramatic = (int)((double)span * drama + 0.5); + + if(frames < dramatic) + frames = dramatic; + + cap = (int)((double)span * (0.20 + (double)climax * 0.38 + (double)velocity * 0.18) + 0.5); + if(cap < 8) + cap = 8; + else if(cap > 720) + cap = 720; + } + + if(frames > cap) + frames = cap; + if(frames < 1) + frames = 1; + + return frames; +} + +static inline int ab_breakbeat_add_quant_candidate(int *candidates, int count, int max_count, int value) +{ + if(value < 1 || count >= max_count) + return count; + + for(int i = 0; i < count; i++) + if(candidates[i] == value) + return count; + + candidates[count++] = value; + return count; +} + +static inline int ab_breakbeat_quantize_slice_frames(veejay_t *v, int frames, const vj_audio_beat_snapshot_t *snap) +{ + int candidates[40]; + int count = 0; + int best; + int best_d; + static const int fixed_grid[] = { 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768 }; + + if(frames < 1) + frames = 1; + + for(int i = 0; i < (int)(sizeof(fixed_grid) / sizeof(fixed_grid[0])); i++) + count = ab_breakbeat_add_quant_candidate(candidates, count, 40, fixed_grid[i]); + + if(snap && snap->bpm > 1.0f) + { + double beat_frames = ((double)ab_breakbeat_base_fps(v) * 60.0) / (double)snap->bpm; + static const double divs[] = { 0.125, 0.1666667, 0.25, 0.3333333, 0.5, 0.6666667, 1.0, 1.5, 2.0, 3.0, 4.0 }; + + if(beat_frames >= 1.0 && beat_frames <= 400.0) + { + for(int i = 0; i < (int)(sizeof(divs) / sizeof(divs[0])); i++) + { + int q = (int)(beat_frames * divs[i] + 0.5); + count = ab_breakbeat_add_quant_candidate(candidates, count, 40, q); + } + } + } + + best = frames; + best_d = INT_MAX; + + for(int i = 0; i < count; i++) + { + int c = candidates[i]; + int d; + + if(snap && snap->hit_kind == AB_HIT_HAT && c > 16) + continue; + + d = c > frames ? c - frames : frames - c; + if(d < best_d) + { + best = c; + best_d = d; + } + } + + if(best < 1) + best = 1; + + return best; +} + +static inline long ab_breakbeat_idle_pause_ms(vj_audio_beat_shared_t *s, const vj_audio_beat_snapshot_t *snap) +{ + long gate_ms = ab_breakbeat_param_ms(s, &s->gate_ms, 10, 1000); + long cooldown_ms = ab_breakbeat_param_ms(s, &s->cooldown_ms, 40, 2000); + long pause_ms = cooldown_ms * 4L + gate_ms; + + if(snap && snap->bpm > 1.0f) + { + double period = 60000.0 / (double)snap->bpm; + long musical; + + if(period < 180.0) + period = 180.0; + else if(period > 2400.0) + period = 2400.0; + + musical = (long)(period * 2.25 + (double)gate_ms + 0.5); + if(pause_ms < musical) + pause_ms = musical; + } + + if(pause_ms < 650) + pause_ms = 650; + else if(pause_ms > 3600) + pause_ms = 3600; + + return pause_ms; +} + +static inline int ab_breakbeat_should_idle_pause(vj_audio_beat_shared_t *s, const vj_audio_beat_snapshot_t *snap, long now) +{ + long age; + long pause_ms; + float activity = 0.0f; + + if(!ab_breakbeat_state.active || ab_breakbeat_state.last_hit_ms <= 0) + return 0; + + age = now - ab_breakbeat_state.last_hit_ms; + if(age < 0) + age = 0; + + pause_ms = ab_breakbeat_idle_pause_ms(s, snap); + + if(snap) + { + activity = snap->level; + if(activity < snap->envelope) + activity = snap->envelope; + if(activity < snap->transient) + activity = snap->transient; + if(activity < snap->flux) + activity = snap->flux; + if(activity < snap->bass) + activity = snap->bass; + if(activity < snap->mid) + activity = snap->mid; + if(activity < snap->high) + activity = snap->high; + } + + if(activity < 0.018f && age >= 360) + return 1; + + return age >= pause_ms; +} + +static inline float ab_breakbeat_hit_fps(veejay_t *v, const vj_audio_beat_snapshot_t *snap, int repeated, int hit_kind) +{ + float base_fps = ab_breakbeat_base_fps(v); + float phrase = ab_breakbeat_phrase_drive(); + float climax = ab_breakbeat_climax_drive(); + float tempo = ab_breakbeat_state.tempo_drive; + float pace = ab_breakbeat_pace_drive(snap); + float velocity = ab_breakbeat_velocity(snap); + float activity = ab_breakbeat_activity(snap); + float percussive = ab_breakbeat_percussive_drive(snap, hit_kind); + float density = ab_breakbeat_clampf(ab_breakbeat_state.rhythm_density); + float regular = ab_breakbeat_regular_drive(); + float accent = ab_breakbeat_accent_drive(); + float accel = ab_breakbeat_accel_drive(); + float expression = ab_breakbeat_expression_drive(); + float mul; + + if(tempo < pace * 0.22f) + tempo = pace * 0.22f; + + if(hit_kind == AB_HIT_SCRATCH) + { + float scratch = snap ? snap->beat_density : 0.0f; + float burst = snap ? snap->transient : 0.0f; + float sv = snap ? snap->flux : velocity; + + mul = 0.46f + scratch * 0.36f + sv * 0.54f + burst * 0.26f; + if(repeated) + mul += 0.10f + burst * 0.10f; + if(mul < 0.34f) + mul = 0.34f; + else if(mul > 1.72f) + mul = 1.72f; + + return ab_breakbeat_clamp_effect_fps(v, base_fps * mul); + } + + if(hit_kind == AB_HIT_HAT) + { + mul = 0.34f + density * 0.12f + velocity * 0.34f + + phrase * 0.08f + climax * 0.12f + accent * 0.14f + tempo * 0.08f; + + if(mul < 0.28f) + mul = 0.28f; + else if(mul > 1.08f) + mul = 1.08f; + + return ab_breakbeat_clamp_effect_fps(v, base_fps * mul); + } + + mul = 0.50f + + density * 0.20f + + velocity * 0.24f + + activity * 0.08f + + tempo * 0.18f + + phrase * 0.18f + + climax * 0.34f + + accent * 0.22f + + accel * 0.16f; + + if(repeated) + mul += expression * 0.05f + climax * 0.04f; + + if(hit_kind == AB_HIT_SNARE) + mul += 0.035f + accent * 0.10f + expression * 0.06f; + else if((hit_kind == AB_HIT_KICK || hit_kind == AB_HIT_FULL) && + (accent > 0.28f || phrase > 0.52f || climax > 0.48f)) + mul += 0.035f + accent * 0.07f + climax * 0.06f; + + if(phrase > 0.72f && climax > 0.68f && expression > 0.32f) + mul += 0.12f + climax * 0.10f; + + if(activity < 0.16f && climax < 0.30f && tempo < 0.18f) + mul *= 0.74f; + + if(regular > 0.58f && expression < 0.42f && accel < 0.28f && climax < 0.74f) + { + float gate = (regular - 0.58f) * (1.0f / 0.42f); + float cap = 0.82f + density * 0.24f + accent * 0.18f + phrase * 0.08f + velocity * 0.08f; + + if(hit_kind == AB_HIT_SNARE) + cap += 0.06f; + + if(cap > 1.34f) + cap = 1.34f; + + cap += (1.0f - gate) * 0.18f; + + if(mul > cap) + mul = cap; + } + + if(expression > 0.50f || accel > 0.42f || (phrase > 0.74f && climax > 0.68f)) + { + float expressive_floor = 0.78f + density * 0.20f + expression * 0.26f + accel * 0.18f; + + if(percussive < 0.46f && expressive_floor > 0.92f) + expressive_floor = 0.92f; + else if(percussive < 0.60f && expressive_floor > 1.08f) + expressive_floor = 1.08f; + + if(mul < expressive_floor) + mul = expressive_floor; + } + + if(percussive < 0.40f && mul > 0.94f) + mul = 0.94f; + else if(percussive < 0.55f && expression < 0.54f && accel < 0.50f && mul > 1.14f) + mul = 1.14f; + else if(percussive < 0.66f && expression < 0.46f && accel < 0.42f && mul > 1.28f) + mul = 1.28f; + + if(snap && snap->bpm > 190.0f && mul > 1.90f) + mul = 1.90f + climax * 0.16f + expression * 0.10f; + else if(snap && snap->bpm > 150.0f && mul > 2.10f) + mul = 2.10f + climax * 0.18f + expression * 0.10f; + + if(mul < 0.32f) + mul = 0.32f; + else if(mul > 2.55f) + mul = 2.55f; + + return ab_breakbeat_clamp_effect_fps(v, base_fps * mul); +} + +static inline float ab_breakbeat_fallback_fps(veejay_t *v, const vj_audio_beat_snapshot_t *snap) +{ + float base_fps = ab_breakbeat_base_fps(v); + float groove = ab_breakbeat_groove_drive(); + float phrase = ab_breakbeat_phrase_drive(); + float climax = ab_breakbeat_climax_drive(); + float tempo = ab_breakbeat_state.tempo_drive; + float pace = ab_breakbeat_pace_drive(snap); + float velocity = ab_breakbeat_velocity(snap); + float activity = ab_breakbeat_activity(snap); + float density = ab_breakbeat_clampf(ab_breakbeat_state.rhythm_density); + float regular = ab_breakbeat_regular_drive(); + float accent = ab_breakbeat_accent_drive(); + float accel = ab_breakbeat_accel_drive(); + float expression = ab_breakbeat_expression_drive(); + float mul; + + if(tempo < pace * 0.20f) + tempo = pace * 0.20f; + + mul = 0.19f + + density * 0.18f + + groove * 0.12f + + phrase * 0.14f + + climax * 0.24f + + tempo * 0.18f + + velocity * 0.08f + + activity * 0.09f + + expression * 0.10f; + + if(snap && snap->beat_age_ms > 1200) + mul *= 0.54f; + else if(snap && snap->beat_age_ms > 700) + mul *= 0.72f; + + if(activity < 0.055f && tempo < 0.16f) + mul = 0.20f; + else if(activity < 0.14f && climax < 0.25f && tempo < 0.28f) + mul *= 0.64f; + + if(pace > 0.0f && snap && snap->beat_age_ms < 1600) + { + float floor_mul = 0.17f + density * 0.14f + pace * 0.10f + tempo * 0.06f; + + if(mul < floor_mul) + mul = floor_mul; + } + + if(regular > 0.58f && expression < 0.40f && snap && snap->beat_age_ms < 900) + { + float floor_mul = 0.54f + density * 0.16f + accent * 0.05f + accel * 0.05f; + float cap_mul = 0.88f + density * 0.13f + phrase * 0.05f + climax * 0.04f; + + if(mul < floor_mul) + mul = floor_mul; + if(mul > cap_mul) + mul = cap_mul; + } + + if(snap && snap->bpm > 190.0f) + mul *= 0.92f; + else if(snap && snap->bpm > 1.0f && snap->bpm < 82.0f) + mul *= 0.94f; + + if(mul < 0.20f) + mul = 0.20f; + else if(mul > 0.86f) + mul = 0.86f; + + return ab_breakbeat_clamp_effect_fps(v, base_fps * mul); +} + +static inline float ab_breakbeat_recent_fallback_floor(veejay_t *v, float fps, long now, const vj_audio_beat_snapshot_t *snap) +{ + long age; + float base; + float floor_fps; + float hold; + float pace; + + if(ab_breakbeat_state.last_hit_ms <= 0 || ab_breakbeat_state.burst_fps <= 0.01f) + return fps; + + age = now - ab_breakbeat_state.last_hit_ms; + if(age < 0 || age > 640) + return fps; + + base = ab_breakbeat_base_fps(v); + hold = 1.0f - ((float)age * (1.0f / 640.0f)); + if(hold < 0.0f) + hold = 0.0f; + else if(hold > 1.0f) + hold = 1.0f; + + pace = ab_breakbeat_pace_drive(snap); + + floor_fps = ab_breakbeat_state.burst_fps * + (0.22f + hold * 0.15f + ab_breakbeat_state.tempo_drive * 0.06f + pace * 0.04f); + + if(floor_fps < base * (0.24f + ab_breakbeat_state.tempo_drive * 0.12f + pace * 0.10f)) + floor_fps = base * (0.24f + ab_breakbeat_state.tempo_drive * 0.12f + pace * 0.10f); + + if(snap && ab_breakbeat_regular_drive() > 0.58f && ab_breakbeat_expression_drive() < 0.40f) + { + float cap = base * (0.74f + ab_breakbeat_state.rhythm_density * 0.18f + + pace * 0.06f + ab_breakbeat_state.tempo_drive * 0.04f); + + if(floor_fps > cap) + floor_fps = cap; + } + + floor_fps = ab_breakbeat_clamp_effect_fps(v, floor_fps); + + return fps < floor_fps ? floor_fps : fps; +} + +static inline int ab_breakbeat_drive_speed(int base_speed, long open_ms, int slice_frames, int repeated, int hit_kind, const vj_audio_beat_snapshot_t *snap) +{ + double fps = ab_breakbeat_video_fps(); + double visible_frames; + float climax = ab_breakbeat_climax_drive(); + float velocity = ab_breakbeat_velocity(snap); + float percussive = ab_breakbeat_percussive_drive(snap, hit_kind); + float expression = ab_breakbeat_expression_drive(); + float accel = ab_breakbeat_accel_drive(); + int wanted; + int drive; + int max_drive; + + if(base_speed < 1) + base_speed = 1; + + if(hit_kind == AB_HIT_HAT) + return base_speed; + + if(hit_kind == AB_HIT_SCRATCH) + { + float v = snap ? snap->flux : 0.0f; + float burst = snap ? snap->transient : 0.0f; + int drive = 1 + (int)(v * 3.0f + burst * 2.0f); + + if(drive < 1) + drive = 1; + else if(drive > 5) + drive = 5; + + return base_speed * drive; + } + + visible_frames = ((double)open_ms * fps) / 1000.0; + if(visible_frames < 1.0) + visible_frames = 1.0; + + wanted = slice_frames; + if(wanted < 6) + wanted = 6; + else if(wanted > 34) + wanted = 34; + + drive = (int)(((double)wanted / visible_frames) + 0.999); + + if(repeated && drive < 3 && percussive > 0.46f) + drive = 3; + + if(velocity > 0.72f && percussive > 0.54f && drive < 4) + drive = 4; + if(climax > 0.66f && percussive > 0.68f && + (expression > 0.46f || accel > 0.46f || repeated) && drive < 5) + drive = 5; + + max_drive = 2 + (int)(climax * 3.60f + velocity * 2.10f + percussive * 1.90f); + + if(velocity > 0.55f && percussive > 0.48f) + max_drive++; + if(climax > 0.50f && percussive > 0.58f) + max_drive++; + + if(percussive < 0.42f && max_drive > 3) + max_drive = 3; + else if(percussive < 0.58f && max_drive > 4) + max_drive = 4; + + if(snap && snap->bpm > 178.0f && max_drive > 6) + max_drive = 6; + else if(max_drive > 8) + max_drive = 8; + + if(max_drive < 2) + max_drive = 2; + + if(drive < 1) + drive = 1; + else if(drive > max_drive) + drive = max_drive; + + return base_speed * drive; +} + +static inline int ab_breakbeat_musical_run_speed(int run_speed, + int hit_kind, + int repeated, + const vj_audio_beat_snapshot_t *snap) +{ + int sign; + int mag; + int cap; + float density; + float regular; + float accent; + float accel; + float expression; + float climax; + float velocity; + float percussive; + + if(hit_kind == AB_HIT_HAT) + return run_speed == 0 ? 1 : (run_speed < 0 ? -1 : 1); + + if(hit_kind == AB_HIT_SCRATCH) + { + int sign = run_speed < 0 ? -1 : 1; + int mag = run_speed < 0 ? -run_speed : run_speed; + float v = snap ? snap->flux : 0.0f; + float burst = snap ? snap->transient : 0.0f; + long age = snap ? snap->beat_age_ms : 0; + int cap = 2 + (int)(v * 2.4f + burst * 1.7f); + + if(mag < 1) + mag = 1; + if(cap < 2) + cap = 2; + else if(cap > 5) + cap = 5; + + if(v < 0.46f && burst < 0.40f && cap > 3) + cap = 3; + if(age > 72L && cap > 3) + cap = 3; + if(age > 125L && cap > 2) + cap = 2; + + if(mag > cap) + mag = cap; + + return sign * mag; + } + + sign = run_speed < 0 ? -1 : 1; + mag = run_speed < 0 ? -run_speed : run_speed; + + if(mag < 1) + mag = 1; + + density = ab_breakbeat_clampf(ab_breakbeat_state.rhythm_density); + regular = ab_breakbeat_regular_drive(); + accent = ab_breakbeat_accent_drive(); + accel = ab_breakbeat_accel_drive(); + expression = ab_breakbeat_expression_drive(); + climax = ab_breakbeat_climax_drive(); + velocity = ab_breakbeat_velocity(snap); + percussive = ab_breakbeat_percussive_drive(snap, hit_kind); + + cap = 1; + + if(repeated || expression > 0.40f || accel > 0.36f || + climax > 0.62f || accent > 0.66f || velocity > 0.74f) + cap = 2; + + cap += (int)(expression * 2.20f + accel * 2.00f + + climax * 3.30f + velocity * 1.45f + density * 0.75f); + + if(hit_kind == AB_HIT_SNARE && (accent > 0.42f || expression > 0.42f)) + cap++; + + if(regular > 0.66f && expression < 0.34f && accel < 0.30f && climax < 0.72f) + { + int regular_cap = repeated || accent > 0.70f ? 2 : 1; + + if(cap > regular_cap) + cap = regular_cap; + } + + if(!repeated && expression < 0.46f && accel < 0.38f && + accent < 0.64f && velocity < 0.78f && cap > 3) + cap = 3; + + if(!repeated && regular > 0.48f && accent < 0.58f && + expression < 0.46f && accel < 0.42f && cap > 2) + cap = 2; + + if(percussive < 0.40f && cap > 3) + cap = 3; + else if(percussive < 0.56f && cap > 4) + cap = 4; + + if(hit_kind == AB_HIT_FULL && percussive < 0.50f && cap > 3) + cap = 3; + + if(sign < 0 && percussive < 0.62f && cap > 3) + cap = 3; + + if(cap > 4 && + !(repeated && percussive > 0.56f && expression > 0.42f && + (accent > 0.54f || accel > 0.46f || climax > 0.70f))) + cap = 4; + + if(cap > 5 && + !(repeated && percussive > 0.70f && expression > 0.56f && + (accent > 0.70f || accel > 0.58f || density > 0.68f) && + (!snap || snap->beat_age_ms <= 80L))) + cap = 5; + + if(snap && snap->beat_age_ms > 95L && cap > 3) + cap = 3; + if(snap && snap->beat_age_ms > 155L && cap > 2) + cap = 2; + + if(snap && snap->bpm > 190.0f && cap > 4) + cap = 4; + else if(snap && snap->bpm > 155.0f && cap > 5) + cap = 5; + + if(cap < 1) + cap = 1; + else if(cap > 7) + cap = 7; + + if(mag > cap) + mag = cap; + + return sign * mag; +} + +static inline long ab_breakbeat_repeat_window_ms(vj_audio_beat_shared_t *s, const vj_audio_beat_snapshot_t *snap) +{ + long gate_ms = ab_breakbeat_param_ms(s, &s->gate_ms, 10, 1000); + long cooldown_ms = ab_breakbeat_param_ms(s, &s->cooldown_ms, 40, 2000); + float climax = ab_breakbeat_climax_drive(); + long window = gate_ms; + + if(snap && snap->hit_kind == AB_HIT_SCRATCH) + { + float amount = snap->beat_density; + float burst = snap->transient; + + window = (long)(110.0 + (double)amount * 120.0 + (double)burst * 70.0 + 0.5); + if(window < 85L) + window = 85L; + else if(window > 360L) + window = 360L; + return window; + } + + if(snap && snap->bpm > 1.0f) + { + double period = 60000.0 / (double)snap->bpm; + double tolerance = 1.0 + (double)climax * 0.32; + long musical; + + if(period < 180.0) + period = 180.0; + else if(period > 2400.0) + period = 2400.0; + + musical = (long)(period * tolerance + (double)gate_ms + 0.5); + if(window < musical) + window = musical; + } + else + { + long fallback = cooldown_ms * (3L + (long)(climax * 2.0f)); + + if(fallback < 260) + fallback = 260; + else if(fallback > 1800) + fallback = 1800; + + if(window < fallback) + window = fallback; + } + + if(window > 3600) + window = 3600; + + return window; +} + +static inline int ab_breakbeat_max_repeats(vj_audio_beat_shared_t *s) +{ + long gate_ms = ab_breakbeat_param_ms(s, &s->gate_ms, 10, 1000); + float climax = ab_breakbeat_climax_drive(); + int repeats = 3 + (int)(gate_ms / 150L) + (int)(climax * 7.0f); + + if(repeats < 3) + repeats = 3; + else if(repeats > 16) + repeats = 16; + + return repeats; +} + +static inline int ab_breakbeat_seek_projected(veejay_t *v, long long frame, int *dir) +{ + int park = 0; + + frame = ab_breakbeat_project_frame(v, frame, dir, &park); + + if(frame > LONG_MAX) + frame = LONG_MAX; + + veejay_set_frame(v, (long)frame); + + return park; +} + +static inline void ab_breakbeat_seek(veejay_t *v, long long frame) +{ + (void)ab_breakbeat_seek_projected(v, frame, NULL); +} + +static void ab_breakbeat_release_transport(veejay_t *v, vj_audio_beat_shared_t *s) +{ + int user_parked = 0; + + if(!ab_breakbeat_state.active) + return; + + if(v && v->settings && s) + user_parked = (v->settings->current_playback_speed == 0 && + !ab_load_i(&s->paused_by_beat)); + + ab_breakbeat_restore_fps(v); + + if(!user_parked && ab_breakbeat_state.saved_speed != 0) + ab_set_speed_from_beat(v, ab_breakbeat_state.saved_speed, 0); + + ab_store_i(&s->paused_by_beat, 0); + ab_store_l(&s->hold_until_ms, 0); + ab_breakbeat_reset_state(); +} + +int vj_audio_beat_release_transport(veejay_t *v, vj_audio_beat_shared_t *s) +{ + int action; + int was_paused; + int was_breakbeat_active; + long hold_until; + int changed = 0; + + if(!s) + return 0; + + action = ab_load_i(&s->action_mode); + was_paused = ab_load_i(&s->paused_by_beat); + was_breakbeat_active = ab_breakbeat_state.active; + hold_until = ab_load_l(&s->hold_until_ms); + + if(was_breakbeat_active) + { + ab_breakbeat_release_transport(v, s); + changed = 1; + } + else if(was_paused) + { + ab_resume_from_consumer(v, s); + changed = 1; + } + else if(hold_until > 0) + { + ab_store_l(&s->hold_until_ms, 0); + changed = 1; + } + + ab_store_i(&s->paused_by_beat, 0); + ab_store_l(&s->hold_until_ms, 0); + ab_store_i(&s->consumed_seq, ab_load_i(&s->hit_seq)); + + if(action == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT && changed) + ab_store_l(&ab_breakbeat_user_override_until_ms, ab_now_ms() + 350L); + + if(changed) + veejay_msg(VEEJAY_MSG_DEBUG, + "[AUDIO-BEAT] released beat transport ownership action=%s(%d) paused=%d break_active=%d hold=%ld", + ab_action_name(action), + action, + was_paused, + was_breakbeat_active, + hold_until); + + return changed; +} + +int vj_audio_beat_disable_for_transport(veejay_t *v, vj_audio_beat_shared_t *s) +{ + if(!s) + return 0; + + vj_audio_beat_release_transport(v, s); + return vj_audio_beat_disable(s); +} + +static int ab_breakbeat_pause_transport(veejay_t *v, vj_audio_beat_shared_t *s, + long now, long hold_ms) +{ + int speed; + + if(!v || !v->settings || !s) + return 0; + + if(hold_ms < 20) + hold_ms = 20; + + speed = v->settings->current_playback_speed; + + if(speed != 0) + { + ab_store_i(&s->resume_speed, speed); + v->settings->previous_playback_speed = speed; + } + + ab_breakbeat_state.fallback_active = 0; + ab_store_i(&s->paused_by_beat, 1); + ab_store_l(&s->hold_until_ms, now + hold_ms); + + if(speed != 0) + ab_set_speed_from_beat(v, 0, 0); + + return 1; +} + +void vj_audio_beat_user_transport_override(veejay_t *v, vj_audio_beat_shared_t *s, + int requested_speed) +{ + int action; + int was_paused; + int was_breakbeat_active; + int manual_pause_active; + int hit_seq; + + if(!s) + return; + + action = ab_load_i(&s->action_mode); + was_paused = ab_load_i(&s->paused_by_beat); + was_breakbeat_active = ab_breakbeat_state.active; + manual_pause_active = ab_breakbeat_user_pause_override_active(); + hit_seq = ab_load_i(&s->hit_seq); + + if(!was_paused && !was_breakbeat_active && !manual_pause_active && + !(action == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT && requested_speed == 0)) + return; + + if(was_breakbeat_active) + { + ab_breakbeat_restore_fps(v); + ab_breakbeat_reset_state(); + } + else if(action == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT && requested_speed == 0) + { + ab_breakbeat_reset_state(); + } + + ab_store_i(&s->paused_by_beat, 0); + ab_store_l(&s->hold_until_ms, 0); + ab_store_i(&s->resume_speed, requested_speed); + ab_store_i(&s->consumed_seq, hit_seq); + + if(action == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) + { + if(requested_speed == 0) + ab_store_l(&ab_breakbeat_user_override_until_ms, AB_BREAKBEAT_USER_PAUSE_OVERRIDE); + else + ab_store_l(&ab_breakbeat_user_override_until_ms, ab_now_ms() + 350L); + } + + veejay_msg(VEEJAY_MSG_DEBUG, + "[AUDIO-BEAT] user transport override cleared beat ownership req=%d action=%s(%d) paused=%d break_active=%d manual_pause=%d", + requested_speed, + ab_action_name(action), + action, + was_paused, + was_breakbeat_active, + requested_speed == 0); +} + +static int ab_breakbeat_fallback_if_due(veejay_t *v, vj_audio_beat_shared_t *s, long now) +{ + vj_audio_beat_snapshot_t snap; + long hold_until; + long long cur; + int slice_frames; + int radius; + int dir; + int speed; + + if(!ab_breakbeat_state.active || !ab_breakbeat_state.anchor_valid) + return 0; + + hold_until = ab_load_l(&s->hold_until_ms); + + if(hold_until <= 0) + return 0; + + if(now < hold_until) + { + if(ab_breakbeat_state.burst_fps > 0.01f && + (ab_breakbeat_state.burst_until_ms <= 0 || now <= ab_breakbeat_state.burst_until_ms)) + ab_breakbeat_apply_fps(v, ab_breakbeat_state.burst_fps); + + return 1; + } + + if(!vj_audio_beat_get_snapshot(s, &snap)) + memset(&snap, 0, sizeof(snap)); + + ab_breakbeat_update_music_state(&snap, now); + + if(ab_breakbeat_should_idle_pause(s, &snap, now)) + { + ab_breakbeat_apply_fps(v, 5.0f); + + return ab_breakbeat_pause_transport(v, s, now, 120); + } + + slice_frames = ab_breakbeat_slice_frames(v, s, &snap); + slice_frames = ab_breakbeat_bound_slice_frames(v, s, slice_frames, &snap); + slice_frames = ab_breakbeat_quantize_slice_frames(v, slice_frames, &snap); + slice_frames = ab_breakbeat_bound_slice_frames(v, s, slice_frames, &snap); + radius = ab_breakbeat_anchor_radius(slice_frames); + cur = ab_breakbeat_clamp_frame(v, ab_breakbeat_current_frame(v)); + + if(ab_breakbeat_state.local_loop_active && + ab_breakbeat_state.local_loop_until_ms > 0 && + now > ab_breakbeat_state.local_loop_until_ms) + ab_breakbeat_clear_local_loop(); + + if(ab_breakbeat_state.local_loop_active && + ab_breakbeat_state.loop_hi > ab_breakbeat_state.loop_lo) + { + dir = ab_breakbeat_state.fallback_dir != 0 ? + ab_breakbeat_state.fallback_dir : + (ab_breakbeat_state.direction != 0 ? ab_breakbeat_state.direction : 1); + + if(cur <= ab_breakbeat_state.loop_lo) + { + dir = 1; + ab_breakbeat_seek(v, ab_breakbeat_state.loop_lo); + } + else if(cur >= ab_breakbeat_state.loop_hi) + { + dir = -1; + ab_breakbeat_seek(v, ab_breakbeat_state.loop_hi); + } + + speed = dir; + ab_breakbeat_state.fallback_active = 1; + ab_breakbeat_state.fallback_dir = dir; + ab_store_l(&s->hold_until_ms, now + 35); + { + float fb_fps = ab_breakbeat_fallback_fps(v, &snap); + fb_fps = ab_breakbeat_recent_fallback_floor(v, fb_fps, now, &snap); + ab_breakbeat_apply_fps(v, fb_fps); + } + ab_breakbeat_apply_transport(v, speed); + return 1; + } + + if(ab_breakbeat_is_sample(v)) + { + long long lo = 0; + long long hi = 0; + int looptype = ab_breakbeat_sample_looptype(v); + + ab_breakbeat_bounds(v, &lo, &hi); + ab_breakbeat_state.loop_lo = lo; + ab_breakbeat_state.loop_hi = hi; + + if(looptype == 1) + { + if(cur >= hi && ab_breakbeat_state.fallback_dir >= 0) + { + dir = 1; + ab_breakbeat_seek(v, lo); + } + else if(cur <= lo && ab_breakbeat_state.fallback_dir < 0) + { + dir = -1; + ab_breakbeat_seek(v, hi); + } + else + { + dir = ab_breakbeat_state.fallback_dir != 0 ? + ab_breakbeat_state.fallback_dir : + (ab_breakbeat_state.direction != 0 ? ab_breakbeat_state.direction : 1); + } + } + else if(looptype == 2) + { + dir = ab_breakbeat_state.fallback_dir != 0 ? + ab_breakbeat_state.fallback_dir : + (ab_breakbeat_state.direction != 0 ? ab_breakbeat_state.direction : 1); + + if(cur <= lo) + { + dir = 1; + ab_breakbeat_seek(v, lo); + } + else if(cur >= hi) + { + dir = -1; + ab_breakbeat_seek(v, hi); + } + } + else + { + dir = ab_breakbeat_state.fallback_dir != 0 ? + ab_breakbeat_state.fallback_dir : + (ab_breakbeat_state.direction != 0 ? ab_breakbeat_state.direction : 1); + + if(cur <= lo || cur >= hi) + { + return ab_breakbeat_pause_transport(v, s, now, 120); + } + } + } + else + { + ab_breakbeat_state.loop_lo = ab_breakbeat_clamp_frame(v, ab_breakbeat_state.anchor_frame - radius); + ab_breakbeat_state.loop_hi = ab_breakbeat_clamp_frame(v, ab_breakbeat_state.anchor_frame + radius); + + if(ab_breakbeat_state.loop_hi < ab_breakbeat_state.loop_lo) + { + long long tmp = ab_breakbeat_state.loop_lo; + ab_breakbeat_state.loop_lo = ab_breakbeat_state.loop_hi; + ab_breakbeat_state.loop_hi = tmp; + } + + dir = ab_breakbeat_state.fallback_dir != 0 ? + ab_breakbeat_state.fallback_dir : + (ab_breakbeat_state.direction != 0 ? ab_breakbeat_state.direction : 1); + + if(cur <= ab_breakbeat_state.loop_lo) + { + dir = 1; + ab_breakbeat_seek(v, ab_breakbeat_state.loop_lo); + } + else if(cur >= ab_breakbeat_state.loop_hi) + { + dir = -1; + ab_breakbeat_seek(v, ab_breakbeat_state.loop_hi); + } + } + + speed = dir; + + ab_breakbeat_state.fallback_active = 1; + ab_breakbeat_state.fallback_dir = dir; + ab_store_l(&s->hold_until_ms, now + 35); + { + float fb_fps = ab_breakbeat_fallback_fps(v, &snap); + fb_fps = ab_breakbeat_recent_fallback_floor(v, fb_fps, now, &snap); + ab_breakbeat_apply_fps(v, fb_fps); + } + ab_breakbeat_apply_transport(v, speed); + + return 1; +} + +static int ab_breakbeat_consume(veejay_t *v, vj_audio_beat_shared_t *s, + long now, int hit_seq, int consumed_seq) +{ + ab_breakbeat_hit_event_t ev; + vj_audio_beat_snapshot_t snap; + long open_ms; + long repeat_ms; + long event_ms; + long stale_ms; + long event_age_ms; + long long cur; + long long target; + int hit_kind; + int base_speed; + int run_speed; + int slice_frames; + int repeated; + int cur_scene_id; + int max_repeats; + int forward_jump; + int forward_jump_frames; + int scratch_dir; + float scratch_amount; + float scratch_velocity; + float scratch_burst; + float target_fps; + long override_until; + + if(ab_breakbeat_respect_user_pause(v, s, hit_seq)) + return 0; + + if(ab_breakbeat_detect_user_resume(v, s, now, hit_seq)) + return 0; + + override_until = ab_load_l(&ab_breakbeat_user_override_until_ms); + if(override_until > 0) + { + if(override_until == AB_BREAKBEAT_USER_PAUSE_OVERRIDE || now < override_until) + { + if(hit_seq != consumed_seq) + ab_store_i(&s->consumed_seq, hit_seq); + ab_breakbeat_hit_queue_clear(); + return 0; + } + + ab_store_l(&ab_breakbeat_user_override_until_ms, 0); + } + + memset(&ev, 0, sizeof(ev)); + + if(!ab_breakbeat_hit_queue_pop_after(consumed_seq, &ev)) + { + if(hit_seq == consumed_seq) + return ab_breakbeat_fallback_if_due(v, s, now); + + if(!vj_audio_beat_get_snapshot(s, &snap)) + memset(&snap, 0, sizeof(snap)); + + ev.valid = 1; + ev.seq = hit_seq; + ev.hit_ms = snap.last_hit_ms > 0 ? snap.last_hit_ms : now; + ev.publish_ms = now; + ev.block_ms = 0; + ev.snap = snap; + } + else + { + snap = ev.snap; + hit_seq = ev.seq; + } + + if(hit_seq <= consumed_seq) + return ab_breakbeat_fallback_if_due(v, s, now); + + event_ms = ev.hit_ms > 0 ? ev.hit_ms : now; + event_age_ms = now >= event_ms ? now - event_ms : 0; + + if(snap.hit_seq <= 0) + snap.hit_seq = hit_seq; + snap.hit_seq = hit_seq; + snap.last_hit_ms = event_ms; + snap.beat_age_ms = event_age_ms; + + if(snap.hit_kind == AB_HIT_SCRATCH) + { + if(ev.scratch_amount <= 0.0f) + ev.scratch_amount = ab_from_q15(ab_load_i(&ab_scratch_q15)); + if(ev.scratch_velocity <= 0.0f) + ev.scratch_velocity = ab_from_q15(ab_load_i(&ab_scratch_velocity_q15)); + if(ev.scratch_burst <= 0.0f) + ev.scratch_burst = ab_from_q15(ab_load_i(&ab_scratch_burst_q15)); + if(ev.scratch_dir == 0) + ev.scratch_dir = ab_load_i(&ab_scratch_dir) < 0 ? -1 : 1; + + snap.beat_density = ev.scratch_amount; + snap.flux = ev.scratch_velocity; + snap.transient = ev.scratch_burst; + snap.beat_pulse = ev.scratch_burst; + snap.beat_gate = ev.scratch_amount > 0.18f ? 1.0f : 0.0f; + } + + stale_ms = 720L; + { + double period = ab_breakbeat_transport_period_ms(&snap); + if(period > 1.0) + { + stale_ms = (long)(period * 0.84 + 0.5); + if(stale_ms < 180L) + stale_ms = 180L; + else if(stale_ms > 900L) + stale_ms = 900L; + } + } + + if(snap.hit_kind == AB_HIT_SCRATCH) + { + long scratch_stale = (long)(118.0 + (double)ev.scratch_amount * 74.0 + + (double)ev.scratch_burst * 48.0 + 0.5); + if(scratch_stale < 110L) + scratch_stale = 110L; + else if(scratch_stale > 240L) + scratch_stale = 240L; + if(stale_ms > scratch_stale) + stale_ms = scratch_stale; + } + + if(event_age_ms > stale_ms) + { + ab_store_i(&s->consumed_seq, hit_seq); + return ab_breakbeat_fallback_if_due(v, s, now); + } + + ab_breakbeat_update_music_state(&snap, event_ms); + + hit_kind = snap.hit_kind; + scratch_amount = ev.scratch_amount; + scratch_velocity = ev.scratch_velocity; + scratch_burst = ev.scratch_burst; + scratch_dir = ev.scratch_dir < 0 ? -1 : 1; + open_ms = ab_breakbeat_open_ms(s, &snap, hit_kind); + repeat_ms = ab_breakbeat_repeat_window_ms(s, &snap); + cur = ab_breakbeat_frame_from_hit_time(v, s, event_ms, now); + cur = ab_breakbeat_scene_refine_frame(v, cur, &snap); + cur_scene_id = ab_breakbeat_scene_id_for_frame(v, cur); + base_speed = ab_breakbeat_base_speed(v, s); + slice_frames = ab_breakbeat_slice_frames(v, s, &snap); + slice_frames = ab_breakbeat_bound_slice_frames(v, s, slice_frames, &snap); + slice_frames = ab_breakbeat_quantize_slice_frames(v, slice_frames, &snap); + slice_frames = ab_breakbeat_bound_slice_frames(v, s, slice_frames, &snap); + max_repeats = ab_breakbeat_max_repeats(s); + + repeated = ab_breakbeat_state.anchor_valid && + ab_breakbeat_state.last_hit_ms > 0 && + event_ms >= ab_breakbeat_state.last_hit_ms && + (event_ms - ab_breakbeat_state.last_hit_ms) <= repeat_ms && + ab_breakbeat_state.repeat_count < max_repeats; + + if(repeated && + ab_breakbeat_state.anchor_scene_id > 0 && + cur_scene_id > 0 && + cur_scene_id != ab_breakbeat_state.anchor_scene_id) + repeated = 0; + + if(!ab_breakbeat_transport_hit_allowed(&snap, event_ms, hit_kind, repeated)) + { + ab_store_i(&s->consumed_seq, hit_seq); + return ab_breakbeat_fallback_if_due(v, s, now); + } + + if(!ab_breakbeat_state.active) + { + ab_breakbeat_state.saved_speed = base_speed; + ab_breakbeat_state.saved_fps = ab_breakbeat_runtime_fps(v); + ab_breakbeat_state.base_fps = (float)ab_breakbeat_video_fps(); + if(ab_breakbeat_state.base_fps <= 0.01f || ab_breakbeat_state.base_fps > 120.0f) + ab_breakbeat_state.base_fps = ab_breakbeat_state.saved_fps; + if(ab_breakbeat_state.base_fps > 60.0f) + ab_breakbeat_state.base_fps = 60.0f; + if(ab_breakbeat_state.saved_fps > ab_breakbeat_max_effect_fps(v)) + ab_breakbeat_state.saved_fps = ab_breakbeat_state.base_fps; + ab_breakbeat_state.current_fps = ab_breakbeat_state.saved_fps; + if(ab_breakbeat_state.current_fps > ab_breakbeat_max_effect_fps(v)) + ab_breakbeat_state.current_fps = ab_breakbeat_state.base_fps; + if(ab_breakbeat_state.current_fps < 5.0f) + ab_breakbeat_state.current_fps = 5.0f; + ab_breakbeat_state.target_fps = ab_breakbeat_state.current_fps; + ab_breakbeat_state.fps_last_ms = now; + ab_breakbeat_state.fps_write_last_ms = now; + ab_breakbeat_state.tempo_drive = 0.0f; + ab_breakbeat_state.tempo_last_hit_ms = 0; + ab_breakbeat_state.tempo_prev_interval_ms = 0; + } + + if(!repeated) + { + ab_breakbeat_state.anchor_valid = 1; + ab_breakbeat_state.anchor_frame = cur; + ab_breakbeat_state.anchor_scene_id = cur_scene_id; + ab_breakbeat_state.anchor_cut_frame = ab_breakbeat_scene_last_cut_frame(v); + ab_breakbeat_state.repeat_count = 0; + ab_breakbeat_state.direction = 1; + ab_breakbeat_state.fallback_dir = 1; + } + else + { + ab_breakbeat_state.repeat_count++; + } + + if(ab_breakbeat_state.direction == 0) + ab_breakbeat_state.direction = 1; + + if(hit_kind == AB_HIT_SCRATCH) + { + ab_breakbeat_state.direction = scratch_dir != 0 ? scratch_dir : ab_breakbeat_state.direction; + if(repeated && scratch_burst > 0.62f) + ab_breakbeat_state.direction = -ab_breakbeat_state.direction; + } + else if(hit_kind == AB_HIT_SNARE) + ab_breakbeat_state.direction = -ab_breakbeat_state.direction; + else if(hit_kind != AB_HIT_HAT && repeated) + ab_breakbeat_state.direction = (ab_breakbeat_state.repeat_count & 1) ? -1 : 1; + + target = ab_breakbeat_state.anchor_frame; + run_speed = ab_breakbeat_drive_speed(base_speed, open_ms, slice_frames, repeated, hit_kind, &snap); + target_fps = ab_breakbeat_hit_fps(v, &snap, repeated, hit_kind); + if(ab_breakbeat_state.tempo_drive > 0.60f && hit_kind != AB_HIT_HAT && hit_kind != AB_HIT_SCRATCH) + { + float pace = ab_breakbeat_pace_drive(&snap); + float phrase = ab_breakbeat_phrase_drive(); + float climax = ab_breakbeat_climax_drive(); + float velocity = ab_breakbeat_velocity(&snap); + float expression = ab_breakbeat_expression_drive(); + float accel = ab_breakbeat_accel_drive(); + + if(accel > 0.32f || expression > 0.46f || + (phrase > 0.76f && climax > 0.78f && velocity > 0.74f)) + { + float accel_floor = ab_breakbeat_base_fps(v) * + (0.66f + ab_breakbeat_state.tempo_drive * 0.24f + + pace * 0.08f + expression * 0.20f + accel * 0.22f); + if(target_fps < accel_floor) + target_fps = ab_breakbeat_clamp_effect_fps(v, accel_floor); + } + } + forward_jump = hit_kind == AB_HIT_SCRATCH ? 0 : ab_breakbeat_should_forward_jump(v, &snap, event_ms); + forward_jump_frames = forward_jump ? ab_breakbeat_forward_jump_frames(v, s, &snap, slice_frames) : 0; + if(forward_jump_frames > 0) + forward_jump_frames = ab_breakbeat_quantize_slice_frames(v, forward_jump_frames, &snap); + if(forward_jump && forward_jump_frames <= 0) + forward_jump = 0; + + if(forward_jump) + { + target = cur + (long long)forward_jump_frames; + ab_breakbeat_state.direction = 1; + ab_breakbeat_state.fallback_dir = 1; + run_speed = 1; + + { + float base = ab_breakbeat_base_fps(v); + float expression = ab_breakbeat_expression_drive(); + float regular = ab_breakbeat_regular_drive(); + float jump_fps = base * + (0.98f + ab_breakbeat_velocity(&snap) * 0.36f + + ab_breakbeat_phrase_drive() * 0.16f + + ab_breakbeat_climax_drive() * 0.58f + + expression * 0.32f); + + if(regular > 0.58f && expression < 0.46f && + !(ab_breakbeat_phrase_drive() > 0.80f && ab_breakbeat_climax_drive() > 0.84f && + ab_breakbeat_velocity(&snap) > 0.86f && ab_breakbeat_activity(&snap) > 0.72f) && jump_fps > base * 1.28f) + jump_fps = base * 1.28f; + else if(snap.bpm > 185.0f && jump_fps > base * 2.10f) + jump_fps = base * 2.10f; + + if(jump_fps > target_fps) + target_fps = ab_breakbeat_clamp_effect_fps(v, jump_fps); + } + } + else if(hit_kind == AB_HIT_SCRATCH) + { + int scrub = 1 + (int)(scratch_velocity * 5.0f) + (int)(scratch_burst * 4.0f) + (repeated ? 1 : 0); + float base = ab_breakbeat_base_fps(v); + float scratch_fps = base * (0.72f + scratch_amount * 0.42f + scratch_velocity * 0.52f + scratch_burst * 0.28f); + + if(scrub < 1) + scrub = 1; + else if(scrub > 12) + scrub = 12; + + scrub = ab_breakbeat_quantize_slice_frames(v, scrub, &snap); + if(scrub > 12) + scrub = 12; + + target = cur + (long long)ab_breakbeat_state.direction * (long long)scrub; + run_speed = ab_breakbeat_state.direction * (1 + (int)(scratch_velocity * 1.6f + scratch_burst * 1.6f)); + if(scratch_velocity < 0.48f && scratch_burst < 0.42f && (run_speed > 3 || run_speed < -3)) + run_speed = ab_breakbeat_state.direction * 3; + + if(scratch_fps > target_fps) + target_fps = ab_breakbeat_clamp_effect_fps(v, scratch_fps); + } + else if(hit_kind == AB_HIT_HAT) + { + int tick = 1 + (int)(snap.high * 3.0f) + (int)(snap.transient * 2.0f); + + if(tick < 1) + tick = 1; + else if(tick > 6) + tick = 6; + + tick = ab_breakbeat_quantize_slice_frames(v, tick, &snap); + if(tick > 6) + tick = 6; + + target += (long long)ab_breakbeat_state.direction * (long long)tick; + run_speed = ab_breakbeat_state.direction; + target_fps = ab_breakbeat_hit_fps(v, &snap, repeated, hit_kind); + } + else if(ab_breakbeat_state.direction < 0) + { + target += (long long)slice_frames; + run_speed = -run_speed; + } + else + { + target = ab_breakbeat_state.anchor_frame; + } + + target = ab_breakbeat_scene_guard_target(v, cur, target, &snap, forward_jump); + + ab_store_i(&s->consumed_seq, hit_seq); + ab_breakbeat_state.active = 1; + ab_breakbeat_state.last_hit_seq = hit_seq; + ab_breakbeat_state.last_hit_ms = event_ms; + ab_breakbeat_state.saved_speed = base_speed; + + { + int parked = 0; + long long projected_target = ab_breakbeat_project_frame(v, target, &ab_breakbeat_state.direction, &parked); + int radius = ab_breakbeat_local_radius(slice_frames, &snap); + + projected_target = ab_breakbeat_scene_refine_frame(v, projected_target, &snap); + + if(projected_target > LONG_MAX) + projected_target = LONG_MAX; + + veejay_set_frame(v, (long)projected_target); + + if(forward_jump) + { + ab_breakbeat_state.anchor_frame = projected_target; + ab_breakbeat_state.anchor_scene_id = ab_breakbeat_scene_id_for_frame(v, projected_target); + ab_breakbeat_state.anchor_cut_frame = ab_breakbeat_scene_last_cut_frame(v); + ab_breakbeat_state.repeat_count = 0; + ab_breakbeat_state.last_forward_jump_ms = event_ms; + ab_breakbeat_clear_local_loop(); + } + else if(ab_breakbeat_should_use_local_loop(&snap, repeated)) + { + ab_breakbeat_set_local_loop(v, projected_target, radius); + if(hit_kind == AB_HIT_SCRATCH) + ab_breakbeat_state.local_loop_until_ms = now + open_ms + 24L + + (long)(scratch_amount * 90.0f); + else + ab_breakbeat_state.local_loop_until_ms = now + open_ms + 90L + + (long)(ab_breakbeat_climax_drive() * 420.0f); + } + else + { + ab_breakbeat_clear_local_loop(); + } + + if(parked) + run_speed = 0; + else if(run_speed == 0 && ab_breakbeat_state.direction != 0) + run_speed = ab_breakbeat_state.direction; + else if(run_speed == 0) + run_speed = 1; + } + + run_speed = ab_breakbeat_musical_run_speed(run_speed, hit_kind, repeated, &snap); + run_speed = ab_breakbeat_scale_speed_for_sfd(v, run_speed); + + ab_breakbeat_state.burst_fps = target_fps; + ab_breakbeat_state.burst_until_ms = now + open_ms; + + ab_breakbeat_apply_fps(v, target_fps); + + ab_breakbeat_state.fallback_active = 0; + ab_breakbeat_state.fallback_dir = ab_breakbeat_state.direction; + ab_store_i(&s->resume_speed, run_speed); + ab_store_l(&s->hold_until_ms, now + open_ms); + ab_breakbeat_apply_transport(v, run_speed); + + return 1; +} + int vj_audio_beat_resume_if_due(veejay_t *v, vj_audio_beat_shared_t *s) { int enabled; @@ -3209,18 +7469,55 @@ int vj_audio_beat_resume_if_due(veejay_t *v, vj_audio_beat_shared_t *s) if(!v || !v->settings || !s || !ab_load_i(&s->initialized)) return 0; - if(!ab_load_i(&s->paused_by_beat)) - return 0; - now = ab_now_ms(); enabled = ab_load_i(&s->enabled); action = ab_load_i(&s->action_mode); hold_until = ab_load_l(&s->hold_until_ms); + if(ab_breakbeat_state.active && (!enabled || action != VJ_AUDIO_BEAT_ACTION_BREAK_BEAT)) + { + ab_breakbeat_release_transport(v, s); + return 1; + } + + if(!ab_load_i(&s->paused_by_beat) && action != VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) + return 0; + if(!enabled || (action != VJ_AUDIO_BEAT_ACTION_FREEZE && - action != VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX) || - (hold_until > 0 && now >= hold_until)) + action != VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX && + action != VJ_AUDIO_BEAT_ACTION_BREAK_BEAT)) + { + if(ab_breakbeat_state.active) + ab_breakbeat_release_transport(v, s); + else + ab_resume_from_consumer(v, s); + return 1; + } + + if(action == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) + { + int cur_hit_seq = ab_load_i(&s->hit_seq); + + if(ab_breakbeat_resume_owned_pause(v, s, now, cur_hit_seq)) + return 1; + + if(ab_breakbeat_respect_user_pause(v, s, cur_hit_seq)) + return 0; + + if(ab_breakbeat_detect_user_resume(v, s, now, cur_hit_seq)) + return 0; + + if(ab_breakbeat_user_pause_override_active()) + return 0; + + return ab_breakbeat_fallback_if_due(v, s, now); + } + + if(ab_freeze_transport_uses_gate(s)) + return 0; + + if(hold_until > 0 && now >= hold_until) { ab_resume_from_consumer(v, s); return 1; @@ -3228,6 +7525,7 @@ int vj_audio_beat_resume_if_due(veejay_t *v, vj_audio_beat_shared_t *s) return 0; } + int vj_audio_beat_consume(veejay_t *v, vj_audio_beat_shared_t *s) { int enabled; @@ -3243,11 +7541,24 @@ int vj_audio_beat_consume(veejay_t *v, vj_audio_beat_shared_t *s) enabled = ab_load_i(&s->enabled); action = ab_load_i(&s->action_mode); - if(ab_load_i(&s->paused_by_beat)) + if(ab_breakbeat_state.active && action != VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) + { + ab_breakbeat_release_transport(v, s); + return 1; + } + + if(ab_load_i(&s->paused_by_beat) && + action != VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) vj_audio_beat_resume_if_due(v, s); if(!enabled) + { + if(ab_breakbeat_state.active) + ab_breakbeat_release_transport(v, s); + else if(ab_load_i(&s->paused_by_beat)) + ab_resume_from_consumer(v, s); return 0; + } hit_seq = ab_load_i(&s->hit_seq); consumed_seq = ab_load_i(&s->consumed_seq); @@ -3260,56 +7571,36 @@ int vj_audio_beat_consume(veejay_t *v, vj_audio_beat_shared_t *s) return 0; } + if(action == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) + { + if(ab_breakbeat_resume_owned_pause(v, s, now, hit_seq)) + return 1; + + if(ab_breakbeat_respect_user_pause(v, s, hit_seq)) + return 0; + + if(ab_breakbeat_detect_user_resume(v, s, now, hit_seq)) + return 0; + + if(ab_breakbeat_user_pause_override_active()) + { + if(hit_seq != consumed_seq) + ab_store_i(&s->consumed_seq, hit_seq); + ab_breakbeat_hit_queue_clear(); + return 0; + } + + return ab_breakbeat_consume(v, s, now, hit_seq, consumed_seq); + } + if(action != VJ_AUDIO_BEAT_ACTION_FREEZE && action != VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX) return 0; - if(hit_seq == consumed_seq) - return 0; + if(ab_freeze_transport_uses_gate(s)) + return ab_freeze_gate_consume(v, s, now, hit_seq, consumed_seq); - ab_store_i(&s->consumed_seq, hit_seq); - - if(ab_load_i(&s->paused_by_beat)) - { - long freeze = (long)ab_load_i(&s->freeze_ms); - - ab_store_l(&s->hold_until_ms, now + freeze); - ab_store_i(&s->consumed_seq, hit_seq); - - /*veejay_msg(VEEJAY_MSG_INFO, - "[AUDIO-BEAT] freeze action: extending pause by %ld ms on hit_seq=%d", - freeze, - hit_seq); - */ - return 1; - } - - if(v->settings->current_playback_speed != 0) - { - int speed = v->settings->current_playback_speed; - long freeze = (long)ab_load_i(&s->freeze_ms); - - ab_store_i(&s->resume_speed, speed); - v->settings->previous_playback_speed = speed; - - /*veejay_msg(VEEJAY_MSG_INFO, - "[AUDIO-BEAT] freeze action: pausing playback for %ld ms on hit_seq=%d speed=%d mode=%d sample=%d", - freeze, - hit_seq, - speed, - v->uc ? v->uc->playback_mode : -1, - v->uc ? v->uc->sample_id : -1); - */ - ab_store_i(&s->paused_by_beat, 1); - ab_store_l(&s->hold_until_ms, now + freeze); - ab_store_i(&s->consumed_seq, hit_seq); - - veejay_set_speed(v, 0, 0); - - return 1; - } - - return 0; + return ab_freeze_hit_pause_consume(v, s, now, hit_seq, consumed_seq); } int vj_audio_beat_is_enabled(vj_audio_beat_shared_t *s) @@ -3346,8 +7637,6 @@ int vj_audio_beat_is_paused_by_beat(vj_audio_beat_shared_t *s) void vj_audio_beat_set_freeze_ms(vj_audio_beat_shared_t *s, int ms) { - int requested = ms; - if(!s) return; @@ -3361,8 +7650,6 @@ void vj_audio_beat_set_freeze_ms(vj_audio_beat_shared_t *s, int ms) void vj_audio_beat_set_cooldown_ms(vj_audio_beat_shared_t *s, int ms) { - int requested = ms; - if(!s) return; @@ -3376,8 +7663,6 @@ void vj_audio_beat_set_cooldown_ms(vj_audio_beat_shared_t *s, int ms) void vj_audio_beat_set_threshold(vj_audio_beat_shared_t *s, int threshold) { - int requested = threshold; - if(!s) return; @@ -3391,7 +7676,6 @@ void vj_audio_beat_set_threshold(vj_audio_beat_shared_t *s, int threshold) void vj_audio_beat_set_input_channels(vj_audio_beat_shared_t *s, int channels) { - int requested = channels; int old_channels; if(!s) @@ -3429,37 +7713,104 @@ void vj_audio_beat_set_input_channels(vj_audio_beat_shared_t *s, int channels) } } +static inline int ab_normalize_action(int action) +{ + if(action < VJ_AUDIO_BEAT_ACTION_NONE) + return VJ_AUDIO_BEAT_ACTION_NONE; + + if(action > VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) + return VJ_AUDIO_BEAT_ACTION_BREAK_BEAT; + + return action; +} + +static void ab_log_action_transport_mode(vj_audio_beat_shared_t *s, int action, const char *reason) +{ + const char *mode = "none"; + + if(action == VJ_AUDIO_BEAT_ACTION_FREEZE || + action == VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX) + { + mode = ab_freeze_transport_uses_gate(s) ? "freeze-gate-open" : "freeze-hit-pause"; + } + else if(action == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) + { + mode = "break-beat-transport"; + } + else if(action == VJ_AUDIO_BEAT_ACTION_AUTO_FX) + { + mode = "auto-fx-render"; + } + + veejay_msg(VEEJAY_MSG_INFO, + "[AUDIO-BEAT] Action mode set to %s(%d), transport=%s%s%s", + ab_action_name(action), + action, + mode, + reason ? " reason=" : "", + reason ? reason : ""); +} + void vj_audio_beat_set_action(vj_audio_beat_shared_t *s, int action) { - int requested = action; int old_action; int hit_seq; if(!s) return; - if(action < VJ_AUDIO_BEAT_ACTION_NONE) - action = VJ_AUDIO_BEAT_ACTION_NONE; - else if(action > VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX) - action = VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX; - + action = ab_normalize_action(action); old_action = ab_load_i(&s->action_mode); hit_seq = ab_load_i(&s->hit_seq); - ab_store_i(&s->action_mode, action); + if(old_action == action) + return; - if(action == VJ_AUDIO_BEAT_ACTION_FREEZE || - action == VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX) + ab_store_i(&s->action_mode, action); + ab_store_i(&s->consumed_seq, hit_seq); + + if(old_action == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT && + action != VJ_AUDIO_BEAT_ACTION_BREAK_BEAT && + ab_breakbeat_state.active) { - ab_store_i(&s->consumed_seq, hit_seq); + veejay_msg(VEEJAY_MSG_DEBUG, + "[AUDIO-BEAT] action change preserves active break-beat transport until release old=%s(%d) new=%s(%d)", + ab_action_name(old_action), old_action, + ab_action_name(action), action); + } + else if(old_action == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT || + action == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) + { + ab_breakbeat_reset_state(); } + if(action != VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) + ab_store_l(&ab_breakbeat_user_override_until_ms, 0); + + ab_log_action_transport_mode(s, action, "set-action"); +} + +void vj_audio_beat_set_action_for_transport(veejay_t *v, + vj_audio_beat_shared_t *s, + int action) +{ + int old_action; + int new_action; + + if(!s) + return; + + new_action = ab_normalize_action(action); + old_action = ab_load_i(&s->action_mode); + + if(old_action != new_action) + vj_audio_beat_release_transport(v, s); + + vj_audio_beat_set_action(s, new_action); } void vj_audio_beat_set_pulse_ms(vj_audio_beat_shared_t *s, int ms) { - int requested = ms; - if(!s) return; @@ -3473,8 +7824,6 @@ void vj_audio_beat_set_pulse_ms(vj_audio_beat_shared_t *s, int ms) void vj_audio_beat_set_gate_ms(vj_audio_beat_shared_t *s, int ms) { - int requested = ms; - if(!s) return; @@ -3518,6 +7867,8 @@ int vj_audio_beat_get_snapshot(vj_audio_beat_shared_t *s, vj_audio_beat_snapshot long age; int pulse_ms; int gate_ms; + int hit_kind; + float bpm; float pulse; if(!s || !dst) @@ -3529,14 +7880,11 @@ int vj_audio_beat_get_snapshot(vj_audio_beat_shared_t *s, vj_audio_beat_snapshot last = ab_load_l(&s->last_hit_ms); age = last > 0 ? now - last : 2147483647L; - pulse_ms = ab_load_i(&s->pulse_ms); - gate_ms = ab_load_i(&s->gate_ms); + bpm = (float)ab_load_i(&s->bpm_q8) * (1.0f / 256.0f); + hit_kind = ab_load_i(&ab_last_hit_kind); - if(pulse_ms < 1) - pulse_ms = 1; - - if(gate_ms < 1) - gate_ms = 1; + pulse_ms = (int)ab_effective_snapshot_window_ms(ab_load_i(&s->pulse_ms), bpm, hit_kind, 0); + gate_ms = (int)ab_effective_snapshot_window_ms(ab_load_i(&s->gate_ms), bpm, hit_kind, 1); pulse = age >= 0 && age < pulse_ms ? 1.0f - ((float)age / (float)pulse_ms) @@ -3563,11 +7911,11 @@ int vj_audio_beat_get_snapshot(vj_audio_beat_shared_t *s, vj_audio_beat_snapshot dst->beat_pulse = pulse; dst->beat_gate = age >= 0 && age < gate_ms ? 1.0f : 0.0f; dst->beat_toggle = ab_from_q15(ab_load_i(&s->beat_toggle_q15)); - dst->bpm = (float)ab_load_i(&s->bpm_q8) * (1.0f / 256.0f); + dst->bpm = bpm; dst->kick = ab_from_q15(ab_load_i(&ab_kick_q15)); dst->snare = ab_from_q15(ab_load_i(&ab_snare_q15)); dst->hat = ab_from_q15(ab_load_i(&ab_hat_q15)); - dst->hit_kind = ab_load_i(&ab_last_hit_kind); + dst->hit_kind = hit_kind; { double beat_ms = dst->bpm > 1.0f ? 60000.0 / (double)dst->bpm : (double)(pulse_ms * 4); @@ -3592,12 +7940,17 @@ int vj_audio_beat_get_snapshot(vj_audio_beat_shared_t *s, vj_audio_beat_snapshot : 0.0; if(dst->bpm > 1.0f) - density = ((double)dst->bpm - 45.0) / 135.0; + density = ((double)dst->bpm - 45.0) / 155.0; else density = 0.0; density = ab_clampd(density, 0.0, 1.0); - density = density * 0.62 + (double)dst->beat_gate * 0.20 + (double)dst->beat_pulse * 0.18; + density = density * 0.42 + + (double)dst->beat_gate * 0.16 + + (double)dst->beat_pulse * 0.16 + + (double)(dst->kick + dst->snare) * 0.10 + + (double)dst->flux * 0.08 + + (double)dst->envelope * 0.08; dst->beat_trail_length = (float)ab_clampd(trail, 0.0, 1.0); dst->beat_density = (float)ab_clampd(density, 0.0, 1.0); @@ -4068,6 +8421,7 @@ static int ab_auto_is_memory_name(const char *name) } +#ifdef VEEJAY_AUDIO_BEAT_AUTO_DEBUG static const char *ab_auto_hint_class_name(int klass) { #ifdef VJ_BEAT_F_REJECT @@ -4113,6 +8467,7 @@ static const char *ab_auto_hint_class_name(int klass) return "none"; #endif } +#endif #ifdef VJ_BEAT_F_REJECT @@ -4382,6 +8737,11 @@ static int ab_auto_hint_to_role( score -= 32; } +#ifdef VJ_BEAT_F_INVERTED + if(flags & VJ_BEAT_F_INVERTED) + inv = 1; +#endif + *role = r; *invert = inv; *amount_pct = amt; @@ -4820,6 +9180,7 @@ static int ab_auto_role_priority(int role) } } +#ifdef VEEJAY_AUDIO_BEAT_AUTO_DEBUG static const char *ab_auto_role_name(int role) { switch(role) @@ -4841,6 +9202,7 @@ static const char *ab_auto_role_name(int role) default: return "unknown"; } } +#endif static int ab_auto_sanitize_soft_range_values( int hard_min, @@ -5041,6 +9403,115 @@ static const ab_auto_param_meta_t *ab_auto_meta_for(int fx_id, int param_nr) return &ab_auto_fx_table[fx_id].params[param_nr]; } +static int ab_auto_hint_class_curve_mix_compatible(int klass) +{ + switch(klass) + { + case VJ_BEAT_FLOW: + case VJ_BEAT_DRIFT: + case VJ_BEAT_WARP: + case VJ_BEAT_MOTION_REACT: + case VJ_BEAT_GEOMETRY_AMPLITUDE: + case VJ_BEAT_GEOMETRY_FREQUENCY: + case VJ_BEAT_MEMORY: + case VJ_BEAT_INERTIA: + case VJ_BEAT_COLOR_AMOUNT: + case VJ_BEAT_DETAIL: + case VJ_BEAT_GLOW: + case VJ_BEAT_ALPHA_OR_OPACITY: + case VJ_BEAT_TRAIL_LENGTH: + case VJ_BEAT_DENSITY: + case VJ_BEAT_CONTRAST: + case VJ_BEAT_INTENSITY: + case VJ_BEAT_TURBULENCE: + case VJ_BEAT_KICK: + case VJ_BEAT_SNARE: + case VJ_BEAT_HAT: + return 1; + + default: + return 0; + } +} + +static int ab_auto_role_curve_mix_compatible(int role) +{ + switch(role) + { + case VJ_AUDIO_BEAT_AUTO_ROLE_AMOUNT: + case VJ_AUDIO_BEAT_AUTO_ROLE_MOTION: + case VJ_AUDIO_BEAT_AUTO_ROLE_FLOW: + case VJ_AUDIO_BEAT_AUTO_ROLE_MEMORY: + case VJ_AUDIO_BEAT_AUTO_ROLE_COLOR: + case VJ_AUDIO_BEAT_AUTO_ROLE_TEXTURE: + case VJ_AUDIO_BEAT_AUTO_ROLE_CONTRAST: + return 1; + + default: + return 0; + } +} + +static int ab_auto_param_meta_curve_mixable(const ab_auto_param_meta_t *m) +{ + unsigned int reject_flags; + + if(!m || !m->valid) + return 0; + + reject_flags = VJ_BEAT_F_REJECT | + VJ_BEAT_F_STRUCTURAL | + VJ_BEAT_F_REBUILDS_STATE | + VJ_BEAT_F_IMPULSE | + VJ_BEAT_F_DISCRETE; + + if(m->hint_flags & reject_flags) + return 0; + + if(m->has_hint) + return ab_auto_hint_class_curve_mix_compatible(m->hint_class); + + return ab_auto_role_curve_mix_compatible(m->role); +} + +static int ab_auto_entry_param_curve_enabled(sample_eff_chain *entry, int param_nr) +{ + char key[40]; + int status = 0; + + if(!entry || !entry->kf || !entry->kf_status) + return 0; + + if(param_nr < 0 || param_nr >= SAMPLE_MAX_PARAMETERS) + return 0; + + snprintf(key, sizeof(key), "status_p%d", param_nr); + + if(vevo_property_get(entry->kf, key, 0, &status) != 0) + return 0; + + return status ? 1 : 0; +} + +static int ab_auto_entry_param_curve_value(sample_eff_chain *entry, int param_nr, long long n_frame, int *value) +{ + if(!value) + return 0; + + if(!ab_auto_entry_param_curve_enabled(entry, param_nr)) + return 0; + + return get_keyframe_value(entry->kf, n_frame, param_nr, value) ? 1 : 0; +} + +static int ab_auto_target_curve_owned_now(const ab_auto_target_t *t) +{ + if(!t || !t->entry_ptr) + return 0; + + return ab_auto_entry_param_curve_enabled(t->entry_ptr, t->param_nr); +} + static int ab_auto_chain_signature(void *ctx, int chain_len, vj_audio_beat_get_fx_id_func get_fx_id) { int sig = 5381; @@ -5462,6 +9933,7 @@ static int ab_auto_insert_numeric_fallback( int chain_len, vj_audio_beat_get_fx_id_func get_fx_id, vj_audio_beat_get_fx_arg_func get_arg, + vj_audio_beat_get_fx_entry_func get_entry, int *scanned_fx, int *scanned_params ) @@ -5476,6 +9948,7 @@ static int ab_auto_insert_numeric_fallback( ab_auto_target_t best[3]; int best_rank[3] = { -2147483647, -2147483647, -2147483647 }; int fx_id = get_fx_id(ctx, chain_pos); + sample_eff_chain *entry = get_entry ? get_entry(ctx, chain_pos) : NULL; int n_params; memset(best, 0, sizeof(best)); @@ -5541,6 +10014,9 @@ static int ab_auto_insert_numeric_fallback( if(rank <= 0) continue; + if(ab_auto_entry_param_curve_enabled(entry, p)) + continue; + base = get_arg(ctx, chain_pos, p); if(base < m->min_value) @@ -5553,6 +10029,9 @@ static int ab_auto_insert_numeric_fallback( cand.effect_id = fx_id; cand.chain_pos = chain_pos; cand.param_nr = p; + cand.entry_ptr = entry; + cand.curve_owned = ab_auto_entry_param_curve_enabled(entry, p); + cand.curve_mixable = cand.curve_owned ? ab_auto_param_meta_curve_mixable(m) : 0; cand.base_value = base; cand.min_value = m->min_value; cand.max_value = m->max_value; @@ -5594,9 +10073,11 @@ static int ab_auto_insert_numeric_fallback( static void ab_auto_trim_macro_state_on_map_change(void) { +#ifdef VEEJAY_AUDIO_BEAT_AUTO_DEBUG float old_groove = ab_auto_groove_level; float old_phrase = ab_auto_phrase_level; float old_climax = ab_auto_climax_level; +#endif ab_auto_groove_level *= 0.64f; ab_auto_phrase_level *= 0.58f; @@ -5620,7 +10101,7 @@ static void ab_auto_trim_macro_state_on_map_change(void) #endif } -static int ab_auto_rebuild_map(void *ctx, int chain_len, vj_audio_beat_get_fx_id_func get_fx_id, vj_audio_beat_get_fx_arg_func get_arg) +static int ab_auto_rebuild_map(void *ctx, int chain_len, vj_audio_beat_get_fx_id_func get_fx_id, vj_audio_beat_get_fx_arg_func get_arg, vj_audio_beat_get_fx_entry_func get_entry) { ab_auto_target_t chosen[VJ_AUDIO_BEAT_AUTO_MAX_TARGETS]; int count = 0; @@ -5630,7 +10111,6 @@ static int ab_auto_rebuild_map(void *ctx, int chain_len, vj_audio_beat_get_fx_id int seeded_targets = 0; int fallback_fx = 0; int fallback_params = 0; - int fallback_added = 0; if(!ctx || !get_fx_id || !get_arg || chain_len <= 0) return 0; @@ -5664,6 +10144,7 @@ static int ab_auto_rebuild_map(void *ctx, int chain_len, vj_audio_beat_get_fx_id ab_auto_target_t best_seed; int best_seed_rank = -2147483647; int fx_id = get_fx_id(ctx, chain_pos); + sample_eff_chain *entry = get_entry ? get_entry(ctx, chain_pos) : NULL; int n_params; memset(&best_seed, 0, sizeof(best_seed)); @@ -5692,6 +10173,9 @@ static int ab_auto_rebuild_map(void *ctx, int chain_len, vj_audio_beat_get_fx_id if(!ab_auto_role_allowed(mode, m->role)) continue; + if(ab_auto_entry_param_curve_enabled(entry, p)) + continue; + base = get_arg(ctx, chain_pos, p); if(base < m->min_value) @@ -5705,6 +10189,9 @@ static int ab_auto_rebuild_map(void *ctx, int chain_len, vj_audio_beat_get_fx_id cand.effect_id = fx_id; cand.chain_pos = chain_pos; cand.param_nr = p; + cand.entry_ptr = entry; + cand.curve_owned = ab_auto_entry_param_curve_enabled(entry, p); + cand.curve_mixable = cand.curve_owned ? ab_auto_param_meta_curve_mixable(m) : 0; cand.base_value = base; cand.min_value = m->min_value; cand.max_value = m->max_value; @@ -5733,9 +10220,9 @@ static int ab_auto_rebuild_map(void *ctx, int chain_len, vj_audio_beat_get_fx_id if(count < VJ_AUDIO_BEAT_AUTO_MAX_TARGETS) { - fallback_added = ab_auto_insert_numeric_fallback(chosen, &count, VJ_AUDIO_BEAT_AUTO_MAX_TARGETS, - ctx, chain_len, get_fx_id, get_arg, - &fallback_fx, &fallback_params); + (void) ab_auto_insert_numeric_fallback(chosen, &count, VJ_AUDIO_BEAT_AUTO_MAX_TARGETS, + ctx, chain_len, get_fx_id, get_arg, get_entry, + &fallback_fx, &fallback_params); } memset(ab_auto_targets, 0, sizeof(ab_auto_targets)); @@ -5763,83 +10250,6 @@ static float ab_auto_mix3(float a, float b, float c) return v; } -static float ab_auto_trigger_signal(const vj_audio_beat_snapshot_t *snap) -{ - float v; - - if(!snap) - return 0.0f; - - v = snap->beat_gate; - - if(snap->beat_pulse > v) - v = snap->beat_pulse; - - if(v < 0.0f) - return 0.0f; - if(v > 1.0f) - return 1.0f; - - return v; -} - -static float ab_auto_hit_signal(const vj_audio_beat_snapshot_t *snap) -{ - float v; - - if(!snap) - return 0.0f; - - v = ab_auto_trigger_signal(snap); - - if((snap->transient * 0.92f) > v) - v = snap->transient * 0.92f; - if((snap->flux * 0.64f) > v) - v = snap->flux * 0.64f; - - if(v < 0.0f) - return 0.0f; - if(v > 1.0f) - return 1.0f; - - return v; -} - -static float ab_auto_drum_drive(const vj_audio_beat_snapshot_t *snap) -{ - float v; - float band_acc; - - if(!snap) - return 0.0f; - - v = ab_auto_trigger_signal(snap) * 0.42f + - snap->transient * 0.30f + - snap->flux * 0.24f + - snap->kick * 0.34f + - snap->snare * 0.26f + - snap->hat * 0.14f + - snap->envelope * 0.16f + - snap->level * 0.14f; - - band_acc = 0.0f; - - if(snap->bass > 0.72f) - band_acc += 0.10f; - if(snap->mid > 0.72f) - band_acc += 0.08f; - if(snap->high > 0.72f) - band_acc += 0.08f; - - v += band_acc * (0.35f + snap->flux * 0.65f); - - if(v < 0.0f) - return 0.0f; - if(v > 1.0f) - return 1.0f; - - return v; -} static float ab_auto_activity_gate(const vj_audio_beat_snapshot_t *snap) { @@ -6126,8 +10536,8 @@ static void ab_auto_build_frame_signal(const vj_audio_beat_snapshot_t *snap, if(snap->bpm > 1.0f) { - fs->tempo_fast = (snap->bpm - 132.0f) * (1.0f / 88.0f); - fs->tempo_slow = (92.0f - snap->bpm) * (1.0f / 52.0f); + fs->tempo_fast = (snap->bpm - 150.0f) * (1.0f / 110.0f); + fs->tempo_slow = (105.0f - snap->bpm) * (1.0f / 70.0f); if(fs->tempo_fast < 0.0f) fs->tempo_fast = 0.0f; @@ -6140,7 +10550,7 @@ static void ab_auto_build_frame_signal(const vj_audio_beat_snapshot_t *snap, fs->tempo_slow = 1.0f; } - fs->restraint = fs->tempo_fast * (0.38f + fs->density * 0.62f); + fs->restraint = fs->tempo_fast * (0.26f + fs->density * 0.50f); if(fs->restraint > 1.0f) fs->restraint = 1.0f; @@ -6194,9 +10604,9 @@ static float ab_auto_signal_for_role(const vj_audio_beat_snapshot_t *snap, groove * 0.18f + phrase * 0.08f + density * 0.14f); case VJ_AUDIO_BEAT_AUTO_ROLE_SPEED: - return ab_auto_mix3(musical * 0.16f + subdiv * 0.06f + hit * 0.14f, - snap->mid * 0.12f + body * 0.10f, - snap->transient * 0.08f + groove * 0.18f + phrase * 0.08f + density * 0.08f); + return ab_auto_mix3(musical * 0.14f + subdiv * 0.04f + hit * 0.10f, + snap->mid * 0.10f + body * 0.08f, + snap->transient * 0.06f + groove * 0.16f + phrase * 0.08f + density * 0.06f); case VJ_AUDIO_BEAT_AUTO_ROLE_MOTION: return ab_auto_mix3(snap->mid * 0.20f + snap->snare * 0.13f + body * 0.12f, @@ -6415,9 +10825,9 @@ static float ab_auto_signal_for_target(const vj_audio_beat_snapshot_t *snap, case VJ_BEAT_SPEED: case VJ_BEAT_SIGNED_SPEED: - v = ab_auto_mix3(hit * 0.22f + drum * 0.16f + subdiv * 0.06f + musical * 0.14f, - snap->mid * 0.10f + snap->transient * 0.08f + snap->flux * 0.06f, - groove * 0.18f + phrase * 0.10f + tempo_slow * 0.08f); + v = ab_auto_mix3(hit * 0.16f + drum * 0.12f + subdiv * 0.04f + musical * 0.12f, + snap->mid * 0.08f + snap->transient * 0.06f + snap->flux * 0.05f, + groove * 0.18f + phrase * 0.10f + density * 0.05f); break; case VJ_BEAT_SIGNED_CURVE: @@ -7162,6 +11572,46 @@ static int ab_auto_quantize_discrete_value(const ab_auto_target_t *t, int value, return value; } +static int ab_auto_apply_global_amount_value(const ab_auto_target_t *t, int value, int global_amount, int lo, int hi) +{ + long delta; + long scaled; + + if(!t || !t->valid) + return value; + + if(global_amount <= 0) + return t->base_value; + + if(global_amount >= 100 || value == t->base_value) + return value; + + delta = (long)value - (long)t->base_value; + scaled = delta * (long)global_amount; + + if(scaled >= 0) + scaled = (scaled + 50L) / 100L; + else + scaled = (scaled - 50L) / 100L; + + if(scaled == 0) + return t->base_value; + + value = t->base_value + (int)scaled; + + if(value < lo) + value = lo; + else if(value > hi) + value = hi; + + if(value < t->min_value) + value = t->min_value; + else if(value > t->max_value) + value = t->max_value; + + return ab_auto_quantize_discrete_value(t, value, lo, hi); +} + static float ab_auto_smoothstepf(float v) { if(v <= 0.0f) @@ -7816,7 +12266,7 @@ static int ab_auto_compute_value(const ab_auto_target_t *t, float signal, int gl ab_auto_calc_dbg.delta = impulse_delta; ab_auto_calc_dbg.reason = "impulse-continuous"; #endif - return impulse_value; + return ab_auto_apply_global_amount_value(t, impulse_value, global_amount, lo, hi); } #endif @@ -7836,7 +12286,7 @@ static int ab_auto_compute_value(const ab_auto_target_t *t, float signal, int gl ab_auto_calc_dbg.reason = signal >= trigger_threshold ? "trigger-hit" : "trigger-idle"; #endif if(signal >= trigger_threshold) - return t->invert ? lo : hi; + return ab_auto_apply_global_amount_value(t, t->invert ? lo : hi, global_amount, lo, hi); return t->base_value; } @@ -8248,7 +12698,7 @@ static int ab_auto_compute_value(const ab_auto_target_t *t, float signal, int gl ab_auto_calc_dbg.delta = (float)step; ab_auto_calc_dbg.reason = "wrap"; #endif - return value; + return ab_auto_apply_global_amount_value(t, value, global_amount, lo, hi); } #endif @@ -8395,7 +12845,7 @@ static int ab_auto_compute_value(const ab_auto_target_t *t, float signal, int gl value = ab_auto_quantize_discrete_value(t, value, lo, hi); - return value; + return ab_auto_apply_global_amount_value(t, value, global_amount, lo, hi); } static void ab_auto_clear_runtime_state(int mark_dirty) @@ -8444,8 +12894,6 @@ static int ab_auto_release_and_clear( void vj_audio_beat_set_auto_mode(vj_audio_beat_shared_t *s, int mode) { - int requested = mode; - (void)s; if(mode < VJ_AUDIO_BEAT_AUTO_OFF) @@ -8459,8 +12907,6 @@ void vj_audio_beat_set_auto_mode(vj_audio_beat_shared_t *s, int mode) void vj_audio_beat_set_auto_amount(vj_audio_beat_shared_t *s, int amount) { - int requested = amount; - (void)s; if(amount < 0) @@ -8477,13 +12923,14 @@ void vj_audio_beat_auto_reset(vj_audio_beat_shared_t *s) ab_auto_clear_runtime_state(1); } -int vj_audio_beat_auto_apply_chain( +int vj_audio_beat_auto_apply_chain_ex( vj_audio_beat_shared_t *s, void *ctx, int chain_len, vj_audio_beat_get_fx_id_func get_fx_id, vj_audio_beat_get_fx_arg_func get_arg, - vj_audio_beat_set_fx_arg_func set_arg + vj_audio_beat_set_fx_arg_func set_arg, + vj_audio_beat_get_fx_entry_func get_entry ) { vj_audio_beat_snapshot_t snap; @@ -8518,7 +12965,8 @@ int vj_audio_beat_auto_apply_chain( paused = ab_load_i(&s->paused_by_beat); if(action != VJ_AUDIO_BEAT_ACTION_AUTO_FX && - action != VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX) + action != VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX && + action != VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) return ab_auto_release_and_clear(ctx, get_fx_id, get_arg, set_arg); mode = ab_load_i(&ab_auto_mode); @@ -8531,7 +12979,8 @@ int vj_audio_beat_auto_apply_chain( { const int combined_freeze_auto = - action == VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX; + action == VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX || + action == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT; if(paused && !combined_freeze_auto) { @@ -8588,7 +13037,7 @@ int vj_audio_beat_auto_apply_chain( ab_auto_trim_macro_state_on_map_change(); ab_auto_signature = sig; - ab_auto_rebuild_map(ctx, chain_len, get_fx_id, get_arg); + ab_auto_rebuild_map(ctx, chain_len, get_fx_id, get_arg, get_entry); } amount = ab_load_i(&ab_auto_amount); @@ -8649,6 +13098,20 @@ int vj_audio_beat_auto_apply_chain( continue; } + if(ab_auto_target_curve_owned_now(t)) + { + t->last_value = t->base_value; + t->active = 0; + t->mod_value = 0.0f; + t->raw_value = 0.0f; + t->climax_value = 0.0f; + t->mod_initialized = 0; + t->last_slew_ms = 0; + t->last_change_ms = 0; + need_dirty_store = 1; + continue; + } + current = get_arg(ctx, t->chain_pos, t->param_nr); if(!t->impulse && @@ -8774,8 +13237,10 @@ int vj_audio_beat_auto_apply_chain( int diff = ab_auto_absi(value - current); int deadband_ok; int valid_value; +#ifdef VEEJAY_AUDIO_BEAT_AUTO_DEBUG int written = 0; const char *skip_reason = "none"; +#endif if(t->hold_ms > 0 && t->last_change_ms > 0 && @@ -8815,24 +13280,34 @@ int vj_audio_beat_auto_apply_chain( #endif current = value; t->last_change_ms = now; +#ifdef VEEJAY_AUDIO_BEAT_AUTO_DEBUG written = 1; +#endif } else { +#ifdef VEEJAY_AUDIO_BEAT_AUTO_DEBUG skip_reason = "set-arg-failed"; +#endif } } else if(!hold_ok) { +#ifdef VEEJAY_AUDIO_BEAT_AUTO_DEBUG skip_reason = "hold"; +#endif } else if(!deadband_ok) { +#ifdef VEEJAY_AUDIO_BEAT_AUTO_DEBUG skip_reason = "deadband"; +#endif } else if(!valid_value) { +#ifdef VEEJAY_AUDIO_BEAT_AUTO_DEBUG skip_reason = "invalid-value"; +#endif } #ifdef VEEJAY_AUDIO_BEAT_AUTO_DEBUG @@ -8867,6 +13342,152 @@ int vj_audio_beat_auto_apply_chain( return changed; } +int vj_audio_beat_auto_apply_chain( + vj_audio_beat_shared_t *s, + void *ctx, + int chain_len, + vj_audio_beat_get_fx_id_func get_fx_id, + vj_audio_beat_get_fx_arg_func get_arg, + vj_audio_beat_set_fx_arg_func set_arg +) +{ + return vj_audio_beat_auto_apply_chain_ex(s, + ctx, + chain_len, + get_fx_id, + get_arg, + set_arg, + NULL); +} + +int vj_audio_beat_auto_modulate_args( + vj_audio_beat_shared_t *s, + sample_eff_chain *entry, + int effect_id, + int *args, + int n_params, + long long n_frame +) +{ + vj_audio_beat_snapshot_t snap; + ab_auto_frame_signal_t frame_sig; + int action; + int mode; + int amount; + int changed = 0; + long now; + + if(!s || !entry || !args || effect_id <= 0 || n_params <= 0) + return 0; + + if(!ab_load_i(&s->initialized) || !ab_load_i(&s->enabled)) + return 0; + + action = ab_load_i(&s->action_mode); + if(action != VJ_AUDIO_BEAT_ACTION_AUTO_FX && + action != VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX && + action != VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) + return 0; + + mode = ab_load_i(&ab_auto_mode); + if(mode <= VJ_AUDIO_BEAT_AUTO_OFF) + return 0; + + if(ab_auto_target_count <= 0) + return 0; + + if(!vj_audio_beat_get_snapshot(s, &snap)) + return 0; + + amount = ab_load_i(&ab_auto_amount); + now = ab_now_ms(); + + ab_auto_build_frame_signal(&snap, ab_auto_climax_level, &frame_sig); + + if(n_params > SAMPLE_MAX_PARAMETERS) + n_params = SAMPLE_MAX_PARAMETERS; + + for(int i = 0; i < ab_auto_target_count; i++) + { + ab_auto_target_t *t = &ab_auto_targets[i]; + float signal; + int current_base; + int curve_value; + int value; + int old_base; + + if(!t->valid) + continue; + + if(t->entry_ptr != entry) + continue; + + if(t->effect_id != effect_id) + continue; + + if(t->param_nr < 0 || t->param_nr >= n_params) + continue; + + if(ab_auto_entry_param_curve_enabled(entry, t->param_nr)) + { + t->last_value = args[t->param_nr]; + t->active = 0; + t->mod_value = 0.0f; + t->raw_value = 0.0f; + t->climax_value = 0.0f; + t->mod_initialized = 0; + t->last_slew_ms = 0; + t->last_change_ms = 0; + continue; + } + + if(!t->curve_owned || !t->curve_mixable) + continue; + + if(!ab_auto_entry_param_curve_value(entry, t->param_nr, n_frame, &curve_value)) + continue; + + current_base = curve_value; + + if(current_base < t->min_value) + current_base = t->min_value; + else if(current_base > t->max_value) + current_base = t->max_value; + + signal = ab_auto_signal_for_target(&snap, t, ab_auto_climax_level, &frame_sig); + signal = ab_auto_slew_signal(t, signal, now); + + old_base = t->base_value; + t->base_value = current_base; + value = ab_auto_compute_value(t, signal, amount, ab_auto_climax_level); + t->base_value = old_base; + + if(signal <= 0.002f && !ab_auto_role_should_hold(t->role)) + value = current_base; + + if(value < t->min_value) + value = t->min_value; + else if(value > t->max_value) + value = t->max_value; + + if(value != args[t->param_nr] && + vje_is_param_value_valid(effect_id, t->param_nr, value)) + { + args[t->param_nr] = value; + changed++; + } + + t->last_value = args[t->param_nr]; + t->active = signal > 0.002f ? 1 : 0; + t->last_change_ms = now; + } + + if(changed > 0) + ab_auto_active = 1; + + return changed; +} + int vj_audio_beat_get_action(vj_audio_beat_shared_t *s) { if(!s) diff --git a/veejay-current/veejay-server/libveejay/vj-audio-beat.h b/veejay-current/veejay-server/libveejay/vj-audio-beat.h index 2a624c50..37113d60 100644 --- a/veejay-current/veejay-server/libveejay/vj-audio-beat.h +++ b/veejay-current/veejay-server/libveejay/vj-audio-beat.h @@ -24,21 +24,18 @@ #ifdef HAVE_JACK -/* Keep this public header independent from vj-lib.h and vj-audio-sync.h. - * Only pointer types are used here, so forward declarations are enough. - * The complete vj_audio_beat_shared_t and veejay_t definitions live in - * vj-lib.h; vj_audio_sync_shared_t lives in vj-audio-sync.h. - */ typedef struct vj_audio_beat_shared_t vj_audio_beat_shared_t; typedef struct vj_audio_sync_shared_t vj_audio_sync_shared_t; typedef struct veejay_t veejay_t; +typedef struct sample_eff_t sample_eff_chain; typedef enum { VJ_AUDIO_BEAT_ACTION_NONE = 0, VJ_AUDIO_BEAT_ACTION_FREEZE = 1, VJ_AUDIO_BEAT_ACTION_AUTO_FX = 2, - VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX = 3 + VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX = 3, + VJ_AUDIO_BEAT_ACTION_BREAK_BEAT = 4 } vj_audio_beat_action_t; typedef enum @@ -131,6 +128,7 @@ typedef struct typedef int (*vj_audio_beat_get_fx_id_func)(void *ctx, int chain_pos); typedef int (*vj_audio_beat_get_fx_arg_func)(void *ctx, int chain_pos, int param_nr); typedef int (*vj_audio_beat_set_fx_arg_func)(void *ctx, int chain_pos, int param_nr, int value); +typedef sample_eff_chain *(*vj_audio_beat_get_fx_entry_func)(void *ctx, int chain_pos); void vj_audio_beat_init(vj_audio_beat_shared_t *s, int input_channels); void vj_audio_beat_bind_sync(vj_audio_beat_shared_t *s, vj_audio_sync_shared_t *sync); @@ -147,6 +145,9 @@ int vj_audio_beat_is_enabled(vj_audio_beat_shared_t *s); int vj_audio_beat_is_running(vj_audio_beat_shared_t *s); int vj_audio_beat_is_open(vj_audio_beat_shared_t *s); int vj_audio_beat_is_paused_by_beat(vj_audio_beat_shared_t *s); +int vj_audio_beat_transport_is_internal(vj_audio_beat_shared_t *s); +void vj_audio_beat_user_transport_override(veejay_t *v, vj_audio_beat_shared_t *s, + int requested_speed); void vj_audio_beat_set_freeze_ms(vj_audio_beat_shared_t *s, int ms); void vj_audio_beat_set_cooldown_ms(vj_audio_beat_shared_t *s, int ms); @@ -170,6 +171,23 @@ int vj_audio_beat_auto_apply_chain( vj_audio_beat_get_fx_arg_func get_arg, vj_audio_beat_set_fx_arg_func set_arg ); +int vj_audio_beat_auto_apply_chain_ex( + vj_audio_beat_shared_t *s, + void *ctx, + int chain_len, + vj_audio_beat_get_fx_id_func get_fx_id, + vj_audio_beat_get_fx_arg_func get_arg, + vj_audio_beat_set_fx_arg_func set_arg, + vj_audio_beat_get_fx_entry_func get_entry +); +int vj_audio_beat_auto_modulate_args( + vj_audio_beat_shared_t *s, + sample_eff_chain *entry, + int effect_id, + int *args, + int n_params, + long long n_frame +); int vj_audio_beat_copy_record_audio( vj_audio_beat_shared_t *s, @@ -190,6 +208,12 @@ int vj_audio_beat_get_snapshot(vj_audio_beat_shared_t *s, vj_audio_beat_snapshot float vj_audio_beat_get_signal(vj_audio_beat_shared_t *s, int signal); int vj_audio_beat_map_signal(vj_audio_beat_shared_t *s, int signal, int min_value, int max_value, int invert); +int vj_audio_beat_disable_for_transport(veejay_t *v, vj_audio_beat_shared_t *s); +int vj_audio_beat_release_transport(veejay_t *v, vj_audio_beat_shared_t *s); +void vj_audio_beat_set_action_for_transport(veejay_t *v, + vj_audio_beat_shared_t *s, + int action); + #endif #endif diff --git a/veejay-current/veejay-server/libveejay/vj-audio-sync.c b/veejay-current/veejay-server/libveejay/vj-audio-sync.c index 6327df4b..1743c8bf 100644 --- a/veejay-current/veejay-server/libveejay/vj-audio-sync.c +++ b/veejay-current/veejay-server/libveejay/vj-audio-sync.c @@ -17,17 +17,17 @@ * */ #include + #ifdef HAVE_JACK -#include -#include -#include #include #include #include #include +#include #include #include +#include #include #include @@ -58,10 +58,7 @@ #endif #ifndef VJ_AUDIO_SYNC_MAX_BPM -#define VJ_AUDIO_SYNC_MAX_BPM 300.0 -#elif VJ_AUDIO_SYNC_MAX_BPM < 300 -#undef VJ_AUDIO_SYNC_MAX_BPM -#define VJ_AUDIO_SYNC_MAX_BPM 300.0 +#define VJ_AUDIO_SYNC_MAX_BPM 240.0 #endif #ifndef VJ_AUDIO_SYNC_MIN_PERIOD_MS @@ -75,12 +72,8 @@ #ifndef VJ_AUDIO_SYNC_BRIDGE_LATENCY_MS #define VJ_AUDIO_SYNC_BRIDGE_LATENCY_MS 80 #endif - -#ifndef VJ_AUDIO_SYNC_BRIDGE_STRETCH_SEGMENT_FRAMES -#define VJ_AUDIO_SYNC_BRIDGE_STRETCH_SEGMENT_FRAMES 1024 -#endif -#ifndef VJ_AUDIO_SYNC_BRIDGE_STRETCH_OVERLAP_FRAMES -#define VJ_AUDIO_SYNC_BRIDGE_STRETCH_OVERLAP_FRAMES 128 +#ifndef VJ_AUDIO_SYNC_BRIDGE_MAX_LATENCY_MS +#define VJ_AUDIO_SYNC_BRIDGE_MAX_LATENCY_MS 260 #endif #ifndef VJ_AUDIO_SYNC_ALIGN_HOP_MS @@ -95,7 +88,7 @@ #endif #ifndef VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS -#define VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS 30000 +#define VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS 8000 #endif #ifndef VJ_AUDIO_SYNC_ALIGN_LOCK_CONF @@ -116,30 +109,7 @@ #endif #ifndef VJ_AUDIO_SYNC_ALIGN_HOLD_BLOCKS -#define VJ_AUDIO_SYNC_ALIGN_HOLD_BLOCKS 96 -#endif - - -#ifndef VJ_AUDIO_SYNC_ALIGN_LOCK_HOLD_CONF -#define VJ_AUDIO_SYNC_ALIGN_LOCK_HOLD_CONF 42 -#endif -#ifndef VJ_AUDIO_SYNC_ALIGN_LOCK_HOLD_SCORE -#define VJ_AUDIO_SYNC_ALIGN_LOCK_HOLD_SCORE 0.68 -#endif -#ifndef VJ_AUDIO_SYNC_ALIGN_LOCK_HOLD_SEPARATION -#define VJ_AUDIO_SYNC_ALIGN_LOCK_HOLD_SEPARATION 0.020 -#endif -#ifndef VJ_AUDIO_SYNC_ALIGN_LOCK_HYSTERESIS_MS -#define VJ_AUDIO_SYNC_ALIGN_LOCK_HYSTERESIS_MS 900 -#endif -#ifndef VJ_AUDIO_SYNC_ALIGN_LOCK_LOST_BLOCKS -#define VJ_AUDIO_SYNC_ALIGN_LOCK_LOST_BLOCKS 24 -#endif -#ifndef VJ_AUDIO_SYNC_ALIGN_LOCK_OFFSET_BLEND -#define VJ_AUDIO_SYNC_ALIGN_LOCK_OFFSET_BLEND 0.10 -#endif -#ifndef VJ_AUDIO_SYNC_ALIGN_HOLD_OFFSET_BLEND -#define VJ_AUDIO_SYNC_ALIGN_HOLD_OFFSET_BLEND 0.025 +#define VJ_AUDIO_SYNC_ALIGN_HOLD_BLOCKS 24 #endif #ifndef VJ_AUDIO_SYNC_ALIGN_PROBE_MAX_FEATURES @@ -150,7 +120,7 @@ #endif #ifndef VJ_AUDIO_SYNC_TARGET_RING_SECONDS -#define VJ_AUDIO_SYNC_TARGET_RING_SECONDS 30 +#define VJ_AUDIO_SYNC_TARGET_RING_SECONDS 4 #endif #ifndef VJ_AUDIO_SYNC_TARGET_MIN_RING_BYTES #define VJ_AUDIO_SYNC_TARGET_MIN_RING_BYTES 65536 @@ -163,11 +133,11 @@ #define VJ_AUDIO_SYNC_TARGET_MIN_HISTORY_MS 250 #endif #ifndef VJ_AUDIO_SYNC_TARGET_WANTED_HISTORY_MS -#define VJ_AUDIO_SYNC_TARGET_WANTED_HISTORY_MS 30000 +#define VJ_AUDIO_SYNC_TARGET_WANTED_HISTORY_MS 750 #endif #ifndef VJ_AUDIO_SYNC_LIVE_SNAP_MIN_OFFSET_MS -#define VJ_AUDIO_SYNC_LIVE_SNAP_MIN_OFFSET_MS 650 +#define VJ_AUDIO_SYNC_LIVE_SNAP_MIN_OFFSET_MS 900 #endif #ifndef VJ_AUDIO_SYNC_LIVE_SNAP_MIN_CONF #define VJ_AUDIO_SYNC_LIVE_SNAP_MIN_CONF 88 @@ -191,12 +161,10 @@ #define VJ_AUDIO_SYNC_LIVE_SNAP_TICK_MS 500L #endif - #ifndef VJ_AUDIO_SYNC_LIVE_SNAP_MIN_OBSERVE_MS #define VJ_AUDIO_SYNC_LIVE_SNAP_MIN_OBSERVE_MS 180L #endif - #ifndef VJ_AUDIO_SYNC_LIVE_SNAP_SETTLE_MAX_FRAMES #define VJ_AUDIO_SYNC_LIVE_SNAP_SETTLE_MAX_FRAMES 36 #endif @@ -204,7 +172,6 @@ #define VJ_AUDIO_SYNC_LIVE_SNAP_REACQUIRE_CONF 92 #endif - #ifndef VJ_AUDIO_SYNC_ALIGN_MIN_SCORE #define VJ_AUDIO_SYNC_ALIGN_MIN_SCORE 0.001 #endif @@ -215,34 +182,6 @@ #define VJ_AUDIO_SYNC_ALIGN_MIN_VARIANCE 1.0e-7 #endif - -#ifndef VJ_AUDIO_SYNC_ALIGN_LIVE_WINDOW_FEATURES -#define VJ_AUDIO_SYNC_ALIGN_LIVE_WINDOW_FEATURES 128 -#endif -#ifndef VJ_AUDIO_SYNC_ALIGN_ACQUIRE_WINDOW_FEATURES -#define VJ_AUDIO_SYNC_ALIGN_ACQUIRE_WINDOW_FEATURES 512 -#endif -#ifndef VJ_AUDIO_SYNC_ALIGN_ACQUIRE_MIN_FEATURES -#define VJ_AUDIO_SYNC_ALIGN_ACQUIRE_MIN_FEATURES 192 -#endif -#ifndef VJ_AUDIO_SYNC_ALIGN_ACQUIRE_SHORT_WINDOW_FEATURES -#define VJ_AUDIO_SYNC_ALIGN_ACQUIRE_SHORT_WINDOW_FEATURES 128 -#endif -#ifndef VJ_AUDIO_SYNC_ALIGN_ACQUIRE_MID_WINDOW_FEATURES -#define VJ_AUDIO_SYNC_ALIGN_ACQUIRE_MID_WINDOW_FEATURES 256 -#endif - - -#ifndef VJ_AUDIO_SYNC_ALIGN_SETTLED_MIN_FEATURES -#define VJ_AUDIO_SYNC_ALIGN_SETTLED_MIN_FEATURES 192 -#endif - - -#ifndef VJ_AUDIO_SYNC_ALIGN_SETTLED_MAX_LAG_MS -#define VJ_AUDIO_SYNC_ALIGN_SETTLED_MAX_LAG_MS 1500 -#endif - - #ifndef VJ_AUDIO_SYNC_ROUGH_HINT_MIN_CONF #define VJ_AUDIO_SYNC_ROUGH_HINT_MIN_CONF 18 #endif @@ -265,10 +204,6 @@ #define VJ_AUDIO_SYNC_QUIET_LIVE_LOG_MS 1500L #endif -#ifndef VJ_AUDIO_SYNC_PUBLISH_DIAG_LOG_MS -#define VJ_AUDIO_SYNC_PUBLISH_DIAG_LOG_MS 1000L -#endif - #ifndef VJ_AUDIO_SYNC_LIVE_LOCK_LOG_DELTA_MS #define VJ_AUDIO_SYNC_LIVE_LOCK_LOG_DELTA_MS 80 #endif @@ -279,14 +214,6 @@ #define VJ_AUDIO_SYNC_SETTLED_BACKWARD_SNAP_BLOCK_MS 1000 #endif -#ifndef VJ_AUDIO_SYNC_TRACK_ALIGN_PUBLISH_MIN_MS -#define VJ_AUDIO_SYNC_TRACK_ALIGN_PUBLISH_MIN_MS 40L -#endif - -#define SYNC_NSEC_PER_SEC 1000000000L -#define SYNC_USEC_PER_SEC 1000000L -#define SYNC_NSEC_PER_USEC 1000L - static inline int sync_load_i(const volatile int *p) { return atomic_load_int(p); } static inline long sync_load_l(const volatile long *p) { return atomic_load_long(p); } static inline void sync_store_i(volatile int *p, int v) { atomic_store_int(p, v); } @@ -300,135 +227,26 @@ static long sync_now_ms(void) return ((long)ts.tv_sec * 1000L) + ((long)ts.tv_nsec / 1000000L); } -static volatile long sync_track_align_last_publish_gate_ms = 0; - -static inline int sync_track_align_publish_gate_ready(void) -{ - long now_ms = sync_now_ms(); - long last_ms = sync_load_l(&sync_track_align_last_publish_gate_ms); - - if(last_ms <= 0 || (now_ms - last_ms) >= VJ_AUDIO_SYNC_TRACK_ALIGN_PUBLISH_MIN_MS) { - sync_store_l(&sync_track_align_last_publish_gate_ms, now_ms); - return 1; - } - - return 0; -} - -static inline void sync_cpu_relax(void) -{ -#if defined(__i386__) || defined(__x86_64__) - __builtin_ia32_pause(); -#elif defined(__arm__) || defined(__aarch64__) - __asm__ volatile("yield"); -#elif defined(__GNUC__) || defined(__clang__) - __asm__ volatile("" ::: "memory"); -#else - sched_yield(); -#endif -} - -static inline void sync_add_us_to_timespec(struct timespec *ts, long usec) -{ - long sec = usec / SYNC_USEC_PER_SEC; - long rem = usec - sec * SYNC_USEC_PER_SEC; - - ts->tv_sec += sec; - ts->tv_nsec += rem * SYNC_NSEC_PER_USEC; - - if(ts->tv_nsec >= SYNC_NSEC_PER_SEC) - { - ts->tv_nsec -= SYNC_NSEC_PER_SEC; - ts->tv_sec++; - } -} static void sync_sleep_us(long usec) { + struct timespec ts; if(usec <= 0) return; - -#if defined(CLOCK_MONOTONIC) && defined(TIMER_ABSTIME) - struct timespec deadline; - - if(clock_gettime(CLOCK_MONOTONIC, &deadline) == 0) - { - int saved_errno = errno; - - sync_add_us_to_timespec(&deadline, usec); - - while(clock_nanosleep(CLOCK_MONOTONIC, - TIMER_ABSTIME, - &deadline, - NULL) == EINTR) - { - } - - errno = saved_errno; - return; - } -#endif - - long sec = usec / SYNC_USEC_PER_SEC; - long rem = usec - sec * SYNC_USEC_PER_SEC; - - struct timespec ts; - ts.tv_sec = sec; - ts.tv_nsec = rem * SYNC_NSEC_PER_USEC; - - int saved_errno = errno; - - while(nanosleep(&ts, &ts) == -1 && errno == EINTR) - { - } - - errno = saved_errno; -} - -static inline int sync_try_lock(vj_audio_sync_shared_t *s) -{ -#if defined(__GNUC__) || defined(__clang__) - if(__atomic_load_n(&s->ring_lock, __ATOMIC_RELAXED) != 0) - return 0; - - return __atomic_exchange_n(&s->ring_lock, 1, __ATOMIC_ACQUIRE) == 0; -#else - return __sync_lock_test_and_set(&s->ring_lock, 1) == 0; -#endif + ts.tv_sec = usec / 1000000L; + ts.tv_nsec = (usec % 1000000L) * 1000L; + while(nanosleep(&ts, &ts) == -1 && errno == EINTR) { } } static inline void sync_lock(vj_audio_sync_shared_t *s) { - int rounds = 0; - - while(!sync_try_lock(s)) - { - if(rounds < 256) - { - for(int i = 0; i < 32; i++) - sync_cpu_relax(); - - rounds++; - } - else if(rounds < 512) - { - sched_yield(); - rounds++; - } - else - { - sync_sleep_us(50); - } - } + while(__sync_lock_test_and_set(&s->ring_lock, 1)) + sync_sleep_us(50); } -static inline void sync_unlock(vj_audio_sync_shared_t *s) +static inline int sync_try_lock(vj_audio_sync_shared_t *s) { -#if defined(__GNUC__) || defined(__clang__) - __atomic_store_n(&s->ring_lock, 0, __ATOMIC_RELEASE); -#else - __sync_lock_release(&s->ring_lock); -#endif + return (__sync_lock_test_and_set(&s->ring_lock, 1) == 0); } static inline int sync_try_lock_for_us(vj_audio_sync_shared_t *s, long max_wait_us) @@ -455,6 +273,10 @@ static inline int sync_try_lock_for_us(vj_audio_sync_shared_t *s, long max_wait_ return 0; } +static inline void sync_unlock(vj_audio_sync_shared_t *s) +{ + __sync_lock_release(&s->ring_lock); +} static inline double sync_clampd(double v, double lo, double hi) { @@ -476,7 +298,8 @@ static inline float sync_from_q15(int v) static inline int sync_q8_bpm(double bpm) { - bpm = bpm < 0.0 ? 0.0 : (bpm > 9999.0 ? 9999.0 : bpm); + if(bpm < 0.0) bpm = 0.0; + if(bpm > 9999.0) bpm = 9999.0; return (int)(bpm * 256.0 + 0.5); } @@ -516,15 +339,18 @@ static inline int sync_clampi(int v, int lo, int hi) static inline int sync_align_hop_frames(int sample_rate) { int hop; - sample_rate = sample_rate <= 0 ? 44100 : sample_rate; + if(sample_rate <= 0) + sample_rate = 44100; hop = (sample_rate * VJ_AUDIO_SYNC_ALIGN_HOP_MS) / 1000; - hop = hop < 64 ? 64 : hop; + if(hop < 64) + hop = 64; return hop; } static void sync_track_align_clear_live_candidate_locked(vj_audio_sync_shared_t *s); static void sync_track_align_clear_rough_hint_locked(vj_audio_sync_shared_t *s); static void sync_clear_target_queue_locked(vj_audio_sync_shared_t *s); +static void sync_wav_side_clear(vj_audio_sync_shared_t *s); static void sync_track_align_clear_locked(vj_audio_sync_shared_t *s) { @@ -564,9 +390,9 @@ static void sync_track_align_clear_locked(vj_audio_sync_shared_t *s) sync_store_i(&s->track_align_correction_ppm, 0); sync_store_i(&s->track_align_state, VJ_AUDIO_SYNC_TRACK_STATE_IDLE); sync_store_l(&s->track_align_last_update_ms, 0); - s->track_align_snap_pending = 0; - s->track_align_snap_delta_frames = 0; - s->track_align_snap_confidence_pct = 0; + sync_store_i(&s->track_align_snap_pending, 0); + sync_store_i(&s->track_align_snap_delta_frames, 0); + sync_store_i(&s->track_align_snap_confidence_pct, 0); } static void sync_track_align_clear_target_locked(vj_audio_sync_shared_t *s) @@ -597,9 +423,9 @@ static void sync_track_align_clear_target_locked(vj_audio_sync_shared_t *s) VJ_AUDIO_SYNC_TRACK_STATE_WAIT_TARGET : VJ_AUDIO_SYNC_TRACK_STATE_WAIT_SOURCE); sync_store_l(&s->track_align_last_update_ms, sync_now_ms()); - s->track_align_snap_pending = 0; - s->track_align_snap_delta_frames = 0; - s->track_align_snap_confidence_pct = 0; + sync_store_i(&s->track_align_snap_pending, 0); + sync_store_i(&s->track_align_snap_delta_frames, 0); + sync_store_i(&s->track_align_snap_confidence_pct, 0); } static inline float sync_align_ring_get(const float *ring, int pos, int count, int newest_back) @@ -647,9 +473,6 @@ static void sync_track_align_append_feature_locked(vj_audio_sync_shared_t *s, if(rise < 0.0) rise = 0.0; - - - feat = (level * 3.0) + (rise * 10.0); feat = sync_clampd(feat, 0.0, 1.0); @@ -660,263 +483,179 @@ static void sync_track_align_append_feature_locked(vj_audio_sync_shared_t *s, *prev = (0.85 * (*prev)) + (0.15 * level); } + +static const char *sync_track_align_state_name_dbg(int state) +{ + switch(state) { + case VJ_AUDIO_SYNC_TRACK_STATE_IDLE: return "idle"; + case VJ_AUDIO_SYNC_TRACK_STATE_WAIT_SOURCE: return "wait-source"; + case VJ_AUDIO_SYNC_TRACK_STATE_WAIT_TARGET: return "wait-target"; + case VJ_AUDIO_SYNC_TRACK_STATE_SEARCHING: return "searching"; + case VJ_AUDIO_SYNC_TRACK_STATE_LOCKED: return "locked"; + case VJ_AUDIO_SYNC_TRACK_STATE_HOLD: return "hold"; + case VJ_AUDIO_SYNC_TRACK_STATE_FALLBACK: return "fallback"; + default: return "unknown"; + } +} + + typedef struct { - int window; - int min_overlap; - int max_lag_steps; - int best_lag; - int best_overlap; - int valid_lags; - int edge_lag; - int no_valid_score; - double best_score; - double second_score; - double peak_sep; - double conf; - const char *quality_reason; -} sync_track_align_live_score_t; + int n; + double mean; + double min; + double max; + double variance; + double sd; + double energy; + int near_zero; +} sync_track_align_feature_stats_t; -static void sync_track_align_score_clear(sync_track_align_live_score_t *r) +static void sync_track_align_stats_clear(sync_track_align_feature_stats_t *st) { - if(!r) + if(!st) return; - r->window = 0; - r->min_overlap = 0; - r->max_lag_steps = 0; - r->best_lag = 0; - r->best_overlap = 0; - r->valid_lags = 0; - r->edge_lag = 0; - r->no_valid_score = 1; - r->best_score = 0.0; - r->second_score = 0.0; - r->peak_sep = 0.0; - r->conf = 0.0; - r->quality_reason = "no-valid-score"; + st->n = 0; + st->mean = 0.0; + st->min = 0.0; + st->max = 0.0; + st->variance = 0.0; + st->sd = 0.0; + st->energy = 0.0; + st->near_zero = 0; } -static void sync_track_align_score_window(const float *source_feat, - int source_pos, - int source_count, - const float *target_feat, - int target_pos, - int target_count, - int window, - int max_lag_ms, - sync_track_align_live_score_t *r) +static void sync_track_align_feature_stats_array(const float *v, + int n, + sync_track_align_feature_stats_t *st) { - int total_lags = 0; - int overlap_rejects = 0; - int lowvar_rejects = 0; - int lowscore_rejects = 0; - int lowvar_source_rejects = 0; - int lowvar_target_rejects = 0; - int max_lag_steps; - int best_lag = 0; - int best_overlap = 0; - int valid_lags = 0; - int edge_lag = 0; - int no_valid_score = 0; - int min_overlap; - double best_score = -1.0; - double second_score = -1.0; - double peak_sep; - const char *quality_reason = "ok"; + double sum = 0.0; + double sum2 = 0.0; - sync_track_align_score_clear(r); + sync_track_align_stats_clear(st); - if(!r || !source_feat || !target_feat || source_count <= 0 || target_count <= 0) + if(!v || !st || n <= 0) return; - if(window > source_count) - window = source_count; - if(window > target_count) - window = target_count; - if(window < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) + st->n = n; + st->min = (double)v[0]; + st->max = (double)v[0]; + + for(int i = 0; i < n; i++) { + double x = (double)v[i]; + sum += x; + sum2 += x * x; + if(x < st->min) + st->min = x; + if(x > st->max) + st->max = x; + if(x > -0.000001 && x < 0.000001) + st->near_zero++; + } + + st->mean = sum / (double)n; + st->energy = sum2 / (double)n; + st->variance = st->energy - (st->mean * st->mean); + if(st->variance < 0.0) + st->variance = 0.0; + st->sd = sqrt(st->variance); +} + +static void sync_track_align_feature_stats_ring(const float *ring, + int pos, + int count, + int window, + sync_track_align_feature_stats_t *st) +{ + float tmp[256]; + + if(window > (int)(sizeof(tmp) / sizeof(tmp[0]))) + window = (int)(sizeof(tmp) / sizeof(tmp[0])); + if(window > count) + window = count; + if(window < 0) + window = 0; + + for(int i = 0; i < window; i++) + tmp[i] = sync_align_ring_get(ring, pos, count, i); + + sync_track_align_feature_stats_array(tmp, window, st); +} + +static void sync_track_align_pcm_stats(const uint8_t *data, + int frames, + int frame_bytes, + int channels, + int bits, + double *rms, + double *peak, + int *nonzero) +{ + int sample_bytes; + double sum2 = 0.0; + double pk = 0.0; + int nz = 0; + + if(rms) + *rms = 0.0; + if(peak) + *peak = 0.0; + if(nonzero) + *nonzero = 0; + + if(!data || frames <= 0 || frame_bytes <= 0 || channels <= 0 || + (bits != 8 && bits != 16)) return; - min_overlap = (window * VJ_AUDIO_SYNC_ALIGN_MIN_OVERLAP_PCT + 99) / 100; - if(min_overlap < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) - min_overlap = VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES; - if(min_overlap > window) - min_overlap = window; - - max_lag_steps = max_lag_ms / VJ_AUDIO_SYNC_ALIGN_HOP_MS; - if(max_lag_steps > source_count - min_overlap) - max_lag_steps = source_count - min_overlap; - if(max_lag_steps > target_count - min_overlap) - max_lag_steps = target_count - min_overlap; - if(max_lag_steps < 2) - max_lag_steps = 2; - - for(int lag = -max_lag_steps; lag <= max_lag_steps; lag++) { - double sum_x = 0.0, sum_y = 0.0; - double sum_xx = 0.0, sum_yy = 0.0, sum_xy = 0.0; - int n = 0; - - total_lags++; - - for(int i = 0; i < window; i++) { - int src_back = i; - int tgt_back = i - lag; - if(tgt_back < 0 || tgt_back >= target_count) - continue; - if(src_back < 0 || src_back >= source_count) - continue; - - double x = sync_align_ring_get(source_feat, - source_pos, - source_count, - src_back); - double y = sync_align_ring_get(target_feat, - target_pos, - target_count, - tgt_back); - sum_x += x; - sum_y += y; - sum_xx += x * x; - sum_yy += y * y; - sum_xy += x * y; - n++; - } - - if(n < min_overlap) { - overlap_rejects++; - continue; - } - - { - double nf = (double)n; - double cov = sum_xy - (sum_x * sum_y / nf); - double vx = sum_xx - (sum_x * sum_x / nf); - double vy = sum_yy - (sum_y * sum_y / nf); - double score = 0.0; - - if(vx <= VJ_AUDIO_SYNC_ALIGN_MIN_VARIANCE || - vy <= VJ_AUDIO_SYNC_ALIGN_MIN_VARIANCE) - { - if(vx <= VJ_AUDIO_SYNC_ALIGN_MIN_VARIANCE) - lowvar_source_rejects++; - if(vy <= VJ_AUDIO_SYNC_ALIGN_MIN_VARIANCE) - lowvar_target_rejects++; - lowvar_rejects++; - continue; - } - - score = cov / sqrt(vx * vy); - if(score < 0.0) - score = 0.0; - else if(score > 1.0) - score = 1.0; - - if(score < VJ_AUDIO_SYNC_ALIGN_MIN_SCORE) { - lowscore_rejects++; - continue; - } - - valid_lags++; - - if(score > best_score) { - second_score = best_score; - best_score = score; - best_lag = lag; - best_overlap = n; - } else if(score > second_score) { - second_score = score; - } - } - } - - if(valid_lags <= 0 || best_score < VJ_AUDIO_SYNC_ALIGN_MIN_SCORE) { - no_valid_score = 1; - best_lag = 0; - best_score = 0.0; - second_score = 0.0; - best_overlap = 0; - quality_reason = (lowvar_rejects > 0 && lowvar_rejects >= overlap_rejects) ? - "low-variance" : - (overlap_rejects > 0 ? "low-overlap" : - (lowscore_rejects > 0 || total_lags > 0 ? "weak-score" : "no-valid-score")); - } else if(second_score < 0.0) { - second_score = 0.0; - } - - peak_sep = best_score - second_score; - if(peak_sep < 0.0) - peak_sep = 0.0; - - edge_lag = (!no_valid_score && - abs(best_lag) >= (max_lag_steps - VJ_AUDIO_SYNC_ALIGN_EDGE_GUARD_STEPS)); - - r->window = window; - r->min_overlap = min_overlap; - r->max_lag_steps = max_lag_steps; - r->best_lag = best_lag; - r->best_overlap = best_overlap; - r->valid_lags = valid_lags; - r->edge_lag = edge_lag; - r->no_valid_score = no_valid_score; - r->best_score = best_score; - r->second_score = second_score; - r->peak_sep = peak_sep; - r->quality_reason = quality_reason; - - if(no_valid_score) { - r->conf = 0.0; + sample_bytes = bits / 8; + if(sample_bytes <= 0 || frame_bytes < sample_bytes * channels) return; + + for(int i = 0; i < frames; i++) { + const uint8_t *f = data + ((size_t)i * (size_t)frame_bytes); + double mono = 0.0; + + for(int c = 0; c < channels; c++) { + int v = sync_read_sample(f + (c * sample_bytes), sample_bytes); + mono += (double)v * (1.0 / 32768.0); + } + + mono /= (double)channels; + if(mono < 0.0) + mono = -mono; + if(mono > pk) + pk = mono; + if(mono > 0.000030) + nz++; + sum2 += mono * mono; } - r->conf = best_score * 100.0; - r->conf *= sync_clampd(peak_sep * 4.0 + 0.45, 0.0, 1.0); + if(rms) + *rms = sqrt(sum2 / (double)frames); + if(peak) + *peak = pk; + if(nonzero) + *nonzero = nz; +} - if(edge_lag && !(best_score >= 0.90 && peak_sep >= 0.12)) { - r->conf *= 0.25; - r->quality_reason = "edge"; - } +static int sync_track_align_probe_debug_should_log(const char *reason, int confidence_pct) +{ + static long last_log_ms = 0; + long now_ms = sync_now_ms(); - if(best_score < VJ_AUDIO_SYNC_ALIGN_MIN_LOCK_SCORE || - peak_sep < VJ_AUDIO_SYNC_ALIGN_MIN_LOCK_SEPARATION) + if(confidence_pct >= 70) + return 1; + + if(reason && strcmp(reason, "ok") != 0 && + (last_log_ms == 0 || (now_ms - last_log_ms) >= 2500L)) { - r->conf *= 0.55; - if(!edge_lag) - r->quality_reason = (best_score < VJ_AUDIO_SYNC_ALIGN_MIN_LOCK_SCORE) ? - "weak-score" : "weak-separation"; + last_log_ms = now_ms; + return 1; } - (void)lowvar_source_rejects; - (void)lowvar_target_rejects; + return 0; } -static int sync_track_align_score_is_better(const sync_track_align_live_score_t *a, - const sync_track_align_live_score_t *b) -{ - if(!a || a->window <= 0) - return 0; - if(!b || b->window <= 0) - return 1; - - if(a->no_valid_score && !b->no_valid_score) - return 0; - if(!a->no_valid_score && b->no_valid_score) - return 1; - - if(a->no_valid_score && b->no_valid_score) - return a->window > b->window; - - - - - if(a->conf > b->conf + 1.0) - return 1; - if(a->conf + 1.0 < b->conf) - return 0; - if(a->peak_sep > b->peak_sep + 0.005) - return 1; - if(a->peak_sep + 0.005 < b->peak_sep) - return 0; - return a->window > b->window; -} static void sync_track_align_clear_live_candidate_locked(vj_audio_sync_shared_t *s) { @@ -974,29 +713,47 @@ static int sync_track_align_offer_snap_ex_locked(vj_audio_sync_shared_t *s, if(cooldown_ms <= 0) cooldown_ms = VJ_AUDIO_SYNC_TRACK_SNAP_OFFER_COOLDOWN_MS; - if(tiny_servo && delta_frames != -1 && delta_frames != 1) return 0; + if(tiny_servo && delta_frames != -1 && delta_frames != 1) { + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][SNAP-OFFER] ignored invalid-servo delta=%+d conf=%d%% reason=%s", + delta_frames, + conf_clamped, + reason ? reason : "servo"); + return 0; + } - pending = s->track_align_snap_pending; - if(pending) return 0; + pending = sync_load_i(&s->track_align_snap_pending); + if(pending) { + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][SNAP-OFFER] suppressed pending delta=%+d conf=%d%% pending=1 reason=%s", + delta_frames, + conf_clamped, + reason ? reason : (tiny_servo ? "servo" : "worker")); + return 0; + } if(!tiny_servo && s->align_last_snap_ms != 0 && (now_ms - s->align_last_snap_ms) < cooldown_ms) { + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][SNAP-OFFER] suppressed cooldown delta=%+d conf=%d%% since_last=%ldms cooldown=%ldms pending=0 reason=%s", + delta_frames, + conf_clamped, + since, + cooldown_ms, + reason ? reason : "worker"); return 0; } - seq = s->track_align_snap_seq + 1; - s->track_align_snap_delta_frames = delta_frames; - s->track_align_snap_confidence_pct = conf_clamped; - s->track_align_snap_pending = 1; - s->track_align_snap_seq = seq; + seq = sync_load_i(&s->track_align_snap_seq) + 1; + sync_store_i(&s->track_align_snap_delta_frames, delta_frames); + sync_store_i(&s->track_align_snap_confidence_pct, conf_clamped); + sync_store_i(&s->track_align_snap_pending, 1); + sync_store_i(&s->track_align_snap_seq, seq); if(!tiny_servo) { s->align_last_snap_ms = now_ms; - - - s->align_live_last_snap_ms = now_ms; s->align_live_last_snap_delta = delta_frames; } @@ -1006,7 +763,21 @@ static int sync_track_align_offer_snap_ex_locked(vj_audio_sync_shared_t *s, else snprintf(since_buf, sizeof(since_buf), "%ldms", since); - + if(tiny_servo) { + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][SNAP-OFFER] accepted servo delta=%+d conf=%d%% seq=%d since_hard=%s reason=%s", + delta_frames, + conf_clamped, + seq, + since_buf, + reason ? reason : "servo"); + } + else { + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][SNAP-OFFER] accepted delta=%+d conf=%d%% seq=%d since_last=%s cooldown=%ldms reason=%s", + delta_frames, conf_clamped, seq, since_buf, cooldown_ms, + reason ? reason : "worker"); + } return 1; } @@ -1048,18 +819,15 @@ static void sync_track_align_live_requirements_locked(vj_audio_sync_shared_t *s, (void)confidence_pct; - - - - if(first_acquire && abs_ms >= VJ_AUDIO_SYNC_LIVE_SNAP_MIN_OFFSET_MS) { - int urgency = abs_ms - VJ_AUDIO_SYNC_LIVE_SNAP_MIN_OFFSET_MS; + if(first_acquire && abs_ms >= 1800) { + int urgency = abs_ms - 1800; if(urgency < 0) urgency = 0; - if(urgency > 4100) - urgency = 4100; + if(urgency > 3200) + urgency = 3200; - - req_conf = 72 - ((urgency * 14) / 4100); + /* 1800ms -> about 76%, 5000ms+ -> about 58%. */ + req_conf = 76 - ((urgency * 18) / 3200); if(req_conf < 58) req_conf = 58; if(req_conf > VJ_AUDIO_SYNC_LIVE_SNAP_MIN_CONF) @@ -1119,10 +887,7 @@ static void sync_track_align_try_live_snap_locked(vj_audio_sync_shared_t *s, if(sync_load_i(&s->target_mode) != VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) return; - - - - if(s->track_align_snap_pending) { + if(sync_load_i(&s->track_align_snap_pending)) { if(s->align_live_candidate_count > 0) sync_track_align_clear_live_candidate_locked(s); return; @@ -1168,27 +933,34 @@ static void sync_track_align_try_live_snap_locked(vj_audio_sync_shared_t *s, abs_delta_frames = delta_frames < 0 ? -delta_frames : delta_frames; - - - if(s->align_live_last_snap_ms != 0 && delta_frames < 0 && abs_ms < VJ_AUDIO_SYNC_SETTLED_BACKWARD_SNAP_BLOCK_MS) { if(s->align_live_candidate_count > 0) sync_track_align_clear_live_candidate_locked(s); + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN] live settled backward snap held delta=%+d from %+dms conf=%d%% block_ms=%d reason=sync-worker", + delta_frames, + (int)(raw_offset_ms + (raw_offset_ms >= 0.0 ? 0.5 : -0.5)), + confidence_pct, + VJ_AUDIO_SYNC_SETTLED_BACKWARD_SNAP_BLOCK_MS); return; } - - - if(s->align_live_last_snap_ms != 0 && abs_delta_frames > VJ_AUDIO_SYNC_LIVE_SNAP_SETTLE_MAX_FRAMES && confidence_pct < VJ_AUDIO_SYNC_LIVE_SNAP_REACQUIRE_CONF) { if(s->align_live_candidate_count > 0) sync_track_align_clear_live_candidate_locked(s); + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN] live offset reacquire suppressed delta=%+d from %+dms conf=%d%% need_conf=%d%% settle_max=%d reason=sync-worker", + delta_frames, + (int)(raw_offset_ms + (raw_offset_ms >= 0.0 ? 0.5 : -0.5)), + confidence_pct, + VJ_AUDIO_SYNC_LIVE_SNAP_REACQUIRE_CONF, + VJ_AUDIO_SYNC_LIVE_SNAP_SETTLE_MAX_FRAMES); return; } @@ -1209,9 +981,6 @@ static void sync_track_align_try_live_snap_locked(vj_audio_sync_shared_t *s, { same_candidate = 1; - - - if(elapsed_ms < required_observe_ms) { static long last_hold_log_ms = 0; static int last_hold_delta = 0; @@ -1219,6 +988,15 @@ static void sync_track_align_try_live_snap_locked(vj_audio_sync_shared_t *s, now_ms - last_hold_log_ms >= 200L || last_hold_delta != s->align_live_candidate_delta) { + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN] live consensus holding %+d frames from %+dms stable=%d/%d elapsed=%ld/%ldms conf=%d%%", + s->align_live_candidate_delta, + (int)(raw_offset_ms + (raw_offset_ms >= 0.0 ? 0.5 : -0.5)), + s->align_live_candidate_count, + required_stable, + elapsed_ms, + required_observe_ms, + confidence_pct); last_hold_log_ms = now_ms; last_hold_delta = s->align_live_candidate_delta; } @@ -1246,6 +1024,20 @@ static void sync_track_align_try_live_snap_locked(vj_audio_sync_shared_t *s, if(!same_candidate || s->align_live_candidate_count < required_stable) { + int avg_dbg = (s->align_live_candidate_count > 0) ? + sync_clampi(s->align_live_candidate_conf_sum / s->align_live_candidate_count, 0, 100) : confidence_pct; + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN] live offset consensus candidate %+d frames from %+dms conf_min=%d%% conf_cur=%d%% conf_avg=%d%% stable=%d/%d need_conf=%d%% observe=%ldms first=%d reason=sync-worker", + s->align_live_candidate_delta, + (int)(raw_offset_ms + (raw_offset_ms >= 0.0 ? 0.5 : -0.5)), + sync_clampi(s->align_live_candidate_conf_min, 0, 100), + confidence_pct, + avg_dbg, + s->align_live_candidate_count, + required_stable, + required_conf, + required_observe_ms, + s->align_live_last_snap_ms == 0); return; } @@ -1279,9 +1071,6 @@ static void sync_track_align_try_live_snap_locked(vj_audio_sync_shared_t *s, s->align_live_last_snap_delta = s->align_live_candidate_delta; sync_track_align_clear_live_candidate_locked(s); } else { - - - sync_track_align_clear_live_candidate_locked(s); } } @@ -1296,12 +1085,18 @@ static void sync_track_align_publish_locked(vj_audio_sync_shared_t *s) int target_pos; int source_count; int target_count; + int source_rate; + int target_rate; long long target_queue_frames = 0; + long target_overruns = 0; + long target_reads = 0; int target_queue_ms = 0; int min_count; + int max_lag_steps; int best_lag = 0; double best_score = -1.0; + double second_score = -1.0; int window; int min_overlap; int state; @@ -1313,21 +1108,27 @@ static void sync_track_align_publish_locked(vj_audio_sync_shared_t *s) double correction_ppm; int lock_eligible; int published_locked; - int previous_locked = 0; - int hold_publish_eligible = 0; - int first_acquire = 1; - int window_cap; - int min_publish_features = VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES; + int valid_lags = 0; + int overlap_rejects = 0; + int lowvar_rejects = 0; + int lowvar_source_rejects = 0; + int lowvar_target_rejects = 0; + int lowscore_rejects = 0; + int total_lags = 0; int best_overlap = 0; int no_valid_score = 0; - sync_track_align_live_score_t selected_score; + double min_src_var = 0.0; + double min_tgt_var = 0.0; + double max_src_var = 0.0; + double max_tgt_var = 0.0; + int var_seen = 0; + sync_track_align_feature_stats_t src_stats; + sync_track_align_feature_stats_t tgt_stats; + const char *quality_reason = "ok"; if(!s) return; - - - sync_lock(s); if(sync_load_i(&s->mode) != VJ_AUDIO_SYNC_MODE_TRACK_ALIGN) { sync_store_i(&s->track_align_locked, 0); @@ -1336,46 +1137,16 @@ static void sync_track_align_publish_locked(vj_audio_sync_shared_t *s) return; } - first_acquire = (s->align_live_last_snap_ms == 0); - if(first_acquire) { - min_publish_features = VJ_AUDIO_SYNC_ALIGN_ACQUIRE_MIN_FEATURES; - if(min_publish_features < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) - min_publish_features = VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES; - if(min_publish_features > VJ_AUDIO_SYNC_ALIGN_FEATURES) - min_publish_features = VJ_AUDIO_SYNC_ALIGN_FEATURES; - } else { - min_publish_features = VJ_AUDIO_SYNC_ALIGN_SETTLED_MIN_FEATURES; - if(min_publish_features < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) - min_publish_features = VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES; - if(min_publish_features > VJ_AUDIO_SYNC_ALIGN_FEATURES) - min_publish_features = VJ_AUDIO_SYNC_ALIGN_FEATURES; - } - - target_queue_frames = 0; - target_queue_ms = 0; - if(s->target_write_frame_abs > s->target_read_frame_abs) - target_queue_frames = s->target_write_frame_abs - s->target_read_frame_abs; - if(s->target_sample_rate > 0 && target_queue_frames > 0) { - long long qms = (target_queue_frames * 1000LL) / (long long)s->target_sample_rate; - if(qms > 999999LL) - qms = 999999LL; - target_queue_ms = (int)qms; - } - - if(s->align_source_count < min_publish_features) { + if(s->align_source_count < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) { sync_store_i(&s->track_align_locked, 0); sync_store_i(&s->track_align_state, VJ_AUDIO_SYNC_TRACK_STATE_WAIT_SOURCE); -if(!first_acquire) - sync_track_align_clear_rough_hint_locked(s); sync_unlock(s); return; } - if(s->align_target_count < min_publish_features) { + if(s->align_target_count < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) { sync_store_i(&s->track_align_locked, 0); sync_store_i(&s->track_align_state, VJ_AUDIO_SYNC_TRACK_STATE_WAIT_TARGET); -if(!first_acquire) - sync_track_align_clear_rough_hint_locked(s); sync_unlock(s); return; } @@ -1384,11 +1155,15 @@ if(!first_acquire) target_pos = s->align_target_pos; source_count = s->align_source_count; target_count = s->align_target_count; + source_rate = s->align_source_rate; + target_rate = s->align_target_rate; memcpy(source_feat, s->align_source_feat, sizeof(source_feat)); memcpy(target_feat, s->align_target_feat, sizeof(target_feat)); if(s->target_write_frame_abs > s->target_read_frame_abs) target_queue_frames = s->target_write_frame_abs - s->target_read_frame_abs; + target_overruns = sync_load_l(&s->target_queue_overruns); + target_reads = sync_load_l(&s->target_queue_reads); if(s->target_sample_rate > 0 && target_queue_frames > 0) { long long qms = (target_queue_frames * 1000LL) / (long long)s->target_sample_rate; if(qms > 999999LL) @@ -1398,8 +1173,7 @@ if(!first_acquire) sync_unlock(s); if(target_queue_ms < VJ_AUDIO_SYNC_TARGET_MIN_HISTORY_MS || - target_count < VJ_AUDIO_SYNC_ALIGN_LOCK_MIN_TARGET_FEATURES || - (!first_acquire && target_count < min_publish_features)) + target_count < VJ_AUDIO_SYNC_ALIGN_LOCK_MIN_TARGET_FEATURES) { sync_lock(s); if(sync_load_i(&s->mode) == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN) { @@ -1407,8 +1181,6 @@ if(!first_acquire) sync_store_i(&s->track_align_state, VJ_AUDIO_SYNC_TRACK_STATE_WAIT_TARGET); sync_store_i(&s->track_align_confidence_pct, sync_clampi((int)(s->align_conf_smooth + 0.5), 0, 100)); -if(!first_acquire) - sync_track_align_clear_rough_hint_locked(s); sync_store_l(&s->track_align_last_update_ms, sync_now_ms()); } sync_unlock(s); @@ -1416,198 +1188,9 @@ if(!first_acquire) } min_count = source_count < target_count ? source_count : target_count; - window_cap = first_acquire ? - VJ_AUDIO_SYNC_ALIGN_ACQUIRE_WINDOW_FEATURES : - VJ_AUDIO_SYNC_ALIGN_LIVE_WINDOW_FEATURES; - if(window_cap < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) - window_cap = VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES; - if(window_cap > VJ_AUDIO_SYNC_ALIGN_FEATURES) - window_cap = VJ_AUDIO_SYNC_ALIGN_FEATURES; - window = min_count; - if(window > window_cap) - window = window_cap; - if(window < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) { - sync_lock(s); - sync_store_i(&s->track_align_locked, 0); - sync_store_i(&s->track_align_state, VJ_AUDIO_SYNC_TRACK_STATE_SEARCHING); -sync_unlock(s); - return; - } - - sync_track_align_score_clear(&selected_score); - - if(first_acquire) { - int acquire_windows[5]; - int acquire_window_count = 0; - sync_track_align_live_score_t acquire_scores[5]; - int acquire_score_count = 0; - - acquire_windows[acquire_window_count++] = VJ_AUDIO_SYNC_ALIGN_ACQUIRE_SHORT_WINDOW_FEATURES; - acquire_windows[acquire_window_count++] = VJ_AUDIO_SYNC_ALIGN_ACQUIRE_MIN_FEATURES; - acquire_windows[acquire_window_count++] = VJ_AUDIO_SYNC_ALIGN_ACQUIRE_MID_WINDOW_FEATURES; - acquire_windows[acquire_window_count++] = (VJ_AUDIO_SYNC_ALIGN_ACQUIRE_MID_WINDOW_FEATURES + VJ_AUDIO_SYNC_ALIGN_ACQUIRE_WINDOW_FEATURES) / 2; - acquire_windows[acquire_window_count++] = VJ_AUDIO_SYNC_ALIGN_ACQUIRE_WINDOW_FEATURES; - - for(int wi = 0; wi < acquire_window_count; wi++) { - sync_track_align_live_score_t candidate; - int candidate_window = acquire_windows[wi]; - - if(candidate_window < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) - candidate_window = VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES; - if(candidate_window > window_cap) - candidate_window = window_cap; - if(candidate_window > min_count) - continue; - - - int duplicate = 0; - for(int pi = 0; pi < wi; pi++) { - int prev_window = acquire_windows[pi]; - if(prev_window < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) - prev_window = VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES; - if(prev_window > window_cap) - prev_window = window_cap; - if(prev_window == candidate_window) { - duplicate = 1; - break; - } - } - if(duplicate) - continue; - - sync_track_align_score_window(source_feat, - source_pos, - source_count, - target_feat, - target_pos, - target_count, - candidate_window, - VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS, - &candidate); - - if(acquire_score_count < 5) - acquire_scores[acquire_score_count++] = candidate; - - if(sync_track_align_score_is_better(&candidate, &selected_score)) - selected_score = candidate; - } - - - - - if(min_count > VJ_AUDIO_SYNC_ALIGN_ACQUIRE_MIN_FEATURES && - min_count < VJ_AUDIO_SYNC_ALIGN_ACQUIRE_WINDOW_FEATURES) - { - sync_track_align_live_score_t candidate; - int candidate_window = min_count; - if(candidate_window > window_cap) - candidate_window = window_cap; - if(candidate_window >= VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) { - sync_track_align_score_window(source_feat, - source_pos, - source_count, - target_feat, - target_pos, - target_count, - candidate_window, - VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS, - &candidate); - if(acquire_score_count < 5) - acquire_scores[acquire_score_count++] = candidate; - if(sync_track_align_score_is_better(&candidate, &selected_score)) - selected_score = candidate; - } - } - - - - - if(acquire_score_count > 1) { - int best_cluster_count = 0; - int best_cluster_conf_sum = 0; - int best_cluster_idx = -1; - int tolerance_steps = VJ_AUDIO_SYNC_ROUGH_HINT_TOLERANCE_MS / VJ_AUDIO_SYNC_ALIGN_HOP_MS; - if(tolerance_steps < 8) - tolerance_steps = 8; - - for(int i = 0; i < acquire_score_count; i++) { - sync_track_align_live_score_t *a = &acquire_scores[i]; - int cluster_count = 0; - int cluster_conf_sum = 0; - - if(a->no_valid_score || a->edge_lag) - continue; - if(a->best_score < VJ_AUDIO_SYNC_ROUGH_HINT_MIN_SCORE || - a->peak_sep < VJ_AUDIO_SYNC_ROUGH_HINT_MIN_SEPARATION) - continue; - - for(int j = 0; j < acquire_score_count; j++) { - sync_track_align_live_score_t *b = &acquire_scores[j]; - int d; - if(b->no_valid_score || b->edge_lag) - continue; - if(b->best_score < VJ_AUDIO_SYNC_ROUGH_HINT_MIN_SCORE || - b->peak_sep < VJ_AUDIO_SYNC_ROUGH_HINT_MIN_SEPARATION) - continue; - d = b->best_lag - a->best_lag; - if(d < 0) - d = -d; - if(d <= tolerance_steps) { - cluster_count++; - cluster_conf_sum += sync_clampi((int)(b->conf + 0.5), 0, 100); - } - } - - if(cluster_count > best_cluster_count || - (cluster_count == best_cluster_count && cluster_conf_sum > best_cluster_conf_sum)) - { - best_cluster_count = cluster_count; - best_cluster_conf_sum = cluster_conf_sum; - best_cluster_idx = i; - } - } - - if(best_cluster_idx >= 0 && best_cluster_count >= 2) { - sync_track_align_live_score_t basin = acquire_scores[best_cluster_idx]; - int avg_conf = best_cluster_conf_sum / best_cluster_count; - if(avg_conf > (int)(basin.conf + 0.5)) - basin.conf = (double)avg_conf; - basin.conf += (double)(best_cluster_count - 1) * 3.0; - if(basin.conf > 100.0) - basin.conf = 100.0; - basin.peak_sep += 0.010 * (double)(best_cluster_count - 1); - basin.quality_reason = "acquire-basin"; - - - - - if(sync_track_align_score_is_better(&basin, &selected_score) || - selected_score.conf < basin.conf + 6.0) - selected_score = basin; - } - } - } else { - sync_track_align_score_window(source_feat, - source_pos, - source_count, - target_feat, - target_pos, - target_count, - window, - VJ_AUDIO_SYNC_ALIGN_SETTLED_MAX_LAG_MS, - &selected_score); - } - - window = selected_score.window; - min_overlap = selected_score.min_overlap; - best_lag = selected_score.best_lag; - best_score = selected_score.best_score; - peak_sep = selected_score.peak_sep; - edge_lag = selected_score.edge_lag; - no_valid_score = selected_score.no_valid_score; - best_overlap = selected_score.best_overlap; - conf = selected_score.conf; + if(window > 128) + window = 128; if(window < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) { sync_lock(s); sync_store_i(&s->track_align_locked, 0); @@ -1616,6 +1199,162 @@ sync_unlock(s); return; } + sync_track_align_feature_stats_ring(source_feat, + source_pos, + source_count, + window, + &src_stats); + sync_track_align_feature_stats_ring(target_feat, + target_pos, + target_count, + window, + &tgt_stats); + + min_overlap = (window * VJ_AUDIO_SYNC_ALIGN_MIN_OVERLAP_PCT + 99) / 100; + if(min_overlap < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) + min_overlap = VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES; + if(min_overlap > window) + min_overlap = window; + + max_lag_steps = VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS / VJ_AUDIO_SYNC_ALIGN_HOP_MS; + if(max_lag_steps > source_count - min_overlap) + max_lag_steps = source_count - min_overlap; + if(max_lag_steps > target_count - min_overlap) + max_lag_steps = target_count - min_overlap; + if(max_lag_steps < 2) + max_lag_steps = 2; + + for(int lag = -max_lag_steps; lag <= max_lag_steps; lag++) { + double sum_x = 0.0, sum_y = 0.0; + double sum_xx = 0.0, sum_yy = 0.0, sum_xy = 0.0; + int n = 0; + + total_lags++; + + for(int i = 0; i < window; i++) { + int src_back = i; + int tgt_back = i - lag; + if(tgt_back < 0 || tgt_back >= target_count) + continue; + if(src_back < 0 || src_back >= source_count) + continue; + + double x = sync_align_ring_get(source_feat, + source_pos, + source_count, + src_back); + double y = sync_align_ring_get(target_feat, + target_pos, + target_count, + tgt_back); + sum_x += x; + sum_y += y; + sum_xx += x * x; + sum_yy += y * y; + sum_xy += x * y; + n++; + } + + if(n < min_overlap) { + overlap_rejects++; + continue; + } + + { + double nf = (double)n; + double cov = sum_xy - (sum_x * sum_y / nf); + double vx = sum_xx - (sum_x * sum_x / nf); + double vy = sum_yy - (sum_y * sum_y / nf); + double score = 0.0; + + if(!var_seen) { + min_src_var = vx; + min_tgt_var = vy; + max_src_var = vx; + max_tgt_var = vy; + var_seen = 1; + } else { + if(vx < min_src_var) min_src_var = vx; + if(vy < min_tgt_var) min_tgt_var = vy; + if(vx > max_src_var) max_src_var = vx; + if(vy > max_tgt_var) max_tgt_var = vy; + } + + if(vx <= VJ_AUDIO_SYNC_ALIGN_MIN_VARIANCE || + vy <= VJ_AUDIO_SYNC_ALIGN_MIN_VARIANCE) + { + if(vx <= VJ_AUDIO_SYNC_ALIGN_MIN_VARIANCE) + lowvar_source_rejects++; + if(vy <= VJ_AUDIO_SYNC_ALIGN_MIN_VARIANCE) + lowvar_target_rejects++; + lowvar_rejects++; + continue; + } + + score = cov / sqrt(vx * vy); + if(score < 0.0) + score = 0.0; + else if(score > 1.0) + score = 1.0; + + if(score < VJ_AUDIO_SYNC_ALIGN_MIN_SCORE) { + lowscore_rejects++; + continue; + } + + valid_lags++; + + if(score > best_score) { + second_score = best_score; + best_score = score; + best_lag = lag; + best_overlap = n; + } else if(score > second_score) { + second_score = score; + } + } + } + + if(valid_lags <= 0 || best_score < VJ_AUDIO_SYNC_ALIGN_MIN_SCORE) { + no_valid_score = 1; + best_lag = 0; + best_score = 0.0; + second_score = 0.0; + best_overlap = 0; + quality_reason = (lowvar_rejects > 0 && lowvar_rejects >= overlap_rejects) ? + "low-variance" : + (overlap_rejects > 0 ? "low-overlap" : "no-valid-score"); + } else if(second_score < 0.0) { + second_score = 0.0; + } + + peak_sep = best_score - second_score; + if(peak_sep < 0.0) + peak_sep = 0.0; + + edge_lag = (!no_valid_score && + abs(best_lag) >= (max_lag_steps - VJ_AUDIO_SYNC_ALIGN_EDGE_GUARD_STEPS)); + + conf = best_score * 100.0; + conf *= sync_clampd(peak_sep * 4.0 + 0.45, 0.0, 1.0); + + if(edge_lag && !(best_score >= 0.90 && peak_sep >= 0.12)) { + conf *= 0.25; + quality_reason = "edge"; + } + + if(best_score < VJ_AUDIO_SYNC_ALIGN_MIN_LOCK_SCORE || + peak_sep < VJ_AUDIO_SYNC_ALIGN_MIN_LOCK_SEPARATION) + { + conf *= 0.55; + if(!no_valid_score && !edge_lag) + quality_reason = (best_score < VJ_AUDIO_SYNC_ALIGN_MIN_LOCK_SCORE) ? + "weak-score" : "weak-separation"; + } + + if(no_valid_score) + conf = 0.0; + offset_ms = no_valid_score ? 0.0 : ((double)best_lag * (double)VJ_AUDIO_SYNC_ALIGN_HOP_MS); raw_offset_ms = offset_ms; @@ -1640,67 +1379,25 @@ sync_unlock(s); return; } - previous_locked = sync_load_i(&s->track_align_locked); - s->align_conf_smooth = (0.82 * s->align_conf_smooth) + (0.18 * conf); - - hold_publish_eligible = 0; - if(!no_valid_score && !edge_lag && - best_score >= (double)VJ_AUDIO_SYNC_ALIGN_LOCK_HOLD_SCORE && - conf >= (double)VJ_AUDIO_SYNC_ALIGN_LOCK_HOLD_CONF && - peak_sep >= (double)VJ_AUDIO_SYNC_ALIGN_LOCK_HOLD_SEPARATION) - { - int raw_ms = sync_clampi((int)(raw_offset_ms + - (raw_offset_ms >= 0.0 ? 0.5 : -0.5)), - -VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS, - VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS); - int smooth_ms = sync_clampi((int)(s->align_offset_smooth_ms + - (s->align_offset_smooth_ms >= 0.0 ? 0.5 : -0.5)), - -VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS, - VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS); - int delta_ms = raw_ms - smooth_ms; - if(delta_ms < 0) - delta_ms = -delta_ms; - - if(!previous_locked || - s->align_hold_blocks > 0 || - delta_ms <= VJ_AUDIO_SYNC_ALIGN_LOCK_HYSTERESIS_MS) - hold_publish_eligible = 1; - } + s->align_conf_smooth = (0.80 * s->align_conf_smooth) + (0.20 * conf); if(lock_eligible && s->align_conf_smooth >= (double)VJ_AUDIO_SYNC_ALIGN_LOCK_CONF) { - const double blend = VJ_AUDIO_SYNC_ALIGN_LOCK_OFFSET_BLEND; - s->align_offset_smooth_ms = ((1.0 - blend) * s->align_offset_smooth_ms) + - (blend * offset_ms); + s->align_offset_smooth_ms = (0.86 * s->align_offset_smooth_ms) + + (0.14 * offset_ms); s->align_hold_blocks = VJ_AUDIO_SYNC_ALIGN_HOLD_BLOCKS; state = VJ_AUDIO_SYNC_TRACK_STATE_LOCKED; - } else if(hold_publish_eligible && (previous_locked || s->align_hold_blocks > 0)) { - const double blend = VJ_AUDIO_SYNC_ALIGN_HOLD_OFFSET_BLEND; - s->align_offset_smooth_ms = ((1.0 - blend) * s->align_offset_smooth_ms) + - (blend * offset_ms); - - if(s->align_hold_blocks > 0) - s->align_hold_blocks--; - - - - - if(previous_locked && s->align_hold_blocks < VJ_AUDIO_SYNC_ALIGN_LOCK_LOST_BLOCKS) - s->align_hold_blocks = VJ_AUDIO_SYNC_ALIGN_LOCK_LOST_BLOCKS; - + } else if(!no_valid_score && !edge_lag && s->align_hold_blocks > 0) { + s->align_hold_blocks--; state = VJ_AUDIO_SYNC_TRACK_STATE_HOLD; } else { - if(s->align_hold_blocks > 0) - s->align_hold_blocks--; state = (s->align_conf_smooth > 25.0) ? VJ_AUDIO_SYNC_TRACK_STATE_SEARCHING : VJ_AUDIO_SYNC_TRACK_STATE_FALLBACK; } - published_locked = (state == VJ_AUDIO_SYNC_TRACK_STATE_LOCKED || - (state == VJ_AUDIO_SYNC_TRACK_STATE_HOLD && - hold_publish_eligible && - (previous_locked || s->align_hold_blocks > 0))) ? 1 : 0; + published_locked = (lock_eligible && + state == VJ_AUDIO_SYNC_TRACK_STATE_LOCKED) ? 1 : 0; { double export_offset_ms = 0.0; @@ -1708,10 +1405,9 @@ sync_unlock(s); long hint_now_ms = sync_now_ms(); int raw_hint_ms = sync_clampi((int)(raw_offset_ms + (raw_offset_ms >= 0.0 ? 0.5 : -0.5)), - -VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS, VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS); + -8000, 8000); int raw_hint_conf = sync_clampi((int)(conf + 0.5), 0, 100); - int rough_usable = (first_acquire && - !no_valid_score && !edge_lag && + int rough_usable = (!no_valid_score && !edge_lag && abs(raw_hint_ms) >= VJ_AUDIO_SYNC_ROUGH_HINT_MIN_OFFSET_MS && raw_hint_conf >= VJ_AUDIO_SYNC_ROUGH_HINT_MIN_CONF && best_score >= VJ_AUDIO_SYNC_ROUGH_HINT_MIN_SCORE && @@ -1762,7 +1458,7 @@ sync_unlock(s); sync_store_i(&s->track_align_offset_ms, sync_clampi((int)(export_offset_ms + (export_offset_ms >= 0.0 ? 0.5 : -0.5)), - -VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS, VJ_AUDIO_SYNC_ALIGN_MAX_LAG_MS)); + -8000, 8000)); sync_store_i(&s->track_align_confidence_pct, sync_clampi((int)(export_conf + 0.5), 0, 100)); } @@ -1823,10 +1519,41 @@ sync_unlock(s); if(should_log) { if(locked_dbg) { -} else if(s->align_rough_hint_ms > 0 && + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][LIVE-LOCK] off=%+dms conf=%d%% raw=%+.0fms best=%.3f sep=%.3f src_feat=%d tgt_feat=%d reason=%s", + hint_dbg, + conf_dbg, + raw_offset_ms, + best_score, + peak_sep, + source_count, + target_count, + quality_reason); + } else if(s->align_rough_hint_ms > 0 && (now_dbg_ms - s->align_rough_hint_ms) <= VJ_AUDIO_SYNC_ROUGH_HINT_HOLD_MS) { -} else { -} + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][LIVE-HINT] hint=%+dms conf=%d%% raw=%+.0fms best=%.3f sep=%.3f count=%d src_feat=%d tgt_feat=%d state=%s reason=%s", + hint_dbg, + conf_dbg, + raw_offset_ms, + best_score, + peak_sep, + s->align_rough_hint_count, + source_count, + target_count, + sync_track_align_state_name_dbg(state), + quality_reason); + } else { + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][LIVE] state=%s src_feat=%d tgt_feat=%d conf=%.1f%% best=%.3f sep=%.3f reason=%s", + sync_track_align_state_name_dbg(state), + source_count, + target_count, + s->align_conf_smooth, + best_score, + peak_sep, + quality_reason); + } dbg_last_state = state; dbg_last_locked = locked_dbg; dbg_last_hint = hint_dbg; @@ -1843,9 +1570,6 @@ static void sync_track_align_publish(vj_audio_sync_shared_t *s) if(!s) return; - - - sync_track_align_publish_locked(s); } @@ -1884,20 +1608,17 @@ static int sync_track_align_extract_features_from_audio(const uint8_t *data, n = 1; double sum = 0.0; - if(channels == 1) { - for(int i = 0; i < n; i++) { - const uint8_t *f = data + ((size_t)(done + i) * (size_t)frame_bytes); - double m = (double)sync_read_sample(f, sample_bytes) * (1.0 / 32768.0); - sum += m * m; - } - } else { - for(int i = 0; i < n; i++) { - const uint8_t *f = data + ((size_t)(done + i) * (size_t)frame_bytes); - int l = sync_read_sample(f, sample_bytes); - int r = sync_read_sample(f + sample_bytes, sample_bytes); - double m = ((double)l + (double)r) * (1.0 / 65536.0); - sum += m * m; + for(int i = 0; i < n; i++) { + const uint8_t *f = data + ((size_t)(done + i) * (size_t)frame_bytes); + int l, r; + if(channels == 1) { + l = r = sync_read_sample(f, sample_bytes); + } else { + l = sync_read_sample(f, sample_bytes); + r = sync_read_sample(f + sample_bytes, sample_bytes); } + double m = ((double)l + (double)r) * (1.0 / 65536.0); + sum += m * m; } accum += sum; @@ -1905,7 +1626,7 @@ static int sync_track_align_extract_features_from_audio(const uint8_t *data, done += n; if(accum_frames >= hop) { - double level = sqrtf((float)(accum / (double)accum_frames)); + double level = sqrt(accum / (double)accum_frames); double rise; double feat; if(!prev_initialized) { @@ -1937,9 +1658,6 @@ static int sync_track_align_master_latency_steps(vj_audio_sync_shared_t *s) if(latency_s < 0.0) latency_s = 0.0; - - - if(latency_s > 0.500) latency_s = 0.500; @@ -1997,7 +1715,7 @@ static double sync_track_align_feature_contrast(const float *v, int n) double d = (double)v[i] - mean; var += d * d; } - var = sqrtf((float)(var / (double)n)) * 8.0; + var = sqrt(var / (double)n) * 8.0; return sync_clampd(var, 0.0, 1.0); } @@ -2095,14 +1813,24 @@ int vj_audio_sync_track_align_probe_target_audio(vj_audio_sync_shared_t *s, float master[VJ_AUDIO_SYNC_ALIGN_PROBE_MAX_FEATURES]; int tgt_count; int n; - int latency_steps; - int source_needed; + int latency_steps = 0; double score; double contrast; double conf; - double dscore; - double tgt_contrast; - double src_contrast; + double dscore = 0.0; + double tgt_contrast = 0.0; + double src_contrast = 0.0; + double pcm_rms = 0.0; + double pcm_peak = 0.0; + int pcm_nonzero = 0; + int source_count_snapshot = 0; + int source_needed = 0; + const char *reject_reason = "ok"; + sync_track_align_feature_stats_t tgt_stats; + sync_track_align_feature_stats_t master_stats; + + sync_track_align_stats_clear(&tgt_stats); + sync_track_align_stats_clear(&master_stats); if(confidence_pct) *confidence_pct = 0; @@ -2110,6 +1838,15 @@ int vj_audio_sync_track_align_probe_target_audio(vj_audio_sync_shared_t *s, if(!s || !src || frames <= 0 || frame_bytes <= 0 || channels <= 0) return 0; + sync_track_align_pcm_stats(src, + frames, + frame_bytes, + channels, + bits_per_channel, + &pcm_rms, + &pcm_peak, + &pcm_nonzero); + tgt_count = sync_track_align_extract_features_from_audio(src, frames, frame_bytes, @@ -2118,17 +1855,37 @@ int vj_audio_sync_track_align_probe_target_audio(vj_audio_sync_shared_t *s, sample_rate, tgt, VJ_AUDIO_SYNC_ALIGN_PROBE_MAX_FEATURES); - if(tgt_count < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) + sync_track_align_feature_stats_array(tgt, tgt_count, &tgt_stats); + + if(tgt_count < VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES) { + reject_reason = "few-target-features"; + if(sync_track_align_probe_debug_should_log(reject_reason, 0)) + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][PROBE] reject=%s frames=%d pcm_rms=%.6f pcm_peak=%.6f pcm_nonzero=%d/%d tgt_feat=%d tgt_mean=%.5f tgt_sd=%.8f need_feat=%d", + reject_reason, frames, pcm_rms, pcm_peak, pcm_nonzero, frames, + tgt_count, tgt_stats.mean, tgt_stats.sd, + VJ_AUDIO_SYNC_ALIGN_MIN_FEATURES); return 0; + } latency_steps = sync_track_align_master_latency_steps(s); sync_lock(s); + source_count_snapshot = s->align_source_count; source_needed = tgt_count + latency_steps; if(sync_load_i(&s->mode) != VJ_AUDIO_SYNC_MODE_TRACK_ALIGN || s->align_source_count < source_needed) { + int mode_snapshot = sync_load_i(&s->mode); sync_unlock(s); + reject_reason = (mode_snapshot != VJ_AUDIO_SYNC_MODE_TRACK_ALIGN) ? + "wrong-mode" : "wait-source-features"; + if(sync_track_align_probe_debug_should_log(reject_reason, 0)) + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][PROBE] reject=%s frames=%d pcm_rms=%.6f pcm_peak=%.6f pcm_nonzero=%d/%d tgt_feat=%d tgt_mean=%.5f tgt_sd=%.8f src_feat=%d need_src=%d latency_steps=%d mode=%d", + reject_reason, frames, pcm_rms, pcm_peak, pcm_nonzero, frames, + tgt_count, tgt_stats.mean, tgt_stats.sd, + source_count_snapshot, source_needed, latency_steps, mode_snapshot); return 0; } @@ -2138,28 +1895,51 @@ int vj_audio_sync_track_align_probe_target_audio(vj_audio_sync_shared_t *s, if(n > VJ_AUDIO_SYNC_ALIGN_PROBE_MAX_FEATURES) n = VJ_AUDIO_SYNC_ALIGN_PROBE_MAX_FEATURES; - for(int i = 0; i < n; i++) + for(int i = 0; i < n; i++) { master[n - 1 - i] = sync_align_ring_get(s->align_source_feat, s->align_source_pos, s->align_source_count, i + latency_steps); + } sync_unlock(s); - score = sync_track_align_score_feature_vectors(master, tgt, n); - tgt_contrast = sync_track_align_feature_contrast(tgt, n); - src_contrast = sync_track_align_feature_contrast(master, n); - dscore = sync_track_align_score_feature_deltas(master, tgt, n); - contrast = tgt_contrast < src_contrast ? tgt_contrast : src_contrast; - if(contrast < VJ_AUDIO_SYNC_ALIGN_PROBE_MIN_CONTRAST) - contrast *= 0.35; + sync_track_align_feature_stats_array(master, n, &master_stats); + + score = sync_track_align_score_feature_vectors(master, tgt, n); + + { + tgt_contrast = sync_track_align_feature_contrast(tgt, n); + src_contrast = sync_track_align_feature_contrast(master, n); + dscore = sync_track_align_score_feature_deltas(master, tgt, n); + contrast = tgt_contrast < src_contrast ? tgt_contrast : src_contrast; + if(contrast < VJ_AUDIO_SYNC_ALIGN_PROBE_MIN_CONTRAST) + contrast *= 0.35; + score = (0.68 * score) + (0.32 * dscore); + } - score = (0.68 * score) + (0.32 * dscore); conf = score * 100.0; conf *= (0.35 + 0.65 * contrast); if(confidence_pct) *confidence_pct = sync_clampi((int)(conf + 0.5), 0, 100); + if(conf < 1.0) + reject_reason = (contrast < VJ_AUDIO_SYNC_ALIGN_PROBE_MIN_CONTRAST) ? + "low-contrast" : "low-confidence"; + + if(sync_track_align_probe_debug_should_log(reject_reason, + sync_clampi((int)(conf + 0.5), 0, 100))) + { + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][PROBE] %s frames=%d pcm_rms=%.6f pcm_peak=%.6f pcm_nonzero=%d/%d feat=%d n=%d src_feat=%d latency_steps=%d score=%.3f dscore=%.3f conf=%.1f%% contrast=%.3f src_contrast=%.3f tgt_contrast=%.3f src_mean=%.5f src_sd=%.8f src_minmax=%.5f..%.5f tgt_mean=%.5f tgt_sd=%.8f tgt_minmax=%.5f..%.5f", + (conf >= 1.0) ? "ok" : reject_reason, + frames, pcm_rms, pcm_peak, pcm_nonzero, frames, + tgt_count, n, source_count_snapshot, latency_steps, + score, dscore, conf, contrast, src_contrast, tgt_contrast, + master_stats.mean, master_stats.sd, master_stats.min, master_stats.max, + tgt_stats.mean, tgt_stats.sd, tgt_stats.min, tgt_stats.max); + } + return (conf >= 1.0) ? 1 : 0; } @@ -2170,8 +1950,12 @@ void vj_audio_sync_track_align_offer_snap(vj_audio_sync_shared_t *s, if(!s) return; - if(delta_frames == 0 || confidence_pct < 1) + if(delta_frames == 0 || confidence_pct < 1) { + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][SNAP-OFFER] ignored invalid delta=%+d conf=%d%%", + delta_frames, confidence_pct); return; + } sync_lock(s); if(sync_load_i(&s->mode) == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN) { @@ -2182,7 +1966,11 @@ void vj_audio_sync_track_align_offer_snap(vj_audio_sync_shared_t *s, sync_track_align_clear_live_candidate_locked(s); } else { -} + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][SNAP-OFFER] ignored mode=%d delta=%+d conf=%d%%", + sync_load_i(&s->mode), delta_frames, + sync_clampi(confidence_pct, 0, 100)); + } sync_unlock(s); } @@ -2195,7 +1983,12 @@ int vj_audio_sync_track_align_offer_servo_nudge(vj_audio_sync_shared_t *s, if(!s) return 0; - if((delta_frames != -1 && delta_frames != 1) || confidence_pct < 1) return 0; + if((delta_frames != -1 && delta_frames != 1) || confidence_pct < 1) { + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][SNAP-OFFER] ignored invalid servo delta=%+d conf=%d%%", + delta_frames, confidence_pct); + return 0; + } sync_lock(s); if(sync_load_i(&s->mode) == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN) { @@ -2207,7 +2000,11 @@ int vj_audio_sync_track_align_offer_servo_nudge(vj_audio_sync_shared_t *s, sync_track_align_clear_live_candidate_locked(s); } else { -} + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][SNAP-OFFER] ignored mode=%d servo delta=%+d conf=%d%%", + sync_load_i(&s->mode), delta_frames, + sync_clampi(confidence_pct, 0, 100)); + } sync_unlock(s); return accepted; @@ -2228,17 +2025,20 @@ int vj_audio_sync_track_align_consume_snap(vj_audio_sync_shared_t *s, return 0; sync_lock(s); - pending = s->track_align_snap_pending; + pending = sync_load_i(&s->track_align_snap_pending); if(pending) { - int d = s->track_align_snap_delta_frames; - int c = s->track_align_snap_confidence_pct; + int d = sync_load_i(&s->track_align_snap_delta_frames); + int c = sync_load_i(&s->track_align_snap_confidence_pct); if(delta_frames) *delta_frames = d; if(confidence_pct) *confidence_pct = c; - s->track_align_snap_pending = 0; - s->track_align_snap_delta_frames = 0; - s->track_align_snap_confidence_pct = 0; + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][SNAP-CONSUME] pending=1 delta=%+d conf=%d%% seq=%d", + d, c, sync_load_i(&s->track_align_snap_seq)); + sync_store_i(&s->track_align_snap_pending, 0); + sync_store_i(&s->track_align_snap_delta_frames, 0); + sync_store_i(&s->track_align_snap_confidence_pct, 0); sync_track_align_clear_live_candidate_locked(s); } sync_unlock(s); @@ -2276,20 +2076,17 @@ static void sync_track_align_push_block(vj_audio_sync_shared_t *s, n = 1; double sum = 0.0; - if(channels == 1) { - for(int i = 0; i < n; i++) { - const uint8_t *f = data + ((size_t)(done + i) * (size_t)frame_bytes); - double m = (double)sync_read_sample(f, sample_bytes) * (1.0 / 32768.0); - sum += m * m; - } - } else { - for(int i = 0; i < n; i++) { - const uint8_t *f = data + ((size_t)(done + i) * (size_t)frame_bytes); - int l = sync_read_sample(f, sample_bytes); - int r = sync_read_sample(f + sample_bytes, sample_bytes); - double m = ((double)l + (double)r) * (1.0 / 65536.0); - sum += m * m; + for(int i = 0; i < n; i++) { + const uint8_t *f = data + ((size_t)(done + i) * (size_t)frame_bytes); + int l, r; + if(channels == 1) { + l = r = sync_read_sample(f, sample_bytes); + } else { + l = sync_read_sample(f, sample_bytes); + r = sync_read_sample(f + sample_bytes, sample_bytes); } + double m = ((double)l + (double)r) * (1.0 / 65536.0); + sum += m * m; } *accum += sum; @@ -2297,19 +2094,14 @@ static void sync_track_align_push_block(vj_audio_sync_shared_t *s, done += n; if(*accum_frames >= hop) { - double level = sqrtf((float)(*accum / (double)(*accum_frames))); + double level = sqrt(*accum / (double)(*accum_frames)); sync_track_align_append_feature_locked(s, is_target, level, sample_rate); *accum = 0.0; *accum_frames = 0; - - - - if(sync_track_align_publish_gate_ready()) { - sync_unlock(s); - sync_track_align_publish(s); - sync_lock(s); - } + sync_unlock(s); + sync_track_align_publish(s); + sync_lock(s); } } sync_unlock(s); @@ -2367,9 +2159,6 @@ static void sync_clear_monitor_transport_locked(vj_audio_sync_shared_t *s) if(!s) return; - - - s->monitor_read_pos = 0.0; s->monitor_read_valid = 0; s->monitor_last_l = 0; @@ -2379,37 +2168,6 @@ static void sync_clear_monitor_transport_locked(vj_audio_sync_shared_t *s) s->monitor_last_debug_ms = 0; } - -static void sync_bridge_clear_stretch_locked(vj_audio_sync_shared_t *s) -{ - if(!s) - return; - - s->bridge_stretch_segment_start = 0.0; - s->bridge_stretch_prev_start = 0.0; - s->bridge_stretch_segment_pos = 0; - s->bridge_stretch_prev_valid = 0; - s->bridge_stretch_segment_len = VJ_AUDIO_SYNC_BRIDGE_STRETCH_SEGMENT_FRAMES; - s->bridge_stretch_overlap = VJ_AUDIO_SYNC_BRIDGE_STRETCH_OVERLAP_FRAMES; -} - -static void sync_bridge_clear_locked(vj_audio_sync_shared_t *s, int state) -{ - if(!s) - return; - - s->bridge_read_pos = 0.0; - s->bridge_read_valid = 0; - s->bridge_ratio_smooth = 0.0; - s->bridge_last_correction = 1.0; - s->bridge_latch_updated_ms = 0; - s->bridge_source_bpm_latched = 0.0; - s->bridge_target_bpm_latched = 0.0; - s->bridge_bpm_latch_valid = 0; - sync_bridge_clear_stretch_locked(s); - sync_store_i(&s->bridge_active, 0); - sync_store_i(&s->bridge_state, state); -} static void sync_monitor_hold_last(vj_audio_sync_shared_t *s, uint8_t *dst, int dst_frames, @@ -2451,11 +2209,10 @@ void vj_audio_sync_init(vj_audio_sync_shared_t *s, int input_channels) sync_store_i(&s->mode, VJ_AUDIO_SYNC_MODE_LIVE_EXTERNAL); sync_store_i(&s->source, VJ_AUDIO_SYNC_SOURCE_JACK); sync_store_i(&s->target_mode, VJ_AUDIO_SYNC_TARGET_MANUAL); - sync_store_i(&s->max_correction_pct, 8); + sync_store_i(&s->max_correction_pct, 4); s->bridge_source_bpm_latched = 0.0; s->bridge_target_bpm_latched = 0.0; s->bridge_bpm_latch_valid = 0; - sync_bridge_clear_stretch_locked(s); sync_track_align_clear_locked(s); sync_store_i(&s->initialized, 1); } @@ -2497,10 +2254,10 @@ void vj_audio_sync_free(vj_audio_sync_shared_t *s) s->bridge_source_bpm_latched = 0.0; s->bridge_target_bpm_latched = 0.0; s->bridge_bpm_latch_valid = 0; - sync_bridge_clear_stretch_locked(s); sync_clear_monitor_transport_locked(s); sync_track_align_clear_locked(s); sync_unlock(s); + sync_wav_side_clear(s); } void vj_audio_sync_request_stop(vj_audio_sync_shared_t *s) @@ -2540,10 +2297,10 @@ static int sync_prepare_ring_locked(vj_audio_sync_shared_t *s, if(s->ring && s->ring_frames >= ring_frames && - s->channels == channels && - s->bytes_per_frame == frame_bytes && - s->bits_per_channel == bits && - s->sample_rate == rate) + sync_load_i(&s->channels) == channels && + sync_load_i(&s->bytes_per_frame) == frame_bytes && + sync_load_i(&s->bits_per_channel) == bits && + sync_load_i(&s->sample_rate) == rate) return 1; ring = (uint8_t*)vj_malloc(ring_bytes); @@ -2570,14 +2327,20 @@ static int sync_prepare_ring_locked(vj_audio_sync_shared_t *s, s->bridge_bpm_latch_valid = 0; sync_store_i(&s->bridge_state, VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_SOURCE); sync_clear_monitor_transport_locked(s); - sync_bridge_clear_stretch_locked(s); + s->bridge_ratio_smooth = 1.0; + s->bridge_last_correction = 1.0; + s->bridge_latch_updated_ms = 0; + s->bridge_source_bpm_latched = 0.0; + s->bridge_target_bpm_latched = 0.0; + s->bridge_bpm_latch_valid = 0; + sync_store_i(&s->bridge_state, VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_SOURCE); sync_track_align_clear_locked(s); - s->channels = channels; - s->bytes_per_frame = frame_bytes; - s->bits_per_channel = bits; - s->sample_rate = rate; + sync_store_i(&s->channels, channels); + sync_store_i(&s->bytes_per_frame, frame_bytes); + sync_store_i(&s->bits_per_channel, bits); + sync_store_i(&s->sample_rate, rate); s->record_channels = channels; s->record_bytes_per_frame = frame_bytes; @@ -2616,13 +2379,11 @@ static void sync_publish_audio(vj_audio_sync_shared_t *s, if(!sync_prepare_ring(s, channels, bits, rate)) return; - sync_lock(s); - - frame_bytes = s->bytes_per_frame; - if(frame_bytes <= 0) { - sync_unlock(s); + frame_bytes = sync_load_i(&s->bytes_per_frame); + if(frame_bytes <= 0) return; - } + + sync_lock(s); if(frames > s->ring_frames) { src += (size_t)(frames - s->ring_frames) * (size_t)frame_bytes; @@ -2708,9 +2469,6 @@ static int sync_prepare_target_ring_locked(vj_audio_sync_shared_t *s, s->target_sample_rate = rate; sync_store_l(&s->target_queue_overruns, 0); sync_store_l(&s->target_queue_reads, 0); - sync_store_l(&s->target_queue_lock_drops, 0); - sync_store_l(&s->target_queue_overflow_events, 0); - sync_store_l(&s->target_queue_dropped_frames, 0); return 1; } @@ -2739,8 +2497,18 @@ static int sync_enqueue_target_audio(vj_audio_sync_shared_t *s, return 0; if(!sync_try_lock_for_us(s, 50)) { - sync_add_l(&s->target_queue_overruns, 1); - sync_add_l(&s->target_queue_lock_drops, 1); + long drops = sync_add_l(&s->target_queue_overruns, 1); + static long dbg_last_target_enqueue_drop_ms = 0; + long now_dbg_ms = sync_now_ms(); + if(dbg_last_target_enqueue_drop_ms == 0 || + (now_dbg_ms - dbg_last_target_enqueue_drop_ms) >= 1000 || + drops <= 8 || (drops % 16) == 0) + { + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][TARGET-Q] enqueue drop reason=lock-contention frames=%d fmt=%dch/%dbit/%dHz drops=%ld wait_us=50", + frames, channels, bits, sample_rate, drops); + dbg_last_target_enqueue_drop_ms = now_dbg_ms; + } return 0; } @@ -2755,12 +2523,9 @@ static int sync_enqueue_target_audio(vj_audio_sync_shared_t *s, } if(frames > s->target_ring_frames) { - int drop = frames - s->target_ring_frames; - src += (size_t)drop * (size_t)frame_bytes; + src += (size_t)(frames - s->target_ring_frames) * (size_t)frame_bytes; frames = s->target_ring_frames; sync_add_l(&s->target_queue_overruns, 1); - sync_add_l(&s->target_queue_overflow_events, 1); - sync_add_l(&s->target_queue_dropped_frames, drop); } remaining = frames; @@ -2787,21 +2552,19 @@ static int sync_enqueue_target_audio(vj_audio_sync_shared_t *s, (long long)s->target_ring_frames) { long long old_read = s->target_read_frame_abs; - long long drop_frames; - s->target_read_frame_abs = s->target_write_frame_abs - (long long)s->target_ring_frames; if(s->target_process_frame_abs < s->target_read_frame_abs) s->target_process_frame_abs = s->target_read_frame_abs; - - drop_frames = s->target_read_frame_abs - old_read; - if(drop_frames < 0) - drop_frames = 0; - sync_add_l(&s->target_queue_overruns, 1); - sync_add_l(&s->target_queue_overflow_events, 1); - if(drop_frames > 0) - sync_add_l(&s->target_queue_dropped_frames, (long)drop_frames); + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][TARGET-Q] overflow drop=%lldfr write=%lld read:%lld->%lld process=%lld ring=%dfr", + s->target_read_frame_abs - old_read, + s->target_write_frame_abs, + old_read, + s->target_read_frame_abs, + s->target_process_frame_abs, + s->target_ring_frames); } sync_unlock(s); @@ -2838,28 +2601,14 @@ static int sync_dequeue_target_audio(vj_audio_sync_shared_t *s, return 0; } - - - retained_avail = s->target_write_frame_abs - s->target_read_frame_abs; if(retained_avail > (long long)s->target_ring_frames) { - long long old_read = s->target_read_frame_abs; - long long drop_frames; - s->target_read_frame_abs = s->target_write_frame_abs - (long long)s->target_ring_frames; if(s->target_process_frame_abs < s->target_read_frame_abs) s->target_process_frame_abs = s->target_read_frame_abs; retained_avail = s->target_write_frame_abs - s->target_read_frame_abs; - - drop_frames = s->target_read_frame_abs - old_read; - if(drop_frames < 0) - drop_frames = 0; - sync_add_l(&s->target_queue_overruns, 1); - sync_add_l(&s->target_queue_overflow_events, 1); - if(drop_frames > 0) - sync_add_l(&s->target_queue_dropped_frames, (long)drop_frames); } if(s->target_process_frame_abs < s->target_read_frame_abs) @@ -2913,6 +2662,45 @@ static int sync_dequeue_target_audio(vj_audio_sync_shared_t *s, *frame_bytes = s->target_bytes_per_frame; sync_add_l(&s->target_queue_reads, 1); + { + static long dbg_last_target_q_ms = 0; + static long dbg_last_target_q_drops = -1; + long now_dbg_ms = sync_now_ms(); + long long after_new_avail = s->target_write_frame_abs - s->target_process_frame_abs; + long long before_new_avail = after_new_avail + frames; + long long retained_after = s->target_write_frame_abs - s->target_read_frame_abs; + long reads_dbg = sync_load_l(&s->target_queue_reads); + long drops_dbg = sync_load_l(&s->target_queue_overruns); + int before_ms = 0; + int retained_ms = 0; + if(s->target_sample_rate > 0) { + before_ms = (int)((before_new_avail * 1000LL) / (long long)s->target_sample_rate); + retained_ms = (int)((retained_after * 1000LL) / (long long)s->target_sample_rate); + } + if(dbg_last_target_q_ms == 0 || + (now_dbg_ms - dbg_last_target_q_ms) >= 1000 || + retained_ms < VJ_AUDIO_SYNC_TARGET_MIN_HISTORY_MS || + retained_ms > VJ_AUDIO_SYNC_TARGET_WANTED_HISTORY_MS || + drops_dbg != dbg_last_target_q_drops) + { + veejay_msg(VEEJAY_MSG_DEBUG, + "[TRACK-ALIGN][TARGET-Q] processed=%d new_before=%lldfr/%dms new_after=%lldfr retained=%lldfr/%dms ring=%dfr reads=%ld drops=%ld fmt=%dch/%dbit/%dHz", + frames, + before_new_avail, + before_ms, + after_new_avail, + retained_after, + retained_ms, + s->target_ring_frames, + reads_dbg, + drops_dbg, + s->target_channels, + s->target_bits_per_channel, + s->target_sample_rate); + dbg_last_target_q_ms = now_dbg_ms; + dbg_last_target_q_drops = drops_dbg; + } + } sync_unlock(s); return frames; @@ -2940,23 +2728,22 @@ static void sync_update_clock_from_block(vj_audio_sync_shared_t *s, if(!s || !data || frames <= 0 || frame_bytes <= 0 || channels <= 0) return; - if(channels == 1) { - for(int i = 0; i < frames; i++) { - const uint8_t *f = data + ((size_t)i * (size_t)frame_bytes); - double m = (double)sync_read_sample(f, sample_bytes) * (1.0 / 32768.0); - sum += m * m; + for(int i = 0; i < frames; i++) { + const uint8_t *f = data + ((size_t)i * (size_t)frame_bytes); + int l, r; + if(channels == 1) { + l = r = sync_read_sample(f, sample_bytes); + } else { + l = sync_read_sample(f, sample_bytes); + r = sync_read_sample(f + sample_bytes, sample_bytes); } - } else { - for(int i = 0; i < frames; i++) { - const uint8_t *f = data + ((size_t)i * (size_t)frame_bytes); - int l = sync_read_sample(f, sample_bytes); - int r = sync_read_sample(f + sample_bytes, sample_bytes); + { double m = ((double)l + (double)r) * (1.0 / 65536.0); sum += m * m; } } - level = sqrtf((float)(sum / (double)frames)); + level = sqrt(sum / (double)frames); s->envelope = (level > s->envelope) ? (0.65 * s->envelope + 0.35 * level) : (0.94 * s->envelope + 0.06 * level); @@ -3043,23 +2830,22 @@ static void sync_update_target_clock_from_block(vj_audio_sync_shared_t *s, if(!s || !data || frames <= 0 || frame_bytes <= 0 || channels <= 0) return; - if(channels == 1) { - for(int i = 0; i < frames; i++) { - const uint8_t *f = data + ((size_t)i * (size_t)frame_bytes); - double m = (double)sync_read_sample(f, sample_bytes) * (1.0 / 32768.0); - sum += m * m; + for(int i = 0; i < frames; i++) { + const uint8_t *f = data + ((size_t)i * (size_t)frame_bytes); + int l, r; + if(channels == 1) { + l = r = sync_read_sample(f, sample_bytes); + } else { + l = sync_read_sample(f, sample_bytes); + r = sync_read_sample(f + sample_bytes, sample_bytes); } - } else { - for(int i = 0; i < frames; i++) { - const uint8_t *f = data + ((size_t)i * (size_t)frame_bytes); - int l = sync_read_sample(f, sample_bytes); - int r = sync_read_sample(f + sample_bytes, sample_bytes); + { double m = ((double)l + (double)r) * (1.0 / 65536.0); sum += m * m; } } - level = sqrtf((float)(sum / (double)frames)); + level = sqrt(sum / (double)frames); s->target_envelope = (level > s->target_envelope) ? (0.80 * s->target_envelope + 0.20 * level) @@ -3078,10 +2864,15 @@ static void sync_update_target_clock_from_block(vj_audio_sync_shared_t *s, last_hit = s->target_last_hit_ms; + double min_level = 0.020; + double min_transient = 0.32; + if(sync_load_i(&s->mode) == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW) { + min_level = 0.008; + min_transient = 0.16; + } - - if(level > 0.020 && transient > 0.32) { + if(level > min_level && transient > min_transient) { long interval = last_hit > 0 ? now - last_hit : 0; int accept = 0; @@ -3102,7 +2893,6 @@ static void sync_update_target_clock_from_block(vj_audio_sync_shared_t *s, } else { double ratio = candidate / s->target_beat_period_ms; - if(ratio < 0.58) candidate *= 2.0; else if(ratio > 1.72) @@ -3137,7 +2927,7 @@ static void sync_update_target_clock_from_block(vj_audio_sync_shared_t *s, sync_store_i(&s->target_phase_q15, sync_q15(phase)); confidence = sync_from_q15(sync_load_i(&s->target_confidence_q15)); - confidence += (transient > 0.28) ? 0.006 : 0.0005; + confidence += (transient > (min_transient * 0.875)) ? 0.006 : 0.0005; confidence *= 0.9985; sync_store_i(&s->target_confidence_q15, sync_q15(confidence)); } else { @@ -3186,10 +2976,7 @@ static int sync_configure_jack(vj_audio_sync_shared_t *s) else if(!vj_jack_has_input()) { long now = sync_now_ms(); - if(vj_jack_init_capture(req_ch, 16, 0)) { - vj_jack_enable(); - } - else if(!vj_jack_has_output()) { + if(!vj_jack_has_output()) { vj_jack_stop(); vj_jack_initialize(); if(!vj_jack_init_capture(req_ch, 16, 0)) { @@ -3207,7 +2994,7 @@ static int sync_configure_jack(vj_audio_sync_shared_t *s) if(last_fail_ms == 0 || (now - last_fail_ms) >= 2000) { last_fail_ms = now; veejay_msg(VEEJAY_MSG_WARNING, - "[AUDIO-SYNC] JACK playback client has no capture ports and adding capture ports failed"); + "[AUDIO-SYNC] JACK is open for playback but has no capture ports; start JACK in duplex/capture mode"); } sync_store_i(&s->open, 0); return 0; @@ -3255,12 +3042,6 @@ typedef struct long data_start; long data_bytes; long data_pos; - - - - - long limit_bytes; - long virtual_pos; } sync_wav_t; static uint32_t wav_u32le(const uint8_t b[4]) @@ -3281,6 +3062,7 @@ static void sync_wav_close(sync_wav_t *w) w->fp = NULL; } } + static int sync_wav_open(sync_wav_t *w, const char *path) { uint8_t hdr[12]; @@ -3290,145 +3072,183 @@ static int sync_wav_open(sync_wav_t *w, const char *path) uint16_t channels = 0; uint16_t bits = 0; uint32_t sample_rate = 0; - char why[192]; - why[0] = '\0'; - -#define WAV_FAIL(...) do { \ - snprintf(why, sizeof(why), __VA_ARGS__); \ - goto fail; \ - } while(0) - - if(!w) + if(!w || !path || !path[0]) return 0; - if(!path || !path[0]) - WAV_FAIL("empty path"); - memset(w, 0, sizeof(*w)); - w->fp = fopen(path, "rb"); if(!w->fp) - WAV_FAIL("fopen failed: %s", strerror(errno)); + return 0; if(fread(hdr, 1, sizeof(hdr), w->fp) != sizeof(hdr)) - WAV_FAIL("short RIFF/WAVE header"); + goto fail; if(memcmp(hdr, "RIFF", 4) != 0 || memcmp(hdr + 8, "WAVE", 4) != 0) - WAV_FAIL("not a RIFF/WAVE file"); + goto fail; while(!got_data) { uint8_t chdr[8]; uint32_t sz; - long here; long next; if(fread(chdr, 1, sizeof(chdr), w->fp) != sizeof(chdr)) break; sz = wav_u32le(chdr + 4); - here = ftell(w->fp); - if(here < 0) - WAV_FAIL("ftell failed while reading chunk header"); - - next = here + (long)sz + (long)(sz & 1U); + next = ftell(w->fp) + (long)sz + (long)(sz & 1U); if(memcmp(chdr, "fmt ", 4) == 0) { uint8_t fmt[40]; size_t n = sz < sizeof(fmt) ? sz : sizeof(fmt); - - if(n < 16) - WAV_FAIL("fmt chunk too small: %u bytes", (unsigned)sz); - if(fread(fmt, 1, n, w->fp) != n) - WAV_FAIL("short fmt chunk"); - - audio_format = wav_u16le(fmt + 0); - channels = wav_u16le(fmt + 2); - sample_rate = wav_u32le(fmt + 4); - bits = wav_u16le(fmt + 14); - got_fmt = 1; + goto fail; + if(n >= 16) { + audio_format = wav_u16le(fmt + 0); + channels = wav_u16le(fmt + 2); + sample_rate = wav_u32le(fmt + 4); + bits = wav_u16le(fmt + 14); + got_fmt = 1; + } } else if(memcmp(chdr, "data", 4) == 0) { if(!got_fmt) - WAV_FAIL("data chunk appeared before fmt chunk"); - + goto fail; w->data_start = ftell(w->fp); - if(w->data_start < 0) - WAV_FAIL("ftell failed at data chunk"); - w->data_bytes = (long)sz; w->data_pos = 0; got_data = 1; } if(fseek(w->fp, next, SEEK_SET) != 0) - WAV_FAIL("failed to seek to next WAV chunk"); + break; } - if(!got_fmt) - WAV_FAIL("missing fmt chunk"); - - if(!got_data) - WAV_FAIL("missing data chunk"); - - if(audio_format != 1) - WAV_FAIL("unsupported WAV format=%u, only PCM format=1 is supported", - (unsigned)audio_format); - - if(channels < 1 || channels > 2) - WAV_FAIL("unsupported channel count=%u, expected mono/stereo", - (unsigned)channels); - - if(bits != 16) { - - - - WAV_FAIL("unsupported bit depth=%u, expected 16-bit PCM", - (unsigned)bits); - } - - if(sample_rate == 0) - WAV_FAIL("invalid sample rate=0"); + if(!got_fmt || !got_data) + goto fail; + if(audio_format != 1 || (channels < 1 || channels > 2) || + (bits != 8 && bits != 16) || sample_rate == 0) + goto fail; w->channels = channels; w->bits = bits; w->sample_rate = (int)sample_rate; w->frame_bytes = (bits / 8) * channels; - w->limit_bytes = 0; - w->virtual_pos = 0; - - if(w->frame_bytes <= 0) - WAV_FAIL("invalid frame size"); - - if(fseek(w->fp, w->data_start, SEEK_SET) != 0) - WAV_FAIL("failed to seek to WAV data start"); - - veejay_msg(VEEJAY_MSG_INFO, - "[AUDIO-SYNC][WAV] validated '%s': %dch %dHz %dbit data=%ld bytes frame_bytes=%d", - path, - w->channels, - w->sample_rate, - w->bits, - w->data_bytes, - w->frame_bytes); - -#undef WAV_FAIL + fseek(w->fp, w->data_start, SEEK_SET); return 1; fail: - veejay_msg(VEEJAY_MSG_WARNING, - "[AUDIO-SYNC][WAV] rejected '%s': %s", - path ? path : "(null)", - why[0] ? why : "unknown error"); sync_wav_close(w); -#undef WAV_FAIL return 0; } + + +#ifndef VJ_AUDIO_SYNC_WAV_SIDE_SLOTS +#define VJ_AUDIO_SYNC_WAV_SIDE_SLOTS 8 +#endif + +typedef struct { + vj_audio_sync_shared_t *owner; + int wav_limit_ms; + int plain_lock_valid; + int plain_lock_delta_frames; + int plain_lock_confidence_pct; +} sync_wav_side_t; + +static sync_wav_side_t sync_wav_side_[VJ_AUDIO_SYNC_WAV_SIDE_SLOTS]; +static volatile int sync_wav_side_lock_ = 0; + +static void sync_wav_side_lock(void) +{ + while(__sync_lock_test_and_set(&sync_wav_side_lock_, 1)) + sync_sleep_us(50); +} + +static void sync_wav_side_unlock(void) +{ + __sync_lock_release(&sync_wav_side_lock_); +} + +static sync_wav_side_t *sync_wav_side_get_locked(vj_audio_sync_shared_t *s, int create) +{ + int i; + int free_slot = -1; + + if(!s) + return NULL; + + for(i = 0; i < VJ_AUDIO_SYNC_WAV_SIDE_SLOTS; i++) { + if(sync_wav_side_[i].owner == s) + return &sync_wav_side_[i]; + if(!sync_wav_side_[i].owner && free_slot < 0) + free_slot = i; + } + + if(create && free_slot >= 0) { + memset(&sync_wav_side_[free_slot], 0, sizeof(sync_wav_side_[free_slot])); + sync_wav_side_[free_slot].owner = s; + return &sync_wav_side_[free_slot]; + } + + return NULL; +} + +static void sync_wav_side_clear(vj_audio_sync_shared_t *s) +{ + sync_wav_side_t *side; + + sync_wav_side_lock(); + side = sync_wav_side_get_locked(s, 0); + if(side) + memset(side, 0, sizeof(*side)); + sync_wav_side_unlock(); +} + +static void sync_wav_side_set_limit(vj_audio_sync_shared_t *s, int limit_ms) +{ + sync_wav_side_t *side; + + if(limit_ms < 0) + limit_ms = 0; + + sync_wav_side_lock(); + side = sync_wav_side_get_locked(s, 1); + if(side) + side->wav_limit_ms = limit_ms; + sync_wav_side_unlock(); +} + +static int sync_wav_side_get_limit(vj_audio_sync_shared_t *s) +{ + sync_wav_side_t *side; + int limit_ms = 0; + + sync_wav_side_lock(); + side = sync_wav_side_get_locked(s, 0); + if(side) + limit_ms = side->wav_limit_ms; + sync_wav_side_unlock(); + + return limit_ms; +} + +static void sync_wav_plain_lock_clear(vj_audio_sync_shared_t *s) +{ + sync_wav_side_t *side; + + sync_wav_side_lock(); + side = sync_wav_side_get_locked(s, 0); + if(side) { + side->plain_lock_valid = 0; + side->plain_lock_delta_frames = 0; + side->plain_lock_confidence_pct = 0; + } + sync_wav_side_unlock(); +} + static int sync_wav_read(sync_wav_t *w, uint8_t *dst, int max_bytes, int loop) { int done = 0; - long logical_bytes; if(!w || !w->fp || !dst || max_bytes <= 0 || w->frame_bytes <= 0) return 0; @@ -3437,54 +3257,17 @@ static int sync_wav_read(sync_wav_t *w, uint8_t *dst, int max_bytes, int loop) if(max_bytes <= 0) return 0; - logical_bytes = (w->limit_bytes > 0) ? w->limit_bytes : w->data_bytes; - logical_bytes -= logical_bytes % w->frame_bytes; - - if(logical_bytes <= 0) - return 0; - while(done < max_bytes) { - long left; + long left = w->data_bytes - w->data_pos; int want; int got; - if(!loop && w->virtual_pos >= logical_bytes) { - - - - memset(dst + done, 0, (size_t)(max_bytes - done)); - done = max_bytes; - break; - } - - if(loop && w->data_pos >= w->data_bytes) { - w->data_pos = 0; - w->virtual_pos = 0; - if(fseek(w->fp, w->data_start, SEEK_SET) != 0) - break; - } - - left = w->data_bytes - w->data_pos; - if(!loop) { - long logical_left = logical_bytes - w->virtual_pos; - if(logical_left < left) - left = logical_left; - } - if(left <= 0) { - if(loop) { - w->data_pos = 0; - w->virtual_pos = 0; - if(fseek(w->fp, w->data_start, SEEK_SET) != 0) - break; - continue; - } - - - memset(dst + done, 0, (size_t)(max_bytes - done)); - w->virtual_pos += (long)(max_bytes - done); - done = max_bytes; - break; + if(!loop) + break; + w->data_pos = 0; + fseek(w->fp, w->data_start, SEEK_SET); + left = w->data_bytes; } want = max_bytes - done; @@ -3495,21 +3278,12 @@ static int sync_wav_read(sync_wav_t *w, uint8_t *dst, int max_bytes, int loop) break; got = (int)fread(dst + done, 1, (size_t)want, w->fp); - if(got <= 0) { - if(loop) - break; - - memset(dst + done, 0, (size_t)want); - got = want; - } - - got -= got % w->frame_bytes; if(got <= 0) break; + got -= got % w->frame_bytes; done += got; w->data_pos += got; - w->virtual_pos += got; } return done; @@ -3550,17 +3324,13 @@ static int sync_read_cursor_audio(vj_audio_sync_shared_t *s, if(!s || !dst || !cursor_ptr || max_bytes <= 0) return 0; - { - int enabled = sync_load_i(&s->enabled); - int open = sync_load_i(&s->open); - if(!enabled || !open) - return 0; - } + if(!sync_load_i(&s->enabled) || !sync_load_i(&s->open)) + return 0; if(!sync_try_lock(s)) return 0; - frame_bytes = s->bytes_per_frame; + frame_bytes = sync_load_i(&s->bytes_per_frame); if(!s->ring || s->ring_frames <= 0 || frame_bytes <= 0) { sync_unlock(s); return 0; @@ -3578,9 +3348,6 @@ static int sync_read_cursor_audio(vj_audio_sync_shared_t *s, cursor = *cursor_ptr; - - - if(cursor <= 0 || cursor < oldest) { long long live_start = s->write_frame_abs - (long long)(max_frames * 2); if(live_start < oldest) @@ -3661,12 +3428,8 @@ int vj_audio_sync_copy_record_audio(vj_audio_sync_shared_t *s, if(!s || !dst || dst_frames <= 0 || dst_frame_bytes <= 0 || dst_channels <= 0 || dst_channels > 2 || dst_sample_rate <= 0) return 0; - { - int enabled = sync_load_i(&s->enabled); - int open = sync_load_i(&s->open); - if(!enabled || !open) - return 0; - } + if(!sync_load_i(&s->enabled) || !sync_load_i(&s->open)) + return 0; if(!sync_try_lock(s)) return 0; @@ -3836,80 +3599,8 @@ static double sync_fold_bpm_near(double bpm, double ref) return bpm; } -static double sync_fold_bpm_octave_near(double bpm, double ref) -{ - if(bpm <= 0.0 || ref <= 0.0) - return bpm; - while(bpm < ref * 0.64 && bpm * 2.0 <= VJ_AUDIO_SYNC_MAX_BPM) - bpm *= 2.0; - while(bpm > ref * 1.56 && bpm * 0.5 >= VJ_AUDIO_SYNC_MIN_BPM) - bpm *= 0.5; - - return bpm; -} - -static double sync_bpm_ratio_distance(double a, double b) -{ - double r; - - if(a <= 0.0 || b <= 0.0) - return 999.0; - - r = a / b; - if(r < 1.0) - r = 1.0 / r; - - return r; -} - -static void sync_bridge_normalize_tempo_pair(double *source_bpm, - double *target_bpm, - int target_mode) -{ - double source; - double target; - double folded_source; - double old_dist; - double new_dist; - double ratio; - - if(!source_bpm || !target_bpm) - return; - - source = *source_bpm; - target = *target_bpm; - - if(source <= 0.0 || target <= 0.0) - return; - - ratio = target / source; - if(ratio > 0.64 && ratio < 1.56) - return; - - folded_source = sync_fold_bpm_octave_near(source, target); - old_dist = sync_bpm_ratio_distance(target, source); - new_dist = sync_bpm_ratio_distance(target, folded_source); - - if(new_dist < old_dist) - source = folded_source; - - if(target_mode != VJ_AUDIO_SYNC_TARGET_MANUAL) { - double folded_target = sync_fold_bpm_octave_near(target, source); - double target_dist = sync_bpm_ratio_distance(folded_target, source); - - if(target_dist + 0.025 < sync_bpm_ratio_distance(target, source)) - target = folded_target; - } - - *source_bpm = source; - *target_bpm = target; -} - - -static double sync_bridge_update_controller_locked(vj_audio_sync_shared_t *s, - long now_ms, - int *state_out) +static void sync_tempo_follow_publish(vj_audio_sync_shared_t *s) { int source_bpm_q8; int target_bpm_q8; @@ -3921,19 +3612,29 @@ static double sync_bridge_update_controller_locked(vj_audio_sync_shared_t *s, double target_bpm; double source_confidence; double target_confidence; - double tempo_ratio = 1.0; - double controller_correction = 1.0; double max_corr; double live_pull; double stale_pull_scale = 1.0; - double correction_alpha; + double desired_ratio = 1.0; + double ratio; + long now_ms; long latch_age_ms = 0; - if(state_out) - *state_out = VJ_AUDIO_SYNC_BRIDGE_STATE_IDLE; - if(!s) - return 1.0; + return; + + if(!sync_load_i(&s->enabled) || + sync_load_i(&s->mode) != VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW) + return; + + now_ms = sync_now_ms(); + + sync_lock(s); + if(!sync_load_i(&s->enabled) || + sync_load_i(&s->mode) != VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW) { + sync_unlock(s); + return; + } max_corr = (double)sync_load_i(&s->max_correction_pct) / 100.0; if(max_corr < 0.0) max_corr = 0.0; @@ -3963,11 +3664,234 @@ static double sync_bridge_update_controller_locked(vj_audio_sync_shared_t *s, if(have_source_clock && have_target_clock) { const double src_alpha = - (target_mode == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) ? 0.006 : 0.012; + (target_mode == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) ? 0.010 : 0.020; const double dst_alpha = - (target_mode == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) ? 0.004 : 0.035; - - sync_bridge_normalize_tempo_pair(&source_bpm, &target_bpm, target_mode); + (target_mode == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) ? 0.008 : 0.030; + + if(!s->bridge_bpm_latch_valid || + s->bridge_source_bpm_latched <= 0.0 || + s->bridge_target_bpm_latched <= 0.0) + { + s->bridge_source_bpm_latched = source_bpm; + s->bridge_target_bpm_latched = target_bpm; + s->bridge_bpm_latch_valid = 1; + } + else + { + double folded_source = sync_fold_bpm_near(source_bpm, + s->bridge_source_bpm_latched); + double folded_target = sync_fold_bpm_near(target_bpm, + s->bridge_target_bpm_latched); + double src_rel = fabs(folded_source - s->bridge_source_bpm_latched) / + ((s->bridge_source_bpm_latched > 1.0) ? s->bridge_source_bpm_latched : 1.0); + double dst_rel = fabs(folded_target - s->bridge_target_bpm_latched) / + ((s->bridge_target_bpm_latched > 1.0) ? s->bridge_target_bpm_latched : 1.0); + + if(src_rel < 0.18) + s->bridge_source_bpm_latched += + src_alpha * (folded_source - s->bridge_source_bpm_latched); + + if(dst_rel < 0.22 || target_mode == VJ_AUDIO_SYNC_TARGET_MANUAL) + s->bridge_target_bpm_latched += + dst_alpha * (folded_target - s->bridge_target_bpm_latched); + } + + s->bridge_latch_updated_ms = now_ms; + source_bpm = s->bridge_source_bpm_latched; + target_bpm = s->bridge_target_bpm_latched; + bridge_state = VJ_AUDIO_SYNC_BRIDGE_STATE_LOCKED; + } + else if(s->bridge_bpm_latch_valid && + s->bridge_source_bpm_latched > 0.0 && + s->bridge_target_bpm_latched > 0.0) + { + latch_age_ms = now_ms - s->bridge_latch_updated_ms; + if(latch_age_ms < 0) + latch_age_ms = 0; + + if(latch_age_ms <= 8000L) { + source_bpm = s->bridge_source_bpm_latched; + target_bpm = s->bridge_target_bpm_latched; + bridge_state = VJ_AUDIO_SYNC_BRIDGE_STATE_HOLD; + + if(latch_age_ms > 3000L) { + stale_pull_scale = 1.0 - ((double)(latch_age_ms - 3000L) / 5000.0); + if(stale_pull_scale < 0.0) + stale_pull_scale = 0.0; + } + } else { + s->bridge_source_bpm_latched = 0.0; + s->bridge_target_bpm_latched = 0.0; + s->bridge_bpm_latch_valid = 0; + s->bridge_latch_updated_ms = 0; + source_bpm = 0.0; + target_bpm = 0.0; + bridge_state = VJ_AUDIO_SYNC_BRIDGE_STATE_FALLBACK; + } + } + else + { + source_bpm = 0.0; + target_bpm = 0.0; + bridge_state = have_source_clock ? + VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_TARGET : + VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_SOURCE; + } + + live_pull = max_corr; + if(live_pull <= 0.0001) + live_pull = 0.0; + live_pull *= stale_pull_scale; + + if(live_pull > 0.0 && source_bpm > 0.0 && target_bpm > 0.0) { + desired_ratio = target_bpm / source_bpm; + desired_ratio = sync_clampd(desired_ratio, + 1.0 - live_pull, + 1.0 + live_pull); + } + + if(desired_ratio < 0.25) desired_ratio = 0.25; + if(desired_ratio > 4.0) desired_ratio = 4.0; + + if(s->bridge_ratio_smooth <= 0.0) + ratio = desired_ratio; + else { + const double ratio_alpha = + (target_mode == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) ? 0.012 : 0.025; + ratio = s->bridge_ratio_smooth + ratio_alpha * (desired_ratio - s->bridge_ratio_smooth); + } + + if(ratio < 0.25) ratio = 0.25; + if(ratio > 4.0) ratio = 4.0; + + s->bridge_ratio_smooth = ratio; + s->bridge_last_correction = ratio; + sync_store_i(&s->bridge_active, + (bridge_state == VJ_AUDIO_SYNC_BRIDGE_STATE_LOCKED || + bridge_state == VJ_AUDIO_SYNC_BRIDGE_STATE_HOLD) ? 1 : 0); + sync_store_i(&s->bridge_state, bridge_state); + sync_unlock(s); +} + + +int vj_audio_sync_render_bridge_s16(vj_audio_sync_shared_t *s, + uint8_t *dst, + int dst_frames, + int dst_frame_bytes, + int dst_channels, + int dst_sample_rate) +{ + int src_rate; + int src_channels; + int src_frame_bytes; + int src_sample_bytes; + int dst_sample_bytes; + int source_bpm_q8; + int target_bpm_q8; + int target_mode; + int have_source_clock; + int have_target_clock; + int bridge_state = VJ_AUDIO_SYNC_BRIDGE_STATE_IDLE; + double source_bpm; + double target_bpm; + double source_confidence; + double target_confidence; + double rate_ratio; + double desired_ratio; + double tempo_ratio = 1.0; + double ratio; + double max_corr; + double live_pull; + double stale_pull_scale = 1.0; + double pull_amount; + double read_pos; + long now_ms; + long latch_age_ms = 0; + long long oldest; + long long newest; + long long latency_frames; + long long guard_frames; + long long latency_ms; + long long target_read; + + if(!s || !dst || dst_frames <= 0 || dst_frame_bytes <= 0 || + dst_channels <= 0 || dst_channels > 2 || dst_sample_rate <= 0) + return 0; + + if(!sync_load_i(&s->enabled) || + !sync_load_i(&s->open) || + sync_load_i(&s->mode) != VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE) + return 0; + + if(!sync_try_lock(s)) + return 0; + + if(!s->ring || s->ring_frames <= 8 || s->channels <= 0 || + s->bytes_per_frame <= 0 || s->sample_rate <= 0) { + sync_store_i(&s->bridge_active, 0); + sync_store_i(&s->bridge_state, VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_SOURCE); + sync_unlock(s); + return 0; + } + + src_rate = s->sample_rate; + src_channels = s->channels; + src_frame_bytes = s->bytes_per_frame; + src_sample_bytes = src_frame_bytes / src_channels; + dst_sample_bytes = dst_frame_bytes / dst_channels; + + if(src_sample_bytes != 2 || dst_sample_bytes != 2) { + sync_store_i(&s->bridge_active, 0); + sync_store_i(&s->bridge_state, VJ_AUDIO_SYNC_BRIDGE_STATE_FALLBACK); + sync_unlock(s); + return 0; + } + + oldest = s->write_frame_abs - (long long)s->ring_frames; + if(oldest < 0) + oldest = 0; + newest = s->write_frame_abs - 2; + if(newest <= oldest + 4) { + sync_add_l(&s->underruns, 1); + sync_store_i(&s->bridge_active, 0); + sync_store_i(&s->bridge_state, VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_SOURCE); + sync_unlock(s); + return 0; + } + + now_ms = sync_now_ms(); + + max_corr = (double)sync_load_i(&s->max_correction_pct) / 100.0; + if(max_corr < 0.0) max_corr = 0.0; + if(max_corr > 0.25) max_corr = 0.25; + + source_bpm_q8 = sync_load_i(&s->source_bpm_q8); + target_bpm_q8 = sync_load_i(&s->target_bpm_q8); + target_mode = sync_load_i(&s->target_mode); + source_confidence = sync_from_q15(sync_load_i(&s->confidence_q15)); + target_confidence = sync_from_q15(sync_load_i(&s->target_confidence_q15)); + source_bpm = sync_bpm_from_q8(source_bpm_q8); + target_bpm = sync_bpm_from_q8(target_bpm_q8); + + if(source_bpm < VJ_AUDIO_SYNC_MIN_BPM || source_bpm > VJ_AUDIO_SYNC_MAX_BPM) + source_bpm = 0.0; + if(source_confidence < 0.38) + source_bpm = 0.0; + + if(target_bpm < VJ_AUDIO_SYNC_MIN_BPM || target_bpm > VJ_AUDIO_SYNC_MAX_BPM) + target_bpm = 0.0; + + if(target_mode == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP && target_confidence < 0.68) + target_bpm = 0.0; + + have_source_clock = (source_bpm > 0.0); + have_target_clock = (target_bpm > 0.0); + + if(have_source_clock && have_target_clock) { + const double src_alpha = + (target_mode == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) ? 0.006 : 0.012; + const double dst_alpha = + (target_mode == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) ? 0.004 : 0.035; if(!s->bridge_bpm_latch_valid || s->bridge_source_bpm_latched <= 0.0 || @@ -4024,7 +3948,6 @@ static double sync_bridge_update_controller_locked(vj_audio_sync_shared_t *s, s->bridge_source_bpm_latched = 0.0; s->bridge_target_bpm_latched = 0.0; s->bridge_bpm_latch_valid = 0; - sync_bridge_clear_stretch_locked(s); s->bridge_latch_updated_ms = 0; source_bpm = 0.0; target_bpm = 0.0; @@ -4046,96 +3969,133 @@ static double sync_bridge_update_controller_locked(vj_audio_sync_shared_t *s, else if(target_mode == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) { if(live_pull > 0.035) live_pull = 0.035; - } else { - if(live_pull > 0.080) - live_pull = 0.080; } live_pull *= stale_pull_scale; - if((bridge_state == VJ_AUDIO_SYNC_BRIDGE_STATE_LOCKED || - bridge_state == VJ_AUDIO_SYNC_BRIDGE_STATE_HOLD) && - live_pull > 0.0 && source_bpm > 0.0 && target_bpm > 0.0) - { + rate_ratio = (double)src_rate / (double)dst_sample_rate; + desired_ratio = rate_ratio; + + if(live_pull > 0.0 && source_bpm > 0.0 && target_bpm > 0.0) { tempo_ratio = target_bpm / source_bpm; - controller_correction = sync_clampd(tempo_ratio, - 1.0 - live_pull, - 1.0 + live_pull); - - correction_alpha = - (target_mode == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) ? 0.010 : 0.035; - if(stale_pull_scale < 1.0) - correction_alpha *= stale_pull_scale; - if(correction_alpha < 0.002) - correction_alpha = 0.002; - - if(s->bridge_last_correction <= 0.0) - s->bridge_last_correction = controller_correction; - else - s->bridge_last_correction += - correction_alpha * (controller_correction - s->bridge_last_correction); - - controller_correction = s->bridge_last_correction; - if(controller_correction < 0.25) controller_correction = 0.25; - if(controller_correction > 4.0) controller_correction = 4.0; + tempo_ratio = sync_clampd(tempo_ratio, + 1.0 - live_pull, + 1.0 + live_pull); + desired_ratio = rate_ratio * tempo_ratio; } - else + + if(desired_ratio < 0.25) desired_ratio = 0.25; + if(desired_ratio > 4.0) desired_ratio = 4.0; + + if(s->bridge_ratio_smooth <= 0.0) + ratio = desired_ratio; + else { + const double ratio_alpha = + (target_mode == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) ? 0.0060 : 0.1800; + ratio = s->bridge_ratio_smooth + ratio_alpha * (desired_ratio - s->bridge_ratio_smooth); + } + + if(ratio < 0.25) ratio = 0.25; + if(ratio > 4.0) ratio = 4.0; + + pull_amount = fabs(tempo_ratio - 1.0) * stale_pull_scale; + + latency_ms = VJ_AUDIO_SYNC_BRIDGE_LATENCY_MS + (long long)(pull_amount * 4500.0); + if(latency_ms < VJ_AUDIO_SYNC_BRIDGE_LATENCY_MS) + latency_ms = VJ_AUDIO_SYNC_BRIDGE_LATENCY_MS; + if(latency_ms > VJ_AUDIO_SYNC_BRIDGE_MAX_LATENCY_MS) + latency_ms = VJ_AUDIO_SYNC_BRIDGE_MAX_LATENCY_MS; + + latency_frames = ((long long)src_rate * latency_ms) / 1000LL; + if(latency_frames < (long long)dst_frames * 4) + latency_frames = (long long)dst_frames * 4; + if(latency_frames < 16) + latency_frames = 16; + if(latency_frames > (long long)s->ring_frames / 2) + latency_frames = (long long)s->ring_frames / 2; + + guard_frames = ((long long)src_rate * 35LL) / 1000LL; + if(guard_frames < (long long)dst_frames * 2) + guard_frames = (long long)dst_frames * 2; + if(guard_frames > latency_frames / 2) + guard_frames = latency_frames / 2; + if(guard_frames < 8) + guard_frames = 8; + + target_read = s->write_frame_abs - latency_frames; + if(target_read < oldest + guard_frames) + target_read = oldest + guard_frames; + if(target_read > newest - guard_frames) + target_read = newest - guard_frames; + + if(!s->bridge_read_valid) { + s->bridge_read_pos = (double)target_read; + s->bridge_read_valid = 1; + } + + read_pos = s->bridge_read_pos; + + if(read_pos < (double)(oldest + guard_frames) || + read_pos > (double)(newest - guard_frames)) { + read_pos = (double)target_read; + sync_add_l(&s->underruns, 1); + } + { - controller_correction = 1.0; + double max_end = (double)(newest - guard_frames); + double min_end = (double)(oldest + guard_frames); + double max_ratio = (max_end - read_pos) / (double)dst_frames; + double min_ratio = (min_end - read_pos) / (double)dst_frames; + + if(max_ratio < 0.25) + max_ratio = 0.25; + if(min_ratio < 0.25) + min_ratio = 0.25; + + if(ratio > max_ratio) { + ratio = max_ratio; + sync_add_l(&s->underruns, 1); + } + + if(read_pos + ((double)dst_frames * ratio) < min_end) { + double catchup_ratio = ((double)target_read - read_pos) / (double)dst_frames; + if(catchup_ratio < 0.25) + catchup_ratio = 0.25; + if(catchup_ratio > 4.0) + catchup_ratio = 4.0; + if(catchup_ratio > ratio) + ratio = catchup_ratio; + } } - s->bridge_last_correction = controller_correction; + for(int i = 0; i < dst_frames; i++) { + uint8_t *df = dst + ((size_t)i * (size_t)dst_frame_bytes); + if(dst_channels == 1) { + int v = sync_sample_at_locked(s, read_pos, 0, 1); + sync_write_sample(df, 2, v); + } else { + int l = sync_sample_at_locked(s, read_pos, 0, 2); + int r = sync_sample_at_locked(s, read_pos, 1, 2); + sync_write_sample(df, 2, l); + sync_write_sample(df + 2, 2, r); + } + read_pos += ratio; + } + + s->bridge_read_pos = read_pos; + s->bridge_ratio_smooth = ratio; + s->bridge_last_correction = (rate_ratio > 0.0) ? (ratio / rate_ratio) : 1.0; + if(s->bridge_last_correction < 0.25) + s->bridge_last_correction = 0.25; + if(s->bridge_last_correction > 4.0) + s->bridge_last_correction = 4.0; + sync_store_i(&s->bridge_active, 1); sync_store_i(&s->bridge_state, bridge_state); - sync_store_i(&s->bridge_active, - (bridge_state == VJ_AUDIO_SYNC_BRIDGE_STATE_LOCKED || - bridge_state == VJ_AUDIO_SYNC_BRIDGE_STATE_HOLD) ? 1 : 0); - - if(state_out) - *state_out = bridge_state; - - return controller_correction; -} - -int vj_audio_sync_render_bridge_s16(vj_audio_sync_shared_t *s, - uint8_t *dst, - int dst_frames, - int dst_frame_bytes, - int dst_channels, - int dst_sample_rate) -{ - int bridge_state = VJ_AUDIO_SYNC_BRIDGE_STATE_IDLE; - - if(!s || !dst || dst_frames <= 0 || dst_frame_bytes <= 0 || - dst_channels <= 0 || dst_channels > 2 || dst_sample_rate <= 0) - return 0; - - { - int enabled = sync_load_i(&s->enabled); - int open = sync_load_i(&s->open); - int mode = sync_load_i(&s->mode); - if(!enabled || !open || mode != VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE) - return 0; - } - - if(sync_try_lock_for_us(s, 500)) { - (void)sync_bridge_update_controller_locked(s, - sync_now_ms(), - &bridge_state); - sync_store_i(&s->bridge_active, - (bridge_state == VJ_AUDIO_SYNC_BRIDGE_STATE_LOCKED || - bridge_state == VJ_AUDIO_SYNC_BRIDGE_STATE_HOLD) ? 1 : 0); - sync_unlock(s); - } - - return vj_audio_sync_render_monitor_s16(s, - dst, - dst_frames, - dst_frame_bytes, - dst_channels, - dst_sample_rate); + sync_unlock(s); + return dst_frames; } #ifndef VJ_AUDIO_SYNC_MONITOR_LATENCY_MS -#define VJ_AUDIO_SYNC_MONITOR_LATENCY_MS 180 +#define VJ_AUDIO_SYNC_MONITOR_LATENCY_MS 70 #endif int vj_audio_sync_render_monitor_s16(vj_audio_sync_shared_t *s, @@ -4150,7 +4110,6 @@ int vj_audio_sync_render_monitor_s16(vj_audio_sync_shared_t *s, int src_frame_bytes; int src_sample_bytes; int dst_sample_bytes; - int mode; double ratio; double read_pos; long long oldest; @@ -4166,19 +4125,16 @@ int vj_audio_sync_render_monitor_s16(vj_audio_sync_shared_t *s, return 0; { - int enabled = sync_load_i(&s->enabled); - int open = sync_load_i(&s->open); - mode = sync_load_i(&s->mode); - if(!enabled || !open || + int mode = sync_load_i(&s->mode); + if(!sync_load_i(&s->enabled) || + !sync_load_i(&s->open) || (mode != VJ_AUDIO_SYNC_MODE_MONITOR && - mode != VJ_AUDIO_SYNC_MODE_TRACK_ALIGN && - mode != VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE)) + mode != VJ_AUDIO_SYNC_MODE_MONITOR_TRICKPLAY && + mode != VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE && + mode != VJ_AUDIO_SYNC_MODE_TRACK_ALIGN)) return 0; } - - - if(!sync_try_lock_for_us(s, 3000)) { sync_add_l(&s->underruns, 1); sync_monitor_hold_last(s, dst, dst_frames, dst_frame_bytes, dst_channels); @@ -4230,12 +4186,12 @@ int vj_audio_sync_render_monitor_s16(vj_audio_sync_shared_t *s, ratio = (double)src_rate / (double)dst_sample_rate; - - - { long long block_src_frames = (long long)(((double)dst_frames * ratio) + 0.999); - long long min_latency = (block_src_frames * 8) + 1024; + long long min_latency = block_src_frames + 512; + + if(min_latency < 1024) + min_latency = 1024; latency_frames = ((long long)src_rate * VJ_AUDIO_SYNC_MONITOR_LATENCY_MS) / 1000LL; if(latency_frames < min_latency) @@ -4287,9 +4243,6 @@ int vj_audio_sync_render_monitor_s16(vj_audio_sync_shared_t *s, sync_add_l(&s->underruns, 1); } else { - - - double err = target - read_pos; double pull = err / ((double)latency_frames * 12.0); if(pull > 0.0025) @@ -4313,9 +4266,6 @@ int vj_audio_sync_render_monitor_s16(vj_audio_sync_shared_t *s, } if(resync && have_old_last && i < 128) { - - - int a = i + 1; int b = 128 - i; l = ((old_last_l * b) + (l * a)) >> 7; @@ -4337,13 +4287,7 @@ int vj_audio_sync_render_monitor_s16(vj_audio_sync_shared_t *s, } s->monitor_read_pos = read_pos; - - - - - if(mode != VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE) - sync_store_i(&s->bridge_active, 0); - + sync_store_i(&s->bridge_active, 0); sync_unlock(s); return dst_frames; } @@ -4359,6 +4303,9 @@ void *vj_audio_sync_thread(void *arg) int last_reset = -1; int last_source = -1; int last_file_generation = -1; + int last_wav_limit_ms = 0; + long long wav_limit_frames = 0; + long long wav_read_frames_total = 0; sync_wav_t wav; memset(&wav, 0, sizeof(wav)); @@ -4371,11 +4318,11 @@ void *vj_audio_sync_thread(void *arg) sync_store_i(&s->stop_request, 0); sync_store_i(&s->running, 1); - + veejay_msg(VEEJAY_MSG_INFO, "[AUDIO-SYNC] external audio sync thread started"); + while(!sync_load_i(&s->stop_request)) { int source; int mode; - int enabled; int reset_seq; int got = 0; int ch = 0; @@ -4385,10 +4332,7 @@ void *vj_audio_sync_thread(void *arg) int target_frames; int target_bytes; - enabled = sync_load_i(&s->enabled); - mode = sync_load_i(&s->mode); - - if(enabled) { + if(sync_load_i(&s->enabled)) { int tq_ch = 0, tq_bits = 0, tq_rate = 0, tq_frame_bytes = 0; int tq_frames = sync_dequeue_target_audio(s, &target_buffer, @@ -4405,7 +4349,9 @@ void *vj_audio_sync_thread(void *arg) tq_ch, tq_bits, tq_rate); - if(mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN) + if(sync_load_i(&s->mode) == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW) + sync_tempo_follow_publish(s); + if(sync_load_i(&s->mode) == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN) sync_track_align_push_block(s, target_buffer, tq_frames, @@ -4415,20 +4361,15 @@ void *vj_audio_sync_thread(void *arg) tq_rate, 1); } - if(enabled && (mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW || - mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE)) { - if(sync_try_lock_for_us(s, 500)) { - sync_bridge_update_controller_locked(s, sync_now_ms(), NULL); - sync_unlock(s); - } - } } - if(!enabled || mode == VJ_AUDIO_SYNC_MODE_OFF) { + if(!sync_load_i(&s->enabled) || sync_load_i(&s->mode) == VJ_AUDIO_SYNC_MODE_OFF) { sync_store_i(&s->open, 0); sync_sleep_us(20000); continue; } + + mode = sync_load_i(&s->mode); source = sync_load_i(&s->source); reset_seq = sync_load_i(&s->reset_seq); @@ -4444,7 +4385,6 @@ void *vj_audio_sync_thread(void *arg) s->bridge_source_bpm_latched = 0.0; s->bridge_target_bpm_latched = 0.0; s->bridge_bpm_latch_valid = 0; - sync_bridge_clear_stretch_locked(s); sync_store_i(&s->bridge_state, VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_SOURCE); s->write_frame_abs = 0; s->ring_write_frame = 0; @@ -4452,6 +4392,9 @@ void *vj_audio_sync_thread(void *arg) sync_clear_target_queue_locked(s); sync_unlock(s); sync_wav_close(&wav); + wav_limit_frames = 0; + wav_read_frames_total = 0; + last_wav_limit_ms = 0; sync_store_i(&s->open, 0); last_reset = reset_seq; last_source = source; @@ -4479,7 +4422,7 @@ void *vj_audio_sync_thread(void *arg) } bits = (frame_bytes * 8) / ch; - target_frames = (rate / 1000) * 8; + target_frames = (rate / 1000) * 8; /* ~8ms */ if(target_frames < VJ_AUDIO_SYNC_ANALYSIS_FRAMES) target_frames = VJ_AUDIO_SYNC_ANALYSIS_FRAMES; if(target_frames > VJ_AUDIO_SYNC_MAX_ANALYSIS_FRAMES) @@ -4521,78 +4464,38 @@ void *vj_audio_sync_thread(void *arg) int gen = sync_load_i(&s->file_generation); if(!wav.fp || gen != last_file_generation) { char path[VJ_AUDIO_SYNC_PATH_MAX]; - int limit_ms; sync_lock(s); strncpy(path, s->wav_path, sizeof(path) - 1); path[sizeof(path) - 1] = '\0'; - limit_ms = sync_load_i(&s->wav_limit_ms); sync_unlock(s); sync_wav_close(&wav); - - veejay_msg(VEEJAY_MSG_INFO, - "[AUDIO-SYNC][WAV] opening gen=%d loop=%d limit=%dms path='%s'", - gen, - sync_load_i(&s->wav_loop), - limit_ms, - path); - if(!sync_wav_open(&wav, path)) { - sync_store_i(&s->open, 0); - sync_sleep_us(1000000); - last_file_generation = gen; - continue; - } - - if(limit_ms > 0 && wav.sample_rate > 0 && wav.frame_bytes > 0) { - long long limit_frames = - ((long long)limit_ms * (long long)wav.sample_rate) / 1000LL; - long long limit_bytes = limit_frames * (long long)wav.frame_bytes; - - if(limit_bytes > 0x7fffffffL) - limit_bytes = 0x7fffffffL; - - wav.limit_bytes = (long)limit_bytes; - wav.limit_bytes -= wav.limit_bytes % wav.frame_bytes; - } else { - wav.limit_bytes = 0; - } - wav.virtual_pos = 0; - - if(!sync_prepare_ring(s, wav.channels, wav.bits, wav.sample_rate)) { veejay_msg(VEEJAY_MSG_WARNING, - "[AUDIO-SYNC][WAV] unable to prepare ring for '%s' %dch %dHz %dbit", - path, - wav.channels, - wav.sample_rate, - wav.bits); - sync_wav_close(&wav); + "[AUDIO-SYNC] unable to open WAV source '%s'", + path); sync_store_i(&s->open, 0); sync_sleep_us(1000000); last_file_generation = gen; continue; } - last_file_generation = gen; - + last_wav_limit_ms = sync_wav_side_get_limit(s); + wav_limit_frames = 0; + wav_read_frames_total = 0; + if(last_wav_limit_ms > 0 && wav.sample_rate > 0) + wav_limit_frames = ((long long)wav.sample_rate * (long long)last_wav_limit_ms) / 1000LL; + sync_prepare_ring(s, wav.channels, wav.bits, wav.sample_rate); veejay_msg(VEEJAY_MSG_INFO, - "[AUDIO-SYNC][WAV] source ready gen=%d loop=%d '%s' %dch %dHz %dbit frame_bytes=%d data=%ld limit=%ld", - gen, - sync_load_i(&s->wav_loop), - path, - wav.channels, - wav.sample_rate, - wav.bits, - wav.frame_bytes, - wav.data_bytes, - wav.limit_bytes); + "[AUDIO-SYNC] WAV source ready: '%s' %dch %dHz %dbit", + path, wav.channels, wav.sample_rate, wav.bits); } ch = wav.channels; bits = wav.bits; rate = wav.sample_rate; frame_bytes = wav.frame_bytes; - target_frames = (rate / 1000) * 10; + target_frames = (rate / 1000) * 10; /* ~10ms */ if(target_frames < VJ_AUDIO_SYNC_ANALYSIS_FRAMES) target_frames = VJ_AUDIO_SYNC_ANALYSIS_FRAMES; if(target_frames > VJ_AUDIO_SYNC_MAX_ANALYSIS_FRAMES) @@ -4602,9 +4505,6 @@ void *vj_audio_sync_thread(void *arg) if(buffer_size < target_bytes) { uint8_t *nb = (uint8_t*)vj_malloc((size_t)target_bytes); if(!nb) { - veejay_msg(VEEJAY_MSG_WARNING, - "[AUDIO-SYNC][WAV] unable to allocate read buffer bytes=%d", - target_bytes); sync_sleep_us(100000); continue; } @@ -4613,28 +4513,33 @@ void *vj_audio_sync_thread(void *arg) buffer_size = target_bytes; } + if(wav_limit_frames > 0) { + long long remaining_frames = wav_limit_frames - wav_read_frames_total; + + if(remaining_frames <= 0) { + sync_store_i(&s->open, 0); + sync_store_i(&s->enabled, 0); + continue; + } + + if((long long)target_bytes > remaining_frames * (long long)frame_bytes) + target_bytes = (int)(remaining_frames * (long long)frame_bytes); + } + got = sync_wav_read(&wav, buffer, target_bytes, sync_load_i(&s->wav_loop)); if(got <= 0) { - veejay_msg(VEEJAY_MSG_INFO, - "[AUDIO-SYNC][WAV] EOF/no-data gen=%d loop=%d pos=%ld/%ld limit=%ld, disabling source", - gen, - sync_load_i(&s->wav_loop), - wav.data_pos, - wav.data_bytes, - wav.limit_bytes); sync_store_i(&s->open, 0); sync_store_i(&s->enabled, 0); continue; } + if(frame_bytes > 0) + wav_read_frames_total += got / frame_bytes; if(rate > 0 && frame_bytes > 0) sync_sleep_us(((long long)(got / frame_bytes) * 1000000LL) / rate); } else if(source == VJ_AUDIO_SYNC_SOURCE_PUSH) { - - - sync_sleep_us(2000); continue; } @@ -4648,18 +4553,10 @@ void *vj_audio_sync_thread(void *arg) int frames = got / frame_bytes; sync_publish_audio(s, buffer, frames, ch, bits, rate); sync_update_clock_from_block(s, buffer, frames, frame_bytes, ch, bits, rate); + if(sync_load_i(&s->mode) == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW) + sync_tempo_follow_publish(s); if(sync_load_i(&s->mode) == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN) sync_track_align_push_block(s, buffer, frames, frame_bytes, ch, bits, rate, 0); - { - int cur_mode = sync_load_i(&s->mode); - if(cur_mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW || - cur_mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE) { - if(sync_try_lock_for_us(s, 500)) { - sync_bridge_update_controller_locked(s, sync_now_ms(), NULL); - sync_unlock(s); - } - } - } sync_add_l(&s->reads, 1); } @@ -4675,7 +4572,7 @@ void *vj_audio_sync_thread(void *arg) vj_audio_sync_free(s); sync_store_i(&s->open, 0); sync_store_i(&s->running, 0); - veejay_msg(VEEJAY_MSG_INFO, "Audio sync thread finished"); + veejay_msg(VEEJAY_MSG_INFO, "[AUDIO-SYNC] external audio sync thread stopped"); return NULL; } @@ -4734,26 +4631,33 @@ void vj_audio_sync_set_mode(vj_audio_sync_shared_t *s, int mode) sync_lock(s); old_mode = sync_load_i(&s->mode); - - - if(old_mode != mode) { + sync_store_i(&s->reset_seq, sync_load_i(&s->reset_seq) + 1); + if(old_mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || old_mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW || mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW) { - sync_bridge_clear_locked(s, - (mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || - mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW) ? - VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_SOURCE : - VJ_AUDIO_SYNC_BRIDGE_STATE_IDLE); + s->bridge_read_valid = 0; + s->bridge_ratio_smooth = 0.0; + s->bridge_last_correction = 1.0; + s->bridge_latch_updated_ms = 0; + s->bridge_source_bpm_latched = 0.0; + s->bridge_target_bpm_latched = 0.0; + s->bridge_bpm_latch_valid = 0; + sync_store_i(&s->bridge_active, 0); + sync_store_i(&s->bridge_state, + (mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || + mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW) ? + VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_SOURCE : + VJ_AUDIO_SYNC_BRIDGE_STATE_IDLE); } if(old_mode == VJ_AUDIO_SYNC_MODE_MONITOR || mode == VJ_AUDIO_SYNC_MODE_MONITOR || - old_mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW || - mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW || + old_mode == VJ_AUDIO_SYNC_MODE_MONITOR_TRICKPLAY || + mode == VJ_AUDIO_SYNC_MODE_MONITOR_TRICKPLAY || old_mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN || mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN) { @@ -4764,14 +4668,8 @@ void vj_audio_sync_set_mode(vj_audio_sync_shared_t *s, int mode) mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN) { sync_track_align_clear_locked(s); - - - sync_store_i(&s->reset_seq, sync_load_i(&s->reset_seq) + 1); if(mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN) { - - - sync_store_i(&s->target_mode, VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP); sync_store_i(&s->track_align_state, VJ_AUDIO_SYNC_TRACK_STATE_WAIT_SOURCE); } else { @@ -4842,7 +4740,6 @@ void vj_audio_sync_set_source_jack(vj_audio_sync_shared_t *s, int channels) s->bridge_source_bpm_latched = 0.0; s->bridge_target_bpm_latched = 0.0; s->bridge_bpm_latch_valid = 0; - sync_bridge_clear_stretch_locked(s); sync_clear_monitor_transport_locked(s); sync_store_i(&s->bridge_active, 0); sync_store_i(&s->bridge_state, VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_SOURCE); @@ -4876,21 +4773,20 @@ void vj_audio_sync_set_source_push(vj_audio_sync_shared_t *s, if(sample_rate <= 0) sample_rate = 44100; - sync_lock(s); - old_source = sync_load_i(&s->source); - old_channels = s->channels; - old_bits = s->bits_per_channel; - old_rate = s->sample_rate; + old_channels = sync_load_i(&s->channels); + old_bits = sync_load_i(&s->bits_per_channel); + old_rate = sync_load_i(&s->sample_rate); if(old_source == VJ_AUDIO_SYNC_SOURCE_PUSH && old_channels == channels && old_bits == bits_per_channel && old_rate == sample_rate) { - sync_unlock(s); return; } + + sync_lock(s); sync_store_i(&s->source, VJ_AUDIO_SYNC_SOURCE_PUSH); sync_store_i(&s->input_channels_request, channels); sync_store_i(&s->reset_seq, sync_load_i(&s->reset_seq) + 1); @@ -4904,7 +4800,6 @@ void vj_audio_sync_set_source_push(vj_audio_sync_shared_t *s, s->bridge_source_bpm_latched = 0.0; s->bridge_target_bpm_latched = 0.0; s->bridge_bpm_latch_valid = 0; - sync_bridge_clear_stretch_locked(s); sync_clear_monitor_transport_locked(s); sync_store_i(&s->bridge_active, 0); sync_store_i(&s->bridge_state, VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_SOURCE); @@ -4949,40 +4844,34 @@ int vj_audio_sync_push_audio(vj_audio_sync_shared_t *s, sync_add_l(&s->reads, 1); return frames; } + int vj_audio_sync_set_source_wav_limited(vj_audio_sync_shared_t *s, - const char *path, - int loop, - int limit_ms) + const char *path, + int loop, + int limit_ms); + +int vj_audio_sync_set_source_wav(vj_audio_sync_shared_t *s, const char *path, int loop) { - int old_source; - int old_generation; - int new_generation; + return vj_audio_sync_set_source_wav_limited(s, path, loop, 0); +} - if(!s || !path || !path[0]) { - veejay_msg(VEEJAY_MSG_WARNING, - "[AUDIO-SYNC][WAV] source-select rejected: empty state/path"); +int vj_audio_sync_set_source_wav_limited(vj_audio_sync_shared_t *s, + const char *path, + int loop, + int limit_ms) +{ + if(!s || !path || !path[0]) return 0; - } - old_source = sync_load_i(&s->source); - old_generation = sync_load_i(&s->file_generation); - new_generation = old_generation + 1; - if(new_generation <= 0) - new_generation = 1; - - if(limit_ms < 0) - limit_ms = 0; + sync_wav_side_set_limit(s, loop ? 0 : limit_ms); + sync_wav_plain_lock_clear(s); sync_lock(s); - strncpy(s->wav_path, path, sizeof(s->wav_path) - 1); s->wav_path[sizeof(s->wav_path) - 1] = '\0'; - sync_store_i(&s->wav_loop, loop ? 1 : 0); - sync_store_i(&s->wav_limit_ms, loop ? 0 : limit_ms); - sync_store_i(&s->wav_silence_after_eof, loop ? 0 : 1); sync_store_i(&s->source, VJ_AUDIO_SYNC_SOURCE_WAV_FILE); - sync_store_i(&s->file_generation, new_generation); + sync_store_i(&s->file_generation, sync_load_i(&s->file_generation) + 1); sync_store_i(&s->reset_seq, sync_load_i(&s->reset_seq) + 1); s->analysis_read_frame = 0; @@ -4995,39 +4884,86 @@ int vj_audio_sync_set_source_wav_limited(vj_audio_sync_shared_t *s, s->bridge_source_bpm_latched = 0.0; s->bridge_target_bpm_latched = 0.0; s->bridge_bpm_latch_valid = 0; - - sync_bridge_clear_stretch_locked(s); sync_clear_monitor_transport_locked(s); - sync_track_align_clear_locked(s); - sync_clear_target_queue_locked(s); - sync_clear_clock(s); - sync_store_i(&s->bridge_active, 0); sync_store_i(&s->bridge_state, VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_SOURCE); - - sync_store_i(&s->wav_plain_lock_valid, 0); - sync_store_i(&s->wav_plain_lock_generation, 0); - sync_store_i(&s->wav_plain_lock_delta_frames, 0); - sync_store_i(&s->wav_plain_lock_confidence_pct, 0); - + sync_clear_clock(s); + sync_track_align_clear_locked(s); sync_unlock(s); - - veejay_msg(VEEJAY_MSG_INFO, - "[AUDIO-SYNC][WAV] source-select old_source=%d new_source=%d gen=%d->%d loop=%d limit=%dms path='%s'", - old_source, - VJ_AUDIO_SYNC_SOURCE_WAV_FILE, - old_generation, - new_generation, - loop ? 1 : 0, - limit_ms, - path); - return 1; } -int vj_audio_sync_set_source_wav(vj_audio_sync_shared_t *s, const char *path, int loop) +void vj_audio_sync_wav_restart(vj_audio_sync_shared_t *s) { - return vj_audio_sync_set_source_wav_limited(s, path, loop, 0); + if(!s) + return; + + sync_lock(s); + sync_store_i(&s->file_generation, sync_load_i(&s->file_generation) + 1); + sync_store_i(&s->reset_seq, sync_load_i(&s->reset_seq) + 1); + s->analysis_read_frame = 0; + s->beat_read_frame = 0; + s->record_read_frame = 0; + s->bridge_read_valid = 0; + s->bridge_ratio_smooth = 0.0; + s->bridge_last_correction = 1.0; + s->bridge_latch_updated_ms = 0; + s->bridge_source_bpm_latched = 0.0; + s->bridge_target_bpm_latched = 0.0; + s->bridge_bpm_latch_valid = 0; + sync_clear_monitor_transport_locked(s); + sync_store_i(&s->bridge_active, 0); + sync_store_i(&s->bridge_state, VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_SOURCE); + sync_clear_clock(s); + sync_track_align_clear_locked(s); + sync_unlock(s); +} + +void vj_audio_sync_wav_plain_lock_store(vj_audio_sync_shared_t *s, + int delta_frames, + int confidence_pct) +{ + sync_wav_side_t *side; + + if(confidence_pct < 0) + confidence_pct = 0; + else if(confidence_pct > 100) + confidence_pct = 100; + + sync_wav_side_lock(); + side = sync_wav_side_get_locked(s, 1); + if(side) { + side->plain_lock_valid = 1; + side->plain_lock_delta_frames = delta_frames; + side->plain_lock_confidence_pct = confidence_pct; + } + sync_wav_side_unlock(); +} + +int vj_audio_sync_wav_plain_lock_get(vj_audio_sync_shared_t *s, + int *delta_frames, + int *confidence_pct) +{ + sync_wav_side_t *side; + int valid = 0; + + sync_wav_side_lock(); + side = sync_wav_side_get_locked(s, 0); + if(side && side->plain_lock_valid) { + if(delta_frames) + *delta_frames = side->plain_lock_delta_frames; + if(confidence_pct) + *confidence_pct = side->plain_lock_confidence_pct; + valid = 1; + } + sync_wav_side_unlock(); + + return valid; +} + +int vj_audio_sync_wav_plain_lock_valid(vj_audio_sync_shared_t *s) +{ + return vj_audio_sync_wav_plain_lock_get(s, NULL, NULL); } void vj_audio_sync_set_target_clock(vj_audio_sync_shared_t *s, @@ -5049,13 +4985,10 @@ void vj_audio_sync_set_target_clock(vj_audio_sync_shared_t *s, sync_store_i(&s->target_phase_q15, sync_q15((double)phase)); sync_store_i(&s->target_confidence_q15, sync_q15((double)confidence)); - - - s->bridge_target_bpm_latched = 0.0; s->bridge_bpm_latch_valid = 0; - sync_bridge_clear_stretch_locked(s); s->bridge_latch_updated_ms = 0; + s->bridge_ratio_smooth = 0.0; s->bridge_last_correction = 1.0; sync_store_i(&s->bridge_active, 0); sync_store_i(&s->bridge_state, @@ -5084,12 +5017,11 @@ void vj_audio_sync_set_target_mode(vj_audio_sync_shared_t *s, int mode) if(mode == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) sync_clear_target_clock(s); - - - + /* Target mode changes invalidate the BPM latch, but keep the live + * read-head continuous to avoid audible capstan jumps. + */ s->bridge_target_bpm_latched = 0.0; s->bridge_bpm_latch_valid = 0; - sync_bridge_clear_stretch_locked(s); s->bridge_latch_updated_ms = 0; s->bridge_last_correction = 1.0; sync_store_i(&s->bridge_active, 0); @@ -5107,6 +5039,7 @@ int vj_audio_sync_get_target_mode(vj_audio_sync_shared_t *s) void vj_audio_sync_set_track_align_video_fps(vj_audio_sync_shared_t *s, double fps) { int q; + int old_q; if(!s) return; @@ -5115,7 +5048,23 @@ void vj_audio_sync_set_track_align_video_fps(vj_audio_sync_shared_t *s, double f else if(fps > 240.0) fps = 240.0; q = (int)(fps * 1000.0 + 0.5); + old_q = sync_load_i(&s->align_video_fps_x1000); sync_store_i(&s->align_video_fps_x1000, q); + + if(old_q != q) { + veejay_msg(VEEJAY_MSG_DEBUG, + "[FPSDBG][SYNC] track-align video fps %.3f->%.3f mode=%d target=%d " + "state=%d locked=%d offset=%dms conf=%d%% reset_seq=%d", + old_q > 0 ? ((double)old_q / 1000.0) : 0.0, + (double)q / 1000.0, + sync_load_i(&s->mode), + sync_load_i(&s->target_mode), + sync_load_i(&s->track_align_state), + sync_load_i(&s->track_align_locked), + sync_load_i(&s->track_align_offset_ms), + sync_load_i(&s->track_align_confidence_pct), + sync_load_i(&s->reset_seq)); + } } void vj_audio_sync_reset_target_clock(vj_audio_sync_shared_t *s) @@ -5123,12 +5072,24 @@ void vj_audio_sync_reset_target_clock(vj_audio_sync_shared_t *s) if(!s) return; + { + veejay_msg(VEEJAY_MSG_DEBUG, + "[FPSDBG][SYNC] reset target clock before write=%lld read=%lld process=%lld " + "overruns=%ld reads=%ld mode=%d target=%d state=%d", + s->target_write_frame_abs, + s->target_read_frame_abs, + s->target_process_frame_abs, + sync_load_l(&s->target_queue_overruns), + sync_load_l(&s->target_queue_reads), + sync_load_i(&s->mode), + sync_load_i(&s->target_mode), + sync_load_i(&s->track_align_state)); + } sync_lock(s); sync_clear_target_clock(s); s->bridge_target_bpm_latched = 0.0; s->bridge_bpm_latch_valid = 0; - sync_bridge_clear_stretch_locked(s); s->bridge_latch_updated_ms = 0; s->bridge_last_correction = 1.0; sync_store_i(&s->bridge_active, 0); @@ -5162,9 +5123,6 @@ int vj_audio_sync_push_target_audio(vj_audio_sync_shared_t *s, if(bits != 8 && bits != 16) return 0; - - - return sync_enqueue_target_audio(s, src, frames, @@ -5184,9 +5142,6 @@ void vj_audio_sync_set_bridge_correction(vj_audio_sync_shared_t *s, int max_pct) sync_lock(s); sync_store_i(&s->max_correction_pct, max_pct); - - - s->bridge_last_correction = 1.0; sync_unlock(s); } @@ -5223,139 +5178,12 @@ void vj_audio_sync_track_align_reset_acquisition(vj_audio_sync_shared_t *s) s->align_live_last_snap_ms = 0; s->align_live_last_snap_delta = 0; s->align_snap_cooldown_ms = VJ_AUDIO_SYNC_TRACK_SNAP_OFFER_COOLDOWN_MS; - s->track_align_snap_pending = 0; - s->track_align_snap_delta_frames = 0; - s->track_align_snap_confidence_pct = 0; + sync_store_i(&s->track_align_snap_pending, 0); + sync_store_i(&s->track_align_snap_delta_frames, 0); + sync_store_i(&s->track_align_snap_confidence_pct, 0); sync_unlock(s); } -void vj_audio_sync_wav_restart(vj_audio_sync_shared_t *s) -{ - int old_generation; - int new_generation; - - if(!s) - return; - - if(sync_load_i(&s->source) != VJ_AUDIO_SYNC_SOURCE_WAV_FILE) - return; - - old_generation = sync_load_i(&s->file_generation); - new_generation = old_generation + 1; - if(new_generation <= 0) - new_generation = 1; - - sync_lock(s); - - sync_store_i(&s->file_generation, new_generation); - sync_store_i(&s->reset_seq, sync_load_i(&s->reset_seq) + 1); - - s->analysis_read_frame = 0; - s->beat_read_frame = 0; - s->record_read_frame = 0; - s->bridge_read_valid = 0; - s->bridge_ratio_smooth = 0.0; - s->bridge_last_correction = 1.0; - s->bridge_latch_updated_ms = 0; - s->bridge_source_bpm_latched = 0.0; - s->bridge_target_bpm_latched = 0.0; - s->bridge_bpm_latch_valid = 0; - - sync_bridge_clear_stretch_locked(s); - sync_clear_monitor_transport_locked(s); - sync_track_align_clear_locked(s); - sync_clear_clock(s); - - - - - if(sync_load_i(&s->wav_plain_lock_valid) && - sync_load_i(&s->wav_plain_lock_generation) == old_generation) - { - sync_store_i(&s->wav_plain_lock_generation, new_generation); - } - - sync_unlock(s); - - veejay_msg(VEEJAY_MSG_INFO, - "[AUDIO-SYNC][WAV] restart requested gen=%d->%d", - old_generation, - new_generation); -} - -int vj_audio_sync_wav_plain_lock_valid(vj_audio_sync_shared_t *s) -{ - int valid; - int generation; - int lock_generation; - - if(!s) - return 0; - - if(sync_load_i(&s->source) != VJ_AUDIO_SYNC_SOURCE_WAV_FILE) - return 0; - - valid = sync_load_i(&s->wav_plain_lock_valid); - generation = sync_load_i(&s->file_generation); - lock_generation = sync_load_i(&s->wav_plain_lock_generation); - - return (valid && generation == lock_generation); -} - -int vj_audio_sync_wav_plain_lock_get(vj_audio_sync_shared_t *s, - int *delta_frames, - int *confidence_pct) -{ - if(!vj_audio_sync_wav_plain_lock_valid(s)) - return 0; - - if(delta_frames) - *delta_frames = sync_load_i(&s->wav_plain_lock_delta_frames); - if(confidence_pct) - *confidence_pct = sync_load_i(&s->wav_plain_lock_confidence_pct); - - return 1; -} - -void vj_audio_sync_wav_plain_lock_store(vj_audio_sync_shared_t *s, - int delta_frames, - int confidence_pct) -{ - int generation; - - if(!s) - return; - - if(sync_load_i(&s->source) != VJ_AUDIO_SYNC_SOURCE_WAV_FILE) - return; - - confidence_pct = sync_clampi(confidence_pct, 0, 100); - generation = sync_load_i(&s->file_generation); - - sync_store_i(&s->wav_plain_lock_generation, generation); - sync_store_i(&s->wav_plain_lock_delta_frames, delta_frames); - sync_store_i(&s->wav_plain_lock_confidence_pct, confidence_pct); - sync_store_i(&s->wav_plain_lock_valid, 1); - - veejay_msg(VEEJAY_MSG_INFO, - "[AUDIO-SYNC][WAV] cached PLAIN lock gen=%d delta=%+dfr conf=%d%%", - generation, - delta_frames, - confidence_pct); -} - -void vj_audio_sync_wav_plain_lock_clear(vj_audio_sync_shared_t *s) -{ - if(!s) - return; - - sync_store_i(&s->wav_plain_lock_valid, 0); - sync_store_i(&s->wav_plain_lock_generation, 0); - sync_store_i(&s->wav_plain_lock_delta_frames, 0); - sync_store_i(&s->wav_plain_lock_confidence_pct, 0); -} - - int vj_audio_sync_track_align_frame_action(vj_audio_sync_shared_t *s, double video_fps, int current_speed) @@ -5369,9 +5197,6 @@ int vj_audio_sync_track_align_frame_action(vj_audio_sync_shared_t *s, if(current_speed < 0) return 1; - - - sync_store_i(&s->track_align_correction_ppm, 0); s->align_last_frame_action = 1; return 1; @@ -5383,27 +5208,17 @@ int vj_audio_sync_get_format(vj_audio_sync_shared_t *s, int *bits_per_channel, int *sample_rate) { - int ch; - int bpf; - int bits; - int rate; - if(!s || !sync_load_i(&s->open)) return 0; - sync_lock(s); - ch = s->channels; - bpf = s->bytes_per_frame; - bits = s->bits_per_channel; - rate = s->sample_rate; - sync_unlock(s); + if(channels) *channels = sync_load_i(&s->channels); + if(bytes_per_frame) *bytes_per_frame = sync_load_i(&s->bytes_per_frame); + if(bits_per_channel) *bits_per_channel = sync_load_i(&s->bits_per_channel); + if(sample_rate) *sample_rate = sync_load_i(&s->sample_rate); - if(channels) *channels = ch; - if(bytes_per_frame) *bytes_per_frame = bpf; - if(bits_per_channel) *bits_per_channel = bits; - if(sample_rate) *sample_rate = rate; - - return ch > 0 && bpf > 0 && rate > 0; + return sync_load_i(&s->channels) > 0 && + sync_load_i(&s->bytes_per_frame) > 0 && + sync_load_i(&s->sample_rate) > 0; } int vj_audio_sync_get_snapshot(vj_audio_sync_shared_t *s, @@ -5419,6 +5234,10 @@ int vj_audio_sync_get_snapshot(vj_audio_sync_shared_t *s, dst->mode = sync_load_i(&s->mode); dst->source = sync_load_i(&s->source); dst->target_mode = sync_load_i(&s->target_mode); + dst->channels = sync_load_i(&s->channels); + dst->bytes_per_frame = sync_load_i(&s->bytes_per_frame); + dst->bits_per_channel = sync_load_i(&s->bits_per_channel); + dst->sample_rate = sync_load_i(&s->sample_rate); dst->level = sync_from_q15(sync_load_i(&s->level_q15)); dst->envelope = sync_from_q15(sync_load_i(&s->envelope_q15)); dst->transient = sync_from_q15(sync_load_i(&s->transient_q15)); @@ -5433,11 +5252,6 @@ int vj_audio_sync_get_snapshot(vj_audio_sync_shared_t *s, dst->overruns = sync_load_l(&s->overruns); dst->underruns = sync_load_l(&s->underruns); dst->reads = sync_load_l(&s->reads); - dst->target_queue_overruns = sync_load_l(&s->target_queue_overruns); - dst->target_queue_reads = sync_load_l(&s->target_queue_reads); - dst->target_queue_lock_drops = sync_load_l(&s->target_queue_lock_drops); - dst->target_queue_overflow_events = sync_load_l(&s->target_queue_overflow_events); - dst->target_queue_dropped_frames = sync_load_l(&s->target_queue_dropped_frames); dst->max_correction_pct = sync_load_i(&s->max_correction_pct); dst->bridge_state = sync_load_i(&s->bridge_state); dst->track_align_locked = sync_load_i(&s->track_align_locked); @@ -5445,38 +5259,16 @@ int vj_audio_sync_get_snapshot(vj_audio_sync_shared_t *s, dst->track_align_confidence_pct = sync_load_i(&s->track_align_confidence_pct); dst->track_align_correction_ppm = sync_load_i(&s->track_align_correction_ppm); dst->track_align_state = sync_load_i(&s->track_align_state); + dst->track_align_snap_pending = sync_load_i(&s->track_align_snap_pending); + dst->track_align_snap_delta_frames = sync_load_i(&s->track_align_snap_delta_frames); + dst->track_align_snap_confidence_pct = sync_load_i(&s->track_align_snap_confidence_pct); sync_lock(s); - dst->channels = s->channels; - dst->bytes_per_frame = s->bytes_per_frame; - dst->bits_per_channel = s->bits_per_channel; - dst->sample_rate = s->sample_rate; - dst->track_align_snap_pending = s->track_align_snap_pending; - dst->track_align_snap_delta_frames = s->track_align_snap_delta_frames; - dst->track_align_snap_confidence_pct = s->track_align_snap_confidence_pct; dst->bridge_ratio = s->bridge_ratio_smooth; dst->bridge_correction = s->bridge_last_correction; - - if(s->target_sample_rate > 0) { - long long pending = s->target_write_frame_abs - s->target_process_frame_abs; - long long retained = s->target_write_frame_abs - s->target_read_frame_abs; - - if(pending < 0) - pending = 0; - if(retained < 0) - retained = 0; - - dst->target_queue_pending_ms = - (int)((pending * 1000LL) / (long long)s->target_sample_rate); - dst->target_queue_retained_ms = - (int)((retained * 1000LL) / (long long)s->target_sample_rate); - dst->target_queue_ring_ms = - (int)(((long long)s->target_ring_frames * 1000LL) / - (long long)s->target_sample_rate); - } sync_unlock(s); return 1; } -#endif +#endif /* HAVE_JACK */ diff --git a/veejay-current/veejay-server/libveejay/vj-audio-sync.h b/veejay-current/veejay-server/libveejay/vj-audio-sync.h index ddd2e33d..830b6e37 100644 --- a/veejay-current/veejay-server/libveejay/vj-audio-sync.h +++ b/veejay-current/veejay-server/libveejay/vj-audio-sync.h @@ -32,19 +32,52 @@ #define VJ_AUDIO_SYNC_ALIGN_FEATURES 4096 +static inline int vj_audio_sync_mode_is_control_only(int mode) +{ + return mode == VJ_AUDIO_SYNC_MODE_LIVE_EXTERNAL; +} +static inline int vj_audio_sync_mode_is_tempo_follow(int mode) +{ + return mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW; +} -#define VJ_AUDIO_SYNC_TRACK_STATE_IDLE 0 -#define VJ_AUDIO_SYNC_TRACK_STATE_WAIT_SOURCE 1 -#define VJ_AUDIO_SYNC_TRACK_STATE_WAIT_TARGET 2 -#define VJ_AUDIO_SYNC_TRACK_STATE_SEARCHING 3 -#define VJ_AUDIO_SYNC_TRACK_STATE_LOCKED 4 -#define VJ_AUDIO_SYNC_TRACK_STATE_HOLD 5 -#define VJ_AUDIO_SYNC_TRACK_STATE_FALLBACK 6 +static inline int vj_audio_sync_mode_is_clean_monitor(int mode) +{ + return mode == VJ_AUDIO_SYNC_MODE_MONITOR; +} -#ifndef VJ_AUDIO_SYNC_MODE_MAX -#define VJ_AUDIO_SYNC_MODE_MAX VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW -#endif +static inline int vj_audio_sync_mode_is_trickplay_monitor(int mode) +{ + return mode == VJ_AUDIO_SYNC_MODE_MONITOR_TRICKPLAY; +} + +static inline int vj_audio_sync_mode_uses_external_playback(int mode) +{ + return mode == VJ_AUDIO_SYNC_MODE_MONITOR || + mode == VJ_AUDIO_SYNC_MODE_MONITOR_TRICKPLAY || + mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || + mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN; +} + +static inline int vj_audio_sync_mode_uses_transport_driven_playback(int mode) +{ + return mode == VJ_AUDIO_SYNC_MODE_MONITOR_TRICKPLAY || + mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || + mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN; +} + +static inline int vj_audio_sync_mode_supports_wav_master(int mode) +{ + return vj_audio_sync_mode_uses_external_playback(mode); +} + +static inline int vj_audio_sync_mode_is_audio_sync_family(int mode) +{ + return mode == VJ_AUDIO_SYNC_MODE_LIVE_EXTERNAL || + mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW || + vj_audio_sync_mode_uses_external_playback(mode); +} typedef struct { @@ -97,20 +130,12 @@ typedef struct int track_align_correction_ppm; int track_align_state; - /* Internal wide-search snap suggestion, consumed by the playback loop. - * Not part of the public status token contract. - */ int track_align_snap_pending; int track_align_snap_delta_frames; int track_align_snap_confidence_pct; } vj_audio_sync_snapshot_t; -/* Full shared state is intentionally defined here because video_playback_setup - * embeds vj_audio_sync_shared_t directly. Do not include vj-lib.h from the - * audio synchronizer: modules that only need pointers can forward-declare this - * typedef, while vj-lib.h includes this header to get the complete size. - */ typedef struct vj_audio_sync_shared_t { volatile int initialized; @@ -186,10 +211,6 @@ typedef struct vj_audio_sync_shared_t long bridge_latch_updated_ms; volatile int bridge_state; - /* Tempo Bridge transport clock. - * Keep BPM estimates latched and slow-moving so the audible read-head - * behaves like a stable transport, not like a phase-chasing cassette motor. - */ double bridge_source_bpm_latched; double bridge_target_bpm_latched; int bridge_bpm_latch_valid; @@ -201,10 +222,6 @@ typedef struct vj_audio_sync_shared_t int bridge_stretch_segment_len; int bridge_stretch_overlap; - /* Track Align / waveform sync feature rings. - * Source = external master track; target = current clip/original audio. - * Features are low-rate onset/envelope values, updated by capture/push paths. - */ float align_source_feat[VJ_AUDIO_SYNC_ALIGN_FEATURES]; float align_target_feat[VJ_AUDIO_SYNC_ALIGN_FEATURES]; int align_source_pos; @@ -227,7 +244,6 @@ typedef struct vj_audio_sync_shared_t int align_snap_cooldown_ms; volatile int align_video_fps_x1000; - /* Worker-thread live snap consensus state. */ int align_live_candidate_delta; int align_live_candidate_conf_min; int align_live_candidate_conf_sum; @@ -236,28 +252,20 @@ typedef struct vj_audio_sync_shared_t long align_live_last_snap_ms; int align_live_last_snap_delta; - /* Rough, non-authoritative live basin exported to performer wide search. */ int align_rough_hint_offset_ms; int align_rough_hint_conf; int align_rough_hint_count; long align_rough_hint_ms; - /* Current-clip target audio queue. Producer enqueues PCM cheaply; - * audio_sync_thread drains it and runs target clock/Track Align analysis. - */ + uint8_t *target_ring; int target_ring_frames; int target_ring_write_frame; long long target_write_frame_abs; - /* Oldest retained target frame in the ring. This is no longer the - * worker consume cursor: Track Align needs retained target history for - * stable correlation, while the worker still needs to process each - * target block exactly once. - */ + long long target_read_frame_abs; - /* Next target frame the sync worker must process into clock/features. */ long long target_process_frame_abs; int target_channels; @@ -280,16 +288,18 @@ typedef struct vj_audio_sync_shared_t volatile int wav_limit_ms; volatile int wav_silence_after_eof; - /* PLAIN+WAV Track Align cache. Once a strong WAV/video lock is found, - * replay can snap immediately without re-running wide target analysis. - * The cache is generation-bound, so changing the WAV path/source clears it. - */ volatile int wav_plain_lock_valid; volatile int wav_plain_lock_generation; volatile int wav_plain_lock_delta_frames; volatile int wav_plain_lock_confidence_pct; - /* internal source detector state; only sync thread / push path writes it */ + + volatile int jack_connect_failures; + volatile int jack_connect_giveup; + volatile long jack_connect_first_fail_ms; + volatile long jack_connect_next_retry_ms; + volatile long jack_connect_last_log_ms; + double fast_energy; double slow_energy; double envelope; @@ -297,7 +307,6 @@ typedef struct vj_audio_sync_shared_t double beat_period_ms; long last_analysis_ms; - /* internal target-clock detector state for current-clip auto target */ double target_fast_energy; double target_slow_energy; double target_envelope; @@ -371,10 +380,7 @@ void vj_audio_sync_track_align_reset_acquisition(vj_audio_sync_shared_t *s); int vj_audio_sync_track_align_source_ready(vj_audio_sync_shared_t *s, int min_source_features); int vj_audio_sync_track_align_last_snap(vj_audio_sync_shared_t *s, long *snap_ms, int *delta_frames); -/* Wide waveform snap support. - * Probe candidate original-audio windows against the recent external master - * feature window, then offer/consume a one-shot video-frame snap. - */ + int vj_audio_sync_track_align_probe_target_audio(vj_audio_sync_shared_t *s, const uint8_t *src, int frames, diff --git a/veejay-current/veejay-server/libveejay/vj-event.c b/veejay-current/veejay-server/libveejay/vj-event.c index 59dd03a1..96b9d651 100644 --- a/veejay-current/veejay-server/libveejay/vj-event.c +++ b/veejay-current/veejay-server/libveejay/vj-event.c @@ -56,6 +56,9 @@ #include #include #include +#ifndef VJ_AUDIO_BEAT_ACTION_BREAK_BEAT +#define VJ_AUDIO_BEAT_ACTION_BREAK_BEAT 4 +#endif #endif #include #include @@ -8670,6 +8673,8 @@ static const char *vj_audio_beat_event_action_name(int action) return "auto-fx"; case VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX: return "freeze+auto-fx"; + case VJ_AUDIO_BEAT_ACTION_BREAK_BEAT: + return "break-beat"; default: return "unknown"; } @@ -9229,7 +9234,7 @@ void vj_event_audio_beat_ui_config(void *ptr, const char format[], va_list ap) if(!vj_event_audio_beat_ready(v)) return; - args[1] = vj_audio_beat_event_clampi(args[1], VJ_AUDIO_BEAT_ACTION_NONE, VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX); + args[1] = vj_audio_beat_event_clampi(args[1], VJ_AUDIO_BEAT_ACTION_NONE, VJ_AUDIO_BEAT_ACTION_BREAK_BEAT); args[8] = vj_audio_beat_event_clampi(args[8], VJ_AUDIO_BEAT_AUTO_OFF, VJ_AUDIO_BEAT_AUTO_CHAOS); args[9] = vj_audio_beat_event_clampi(args[9], 0, 100); @@ -9405,7 +9410,7 @@ void vj_event_audio_beat_action(void *ptr, const char format[], va_list ap) args[0] = vj_audio_beat_event_clampi(args[0], VJ_AUDIO_BEAT_ACTION_NONE, - VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX); + VJ_AUDIO_BEAT_ACTION_BREAK_BEAT); { int freeze = -1; diff --git a/veejay-current/veejay-server/libveejay/vj-eventman.c b/veejay-current/veejay-server/libveejay/vj-eventman.c index 9b1f9595..b1655837 100644 --- a/veejay-current/veejay-server/libveejay/vj-eventman.c +++ b/veejay-current/veejay-server/libveejay/vj-eventman.c @@ -2362,7 +2362,7 @@ void vj_init_vevo_events(void) vj_event_audio_beat_action, 1, VIMS_REQUIRE_ALL_PARAMS, - "Action: 0=none, 1=freeze, 2=auto-fx, 3=freeze+auto-fx", + "Action: 0=none, 1=freeze, 2=auto-fx, 3=freeze+auto-fx, 4=break-beat", 2, NULL ); @@ -2428,7 +2428,7 @@ void vj_init_vevo_events(void) VIMS_REQUIRE_ALL_PARAMS, "Enabled state, 0 or 1", 1, - "Action: 0=none, 1=freeze, 2=auto-fx, 3=freeze+auto-fx", + "Action: 0=none, 1=freeze, 2=auto-fx, 3=freeze+auto-fx, 4=break-beat", 2, "Freeze duration in milliseconds", 90, diff --git a/veejay-current/veejay-server/libveejay/vj-lib.h b/veejay-current/veejay-server/libveejay/vj-lib.h index be667a5e..6c1bcfa9 100644 --- a/veejay-current/veejay-server/libveejay/vj-lib.h +++ b/veejay-current/veejay-server/libveejay/vj-lib.h @@ -44,7 +44,6 @@ enum { LAVPLAY_STATE_PLAYING = 2 /* speed != 0 */ }; -/* nmacro recorder, 5 lines code for play back of what you changed at navigation */ enum { VJ_MACRO_PLAIN_RECORD = 0, VJ_MACRO_PLAIN_PLAY = 1, @@ -56,10 +55,6 @@ enum { #define AUDIO_MODE_SILENCE_FILL 0 #define AUDIO_MODE_CONTENT 1 -/* Coherent recording audio source selection. - * AUTO follows the active audible external path when one is selected, - * otherwise it records the original media audio. - */ #define VJ_RECORD_AUDIO_SOURCE_AUTO 0 #define VJ_RECORD_AUDIO_SOURCE_ORIGINAL 1 #define VJ_RECORD_AUDIO_SOURCE_BEAT_JACK 2 @@ -70,7 +65,6 @@ enum { #define DUMMY_DEFAULT_HEIGHT 288 #define DUMMY_DEFAULT_FPS 25 -/* Video Playback Setup, necessary items for reading and playing video */ struct mjpeg_sync { unsigned long frame; /* Frame (0 - n) for double buffer */ @@ -225,16 +219,35 @@ typedef struct vj_audio_beat_shared_t volatile long record_underruns; #ifdef HAVE_JACK - /* - * External capture/clock provider. - * Beat consumes this instead of owning JACK input directly. - * NULL keeps legacy direct-JACK capture alive during transition. - */ vj_audio_sync_shared_t *sync; #endif } vj_audio_beat_shared_t; + +#define VJ_SCENE_ANALYSIS_MAX_SIDE 64 +#define VJ_SCENE_ANALYSIS_MAX_PIXELS (VJ_SCENE_ANALYSIS_MAX_SIDE * VJ_SCENE_ANALYSIS_MAX_SIDE) + +typedef struct vj_scene_detect_t +{ + volatile int valid; + volatile long long frame; + volatile int scene_id; + volatile long long last_cut_frame; + volatile int scene_age_frames; + volatile int hard_cut; + volatile int cut_score_q15; + volatile int diff_q15; + volatile int mean_q15; + volatile int analysis_w; + volatile int analysis_h; + int prev_ready; + double diff_ema; + double diff_var; + double mean_ema; + uint8_t prev[VJ_SCENE_ANALYSIS_MAX_PIXELS]; +} vj_scene_detect_t; + typedef struct vj_audio_clock_osd_t { volatile long long prod_loops; volatile long long prod_anomalies; @@ -298,6 +311,7 @@ typedef struct { vj_audio_sync_shared_t audio_sync; #endif vj_audio_beat_shared_t audio_beat; + vj_scene_detect_t scene_detect; volatile int audio_mode; volatile int state; @@ -310,13 +324,6 @@ typedef struct { volatile double runtime_playback_rate; volatile long long anchor_frame; #ifdef HAVE_JACK - /* Track Align re-anchor bookkeeping. When the user scratches, reverses, - * pauses, or changes runtime FPS, the external JACK/master track keeps - * moving forward. On return to normal +1 playback we can estimate the - * expected media frame linearly from the audio clock, then let waveform - * align fine-tune it. track_align_reacquire_seq lets performer-side wide - * acquisition clear stale buckets without coupling libveejay to performer_t. - */ volatile int track_align_reacquire_seq; volatile int track_align_linear_active; volatile long long track_align_linear_anchor_frame; @@ -326,13 +333,7 @@ typedef struct { volatile double track_align_linear_frame_accum; volatile int track_align_linear_mode; volatile int track_align_linear_id; - /* Track Align correction edge handling. - * Current Track Align corrections use the normal AUDIO_EDGE_JUMP - * crossfade/declick path for arbitrary corrected seeks. AUDIO_EDGE_RESET - * stays reserved for real reset boundaries such as sample start/min-frame - * handling. These fields are kept for ABI/source compatibility with earlier - * experimental builds; playback no longer mutes on track_align_audio_guard. - */ + volatile int track_align_force_audio_edge_reset; volatile long long track_align_audio_guard_until_ms; #endif diff --git a/veejay-current/veejay-server/libveejay/vj-perform.c b/veejay-current/veejay-server/libveejay/vj-perform.c index d452108f..77f76606 100644 --- a/veejay-current/veejay-server/libveejay/vj-perform.c +++ b/veejay-current/veejay-server/libveejay/vj-perform.c @@ -77,16 +77,59 @@ #endif #define PERFORM_AUDIO_SIZE 16384 #define AUDIO_TURN_HISTORY_BYTES 4096 + #ifndef VJ_EXTERNAL_AUDIO_HISTORY_SECONDS #define VJ_EXTERNAL_AUDIO_HISTORY_SECONDS 240.0 #endif + #ifndef VJ_EXTERNAL_AUDIO_HISTORY_MAX_SECONDS #define VJ_EXTERNAL_AUDIO_HISTORY_MAX_SECONDS 240.0 #endif + +#ifndef VJ_EXTERNAL_AUDIO_REVERSE_LATENCY_MS +#define VJ_EXTERNAL_AUDIO_REVERSE_LATENCY_MS 500 +#endif + +#ifndef VJ_EXTERNAL_AUDIO_LIVE_REVERSE_LATENCY_MS +#define VJ_EXTERNAL_AUDIO_LIVE_REVERSE_LATENCY_MS 200 +#endif + +#ifndef VJ_EXTERNAL_AUDIO_LIVE_REVERSE_PREROLL_MS +#define VJ_EXTERNAL_AUDIO_LIVE_REVERSE_PREROLL_MS 1200 +#endif + +#ifndef VJ_EXTERNAL_AUDIO_LIVE_REVERSE_WINDOW_MS +#define VJ_EXTERNAL_AUDIO_LIVE_REVERSE_WINDOW_MS 45000 +#endif + +#ifndef VJ_EXTERNAL_AUDIO_LIVE_REVERSE_MIN_WINDOW_MS +#define VJ_EXTERNAL_AUDIO_LIVE_REVERSE_MIN_WINDOW_MS 2500 +#endif + +#ifndef VJ_EXTERNAL_AUDIO_LIVE_REVERSE_EDGE_FADE_MS +#define VJ_EXTERNAL_AUDIO_LIVE_REVERSE_EDGE_FADE_MS 60 +#endif + #ifndef TRACK_ALIGN_AUDIO_RENDER_FRAMES #define TRACK_ALIGN_AUDIO_RENDER_FRAMES 128 #endif +#ifndef VJ_AUDIO_JACK_START_RETRY_INITIAL_MS +#define VJ_AUDIO_JACK_START_RETRY_INITIAL_MS 250L +#endif +#ifndef VJ_AUDIO_JACK_START_RETRY_MAX_MS +#define VJ_AUDIO_JACK_START_RETRY_MAX_MS 8000L +#endif +#ifndef VJ_AUDIO_JACK_START_RETRY_GIVEUP_ATTEMPTS +#define VJ_AUDIO_JACK_START_RETRY_GIVEUP_ATTEMPTS 8 +#endif +#ifndef VJ_AUDIO_JACK_START_RETRY_GIVEUP_MS +#define VJ_AUDIO_JACK_START_RETRY_GIVEUP_MS 32000L +#endif +#ifndef VJ_AUDIO_JACK_START_RETRY_LOG_MIN_MS +#define VJ_AUDIO_JACK_START_RETRY_LOG_MIN_MS 2000L +#endif + #ifndef VJ_TRACK_ALIGN_WIDE_FAST_CONF #define VJ_TRACK_ALIGN_WIDE_FAST_CONF 75 #endif @@ -508,6 +551,112 @@ static inline int vj_perform_audio_media_valid(const editlist *el) } #ifdef HAVE_JACK +static volatile int vj_audio_jack_start_failures_ = 0; +static volatile int vj_audio_jack_start_giveup_ = 0; +static volatile long vj_audio_jack_start_first_fail_ms_ = 0; +static volatile long vj_audio_jack_start_next_retry_ms_ = 0; +static volatile long vj_audio_jack_start_last_log_ms_ = 0; + +static long vj_audio_jack_start_now_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ((long)ts.tv_sec * 1000L) + ((long)ts.tv_nsec / 1000000L); +} + +static long vj_audio_jack_start_delay_ms(int failures) +{ + long delay = VJ_AUDIO_JACK_START_RETRY_INITIAL_MS; + + if(failures < 1) + failures = 1; + + while(failures > 1 && delay < VJ_AUDIO_JACK_START_RETRY_MAX_MS) { + delay *= 2L; + if(delay > VJ_AUDIO_JACK_START_RETRY_MAX_MS) + delay = VJ_AUDIO_JACK_START_RETRY_MAX_MS; + failures--; + } + + return delay; +} + +static void vj_audio_jack_start_backoff_reset(void) +{ + atomic_store_int(&vj_audio_jack_start_failures_, 0); + atomic_store_int(&vj_audio_jack_start_giveup_, 0); + atomic_store_long(&vj_audio_jack_start_first_fail_ms_, 0); + atomic_store_long(&vj_audio_jack_start_next_retry_ms_, 0); + atomic_store_long(&vj_audio_jack_start_last_log_ms_, 0); +} + +static int vj_audio_jack_start_may_attempt(long now_ms) +{ + long next_ms; + + if(atomic_load_int(&vj_audio_jack_start_giveup_)) + return 0; + + next_ms = atomic_load_long(&vj_audio_jack_start_next_retry_ms_); + if(next_ms > 0 && now_ms < next_ms) + return 0; + + return 1; +} + +static void vj_audio_jack_start_note_failure(long now_ms, const char *reason) +{ + int failures; + long first_ms; + long elapsed_ms; + long delay_ms; + long last_log_ms; + int giveup; + + failures = atomic_load_int(&vj_audio_jack_start_failures_) + 1; + atomic_store_int(&vj_audio_jack_start_failures_, failures); + + first_ms = atomic_load_long(&vj_audio_jack_start_first_fail_ms_); + if(first_ms <= 0) { + first_ms = now_ms; + atomic_store_long(&vj_audio_jack_start_first_fail_ms_, first_ms); + } + + elapsed_ms = now_ms - first_ms; + if(elapsed_ms < 0) + elapsed_ms = 0; + + giveup = (failures >= VJ_AUDIO_JACK_START_RETRY_GIVEUP_ATTEMPTS || + elapsed_ms >= VJ_AUDIO_JACK_START_RETRY_GIVEUP_MS); + + if(giveup) { + atomic_store_int(&vj_audio_jack_start_giveup_, 1); + atomic_store_long(&vj_audio_jack_start_next_retry_ms_, 0); + veejay_msg(VEEJAY_MSG_WARNING, + "[AUDIO] JACK playback disabled after %d failed attempt%s (%ld ms): %s. " + "On PipeWire JACK systems, start veejay through 'pw-jack' or fix the JACK environment.", + failures, failures == 1 ? "" : "s", elapsed_ms, + reason ? reason : "connection failed"); + return; + } + + delay_ms = vj_audio_jack_start_delay_ms(failures); + atomic_store_long(&vj_audio_jack_start_next_retry_ms_, now_ms + delay_ms); + + last_log_ms = atomic_load_long(&vj_audio_jack_start_last_log_ms_); + if(last_log_ms <= 0 || + (now_ms - last_log_ms) >= VJ_AUDIO_JACK_START_RETRY_LOG_MIN_MS || + failures <= 2) + { + atomic_store_long(&vj_audio_jack_start_last_log_ms_, now_ms); + veejay_msg(VEEJAY_MSG_WARNING, + "[AUDIO] JACK playback unavailable (%s, attempt %d/%d); retry in %.1f s", + reason ? reason : "connection failed", + failures, VJ_AUDIO_JACK_START_RETRY_GIVEUP_ATTEMPTS, + ((double)delay_ms) / 1000.0); + } +} + static int vj_perform_prepare_silence_audio_params(editlist *el) { if(!el || el->video_fps <= 0.0) @@ -631,6 +780,9 @@ typedef struct long long external_audio_history_abs_write; double external_audio_read_pos; double external_audio_read_vel; + int external_audio_live_reverse_valid; + long long external_audio_live_reverse_start; + long long external_audio_live_reverse_end; uint8_t *external_audio_context; size_t external_audio_context_capacity; int external_audio_transport_active; @@ -796,6 +948,7 @@ static void vj_perform_plain_fill_buffer(veejay_t * info,performer_t *p,VJFrame static void vj_perform_tag_fill_buffer(veejay_t * info, performer_t *p, VJFrame *dst, int sample_id); static int vj_perform_apply_secundary_tag(veejay_t * info, performer_t *p, int sample_id,int type, int chain_entry, VJFrame *src, VJFrame *dst,uint8_t *p0, uint8_t *p1, int subrender); static int vj_perform_apply_secundary(veejay_t * info, performer_t *p, int this_sample_id,int sample_id,int type, int chain_entry, VJFrame *src, VJFrame *dst,uint8_t *p0, uint8_t *p1, int subrender); +extern int veejay_set_speed(veejay_t *info, int speed, int force_seek); static void vj_perform_tag_complete_buffers(veejay_t * info, performer_t *p, vjp_kf *effect_info, int *h, VJFrame *f0, VJFrame *f1, int sample_id, int pm, vjp_kf *setup, sample_eff_chain **chain, vj_tag *tag); static void vj_perform_sample_complete_buffers(veejay_t * info, performer_t *p, vjp_kf *effect_info, int *h, VJFrame *f0, VJFrame *f2, int sample_id, int pm, vjp_kf *setup, sample_eff_chain **chain, sample_info *si); static void vj_perform_apply_first(veejay_t *info, performer_t *p, vjp_kf *todo_info, VJFrame **frames, sample_eff_chain *entry, int e, int c, long long n_frames, void *ptr, int playmode ); @@ -822,8 +975,27 @@ int vj_audio_sync_track_align_source_ready(vj_audio_sync_shared_t *s, int vj_audio_sync_track_align_last_snap(vj_audio_sync_shared_t *s, long *snap_ms, int *delta_frames); +int vj_audio_beat_transport_is_internal(vj_audio_beat_shared_t *s); +void vj_audio_beat_user_transport_override(veejay_t *v, + vj_audio_beat_shared_t *s, + int requested_speed); #endif +static int vj_perform_set_speed_beat_aware(veejay_t *info, int speed, int force_seek) +{ +#ifdef HAVE_JACK + if(info && info->settings) + { + video_playback_setup *settings = info->settings; + + if(!vj_audio_beat_transport_is_internal(&settings->audio_beat)) + vj_audio_beat_user_transport_override(info, &settings->audio_beat, speed); + } +#endif + + return veejay_set_speed(info, speed, force_seek); +} + static void vj_perform_set_444__(VJFrame *frame) { frame->ssm = 1; @@ -1473,6 +1645,11 @@ static int vj_perform_ab_chain_set_arg(void *ctx, int chain_pos, int param_nr, i return 1; } +static sample_eff_chain *vj_perform_ab_chain_get_entry(void *ctx, int chain_pos) +{ + return vj_perform_ab_ctx_entry(ctx, chain_pos); +} + static int vj_perform_ab_ctx_active_fx_count(vj_perform_ab_render_ctx_t *ctx) { int active = 0; @@ -1618,7 +1795,8 @@ static int vj_perform_audio_beat_apply_render_chains( if(action != VJ_AUDIO_BEAT_ACTION_AUTO_FX && - action != VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX) + action != VJ_AUDIO_BEAT_ACTION_FREEZE_AND_AUTO_FX && + action != VJ_AUDIO_BEAT_ACTION_BREAK_BEAT) return 0; if(!vj_perform_audio_beat_playmode_has_fx_chain(playmode)) @@ -1691,13 +1869,14 @@ static int vj_perform_audio_beat_apply_render_chains( #pragma omp critical(vj_perform_audio_beat_auto_apply) #endif { - changed = vj_audio_beat_auto_apply_chain( + changed = vj_audio_beat_auto_apply_chain_ex( &settings->audio_beat, &ctx, ctx.chain_count * SAMPLE_MAX_EFFECTS, vj_perform_ab_chain_get_fx_id, vj_perform_ab_chain_get_arg, - vj_perform_ab_chain_set_arg + vj_perform_ab_chain_set_arg, + vj_perform_ab_chain_get_entry ); } @@ -1745,6 +1924,25 @@ static inline int vj_seq_type_to_playback_mode(int type) : VJ_PLAYBACK_MODE_TAG; } +static int vj_perform_sequence_transition_still_valid(veejay_t *info) +{ + if (!info || !info->settings || !info->seq || !info->seq->active) + return 1; + + video_playback_setup *settings = info->settings; + const int slot = settings->transition.seq_index; + + if (slot < 0 || slot >= MAX_SEQUENCES) + return 0; + + const int armed_id = settings->transition.next_id; + const int armed_mode = vj_seq_type_to_playback_mode(settings->transition.next_type); + const int slot_id = info->seq->samples[slot].sample_id; + const int slot_mode = vj_seq_type_to_playback_mode(info->seq->samples[slot].type); + + return armed_id > 0 && slot_id == armed_id && slot_mode == armed_mode; +} + int vj_perform_get_next_sequence_id(veejay_t *info, int *type, int current, int *new_current) { int cur = current; @@ -1823,9 +2021,32 @@ void vj_perform_setup_transition(veejay_t *info, return; } - int span = end - start; - if (transition_length > span) - transition_length = span; + const int span = end - start; + + if (transition_length >= span) { + if (info->seq && info->seq->active) { + vj_perform_reset_transition(info); + return; + } + transition_length = span - 1; + } + + if (transition_length <= 0) { + vj_perform_reset_transition(info); + return; + } + + if (info->seq && info->seq->active) { + int max_seq_transition = span / 2; + + if (max_seq_transition < 1) { + vj_perform_reset_transition(info); + return; + } + + if (transition_length > max_seq_transition) + transition_length = max_seq_transition; + } long long start_tx = start; long long end_tx = end; @@ -1918,9 +2139,18 @@ int vj_perform_try_sequence(veejay_t *info) const int global_transition_on = atomic_load_int(&settings->transition.global_state); - const int armed_transition_active = + int armed_transition_active = atomic_load_int(&settings->transition.active); + if (armed_transition_active && !vj_perform_sequence_transition_still_valid(info)) { + veejay_msg(VEEJAY_MSG_DEBUG, + "[SEQ] stale armed transition dropped slot=%d next=%d", + settings->transition.seq_index, + settings->transition.next_id); + vj_perform_reset_transition(info); + armed_transition_active = 0; + } + veejay_msg(VEEJAY_MSG_DEBUG, "[SEQ] advance slot=%d -> %d %s:%d -> %s:%d transition_global=%d transition_active=%d transport=%lld", info->seq->current, @@ -2311,6 +2541,9 @@ static void vj_perform_close_audio(performer_t *p) { p->external_audio_history_abs_write = 0; p->external_audio_read_pos = 0.0; p->external_audio_read_vel = 0.0; + p->external_audio_live_reverse_valid = 0; + p->external_audio_live_reverse_start = 0; + p->external_audio_live_reverse_end = 0; p->external_audio_prev_valid = 0; p->external_audio_prev_frame_bytes = 0; p->clip_target_last_frame = -1; @@ -2464,6 +2697,9 @@ int vj_perform_init_audio(veejay_t * info, int AorB) p->external_audio_history_abs_write = 0; p->external_audio_read_pos = 0.0; p->external_audio_read_vel = 0.0; + p->external_audio_live_reverse_valid = 0; + p->external_audio_live_reverse_start = 0; + p->external_audio_live_reverse_end = 0; veejay_msg(VEEJAY_MSG_INFO, "[AUDIO-EXT] JACK monitor history %.1f seconds (%u bytes, rate=%d frame_bytes=%d)", @@ -2703,6 +2939,12 @@ int vj_perform_audio_start(veejay_t * info) return 0; } + { + long now_ms = vj_audio_jack_start_now_ms(); + if(!vj_audio_jack_start_may_attempt(now_ms)) + return 0; + } + const int silent_output = !el->has_audio; int saved_has_audio = el->has_audio; @@ -2739,6 +2981,8 @@ int vj_perform_audio_start(veejay_t * info) el->has_audio = saved_has_audio; if( res <= 0 ) { + vj_audio_jack_start_note_failure(vj_audio_jack_start_now_ms(), + "unable to connect to JACK server"); veejay_msg(0, "[AUDIO] Audio playback disabled"); info->audio = NO_AUDIO; return 0; @@ -2752,6 +2996,8 @@ int vj_perform_audio_start(veejay_t * info) return 0; } + vj_audio_jack_start_backoff_reset(); + if(vj_jack_has_input()) veejay_msg(VEEJAY_MSG_DEBUG,"[AUDIO] Jack audio playback started with capture input ports"); else @@ -3016,6 +3262,23 @@ static void vj_perform_apply_first(veejay_t *info,performer_t *p, vjp_kf *todo_i return; } +#ifdef HAVE_JACK + if(info && info->settings) + { +#ifdef _OPENMP +#pragma omp critical(vj_perform_audio_beat_auto_apply) +#endif + { + vj_audio_beat_auto_modulate_args(&info->settings->audio_beat, + entry, + e, + args, + n_params, + n_frame); + } + } +#endif + if( rgb ) { if(!vj_perform_format_changed_rgb(p, frames[0])) { @@ -5761,6 +6024,7 @@ static int vj_perform_record_audio_frame( int sync_mode = atomic_load_int(&settings->audio_sync.mode); if(sync_mode == VJ_AUDIO_SYNC_MODE_LIVE_EXTERNAL || sync_mode == VJ_AUDIO_SYNC_MODE_MONITOR || + sync_mode == VJ_AUDIO_SYNC_MODE_MONITOR_TRICKPLAY || sync_mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE) return vj_perform_record_audio_from_beat(info, p, el); } @@ -6604,9 +6868,11 @@ static int vj_external_audio_history_ready(performer_t *p, int needed_frames, in static int vj_external_audio_min_ready_frames(int dst_frames, double transport_rate, int sample_rate); static void vj_external_audio_store_tail(performer_t *p, const uint8_t *buf, int frames, int frame_bytes); static void vj_external_audio_smooth_block_start(performer_t *p, uint8_t *buf, int frames, int frame_bytes, int force_edge); +static void vj_external_audio_smooth_block_start_live_reverse(performer_t *p, uint8_t *buf, int frames, int frame_bytes, int sample_rate); static int vj_external_audio_render_transport(performer_t *p, uint8_t *dst, int dst_frames, int frame_bytes, double transport_rate, int sample_rate); static int vj_external_audio_render_tape_deck(performer_t *p, uint8_t *dst, int dst_frames, int frame_bytes, double deck_rate, int reverse, int sample_rate); -static int vj_external_audio_render_reverse_deck(performer_t *p, uint8_t *dst, int dst_frames, int frame_bytes, double deck_rate, int sample_rate); +static int vj_external_audio_render_reverse_deck(performer_t *p, uint8_t *dst, int dst_frames, int frame_bytes, double deck_rate, int sample_rate, int latency_ms, int min_history_ms, int *edge_reset); +static int vj_external_audio_render_live_reverse_deck(performer_t *p, uint8_t *dst, int dst_frames, int frame_bytes, double deck_rate, int sample_rate, int latency_ms, int min_history_ms, int *edge_reset); static void vj_perform_feed_beat_analysis(veejay_t *info, const uint8_t *buf, int frames, int frame_bytes, editlist *el); static double vj_perform_track_align_transport_fps(veejay_t *info, editlist *el) { @@ -7119,6 +7385,25 @@ int vj_perform_queue_audio_chunk_ext( if (frame_bytes <= 0) return 0; +#ifdef HAVE_JACK + { + int sync_enabled = vj_audio_sync_is_enabled(&settings->audio_sync); + int sync_mode = atomic_load_int(&settings->audio_sync.mode); + + if(sync_enabled && + sync_mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW && + vj_audio_sync_get_target_mode(&settings->audio_sync) == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) + { + vj_perform_feed_clip_target_clock(info, + p, + el, + target_frame, + client_frames_to_write, + frame_bytes); + } + } +#endif + #ifdef HAVE_JACK { int guard_seq = 0; @@ -7172,11 +7457,15 @@ int vj_perform_queue_audio_chunk_ext( const int external_playback_mode = - (sync_mode == VJ_AUDIO_SYNC_MODE_MONITOR || - sync_mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || - sync_mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN); + (sync_mode != VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW) && + vj_audio_sync_mode_uses_external_playback(sync_mode); - const int sync_source_now = atomic_load_int(&settings->audio_sync.source); + const int transport_driven_external_playback = + external_playback_mode && + vj_audio_sync_mode_uses_transport_driven_playback(sync_mode); + + int sync_source_now = atomic_load_int(&settings->audio_sync.source); + int sync_reset_seq_now = atomic_load_int(&settings->audio_sync.reset_seq); const int source_wants_jack_playback = (audio_source == VJ_RECORD_AUDIO_SOURCE_BEAT_JACK && @@ -7197,23 +7486,7 @@ int vj_perform_queue_audio_chunk_ext( route_sfd <= 1 && vj_perform_external_jack_rate_is_neutral(effective_rate)); - const int monitor_neutral_passthrough = - (sync_mode == VJ_AUDIO_SYNC_MODE_MONITOR && - monitor_neutral_forward); - - const int track_align_neutral_passthrough = - (sync_mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN && - monitor_neutral_forward); - - const int direct_jack_passthrough = - (sync_enabled && - sync_source_now == VJ_AUDIO_SYNC_SOURCE_JACK && - (monitor_neutral_passthrough || track_align_neutral_passthrough) && - !(p && p->external_audio_transport_active)); - - - vj_jack_set_input_passthrough(direct_jack_passthrough && - !atomic_load_int(&settings->audio_mute)); + int direct_jack_passthrough = 0; if (sync_enabled && sync_mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN && @@ -7227,17 +7500,6 @@ int vj_perform_queue_audio_chunk_ext( ); } - if(sync_enabled && sync_mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW && - vj_audio_sync_get_target_mode(&settings->audio_sync) == VJ_AUDIO_SYNC_TARGET_CURRENT_CLIP) - { - vj_perform_feed_clip_target_clock(info, - p, - el, - target_frame, - client_frames_to_write, - frame_bytes); - } - if (source_wants_jack_playback || auto_wants_external || mode_wants_external_audio) { const size_t out_bytes = (size_t)client_frames_to_write * (size_t)frame_bytes; @@ -7253,16 +7515,6 @@ int vj_perform_queue_audio_chunk_ext( if (client_rate <= 0) client_rate = el->audio_rate; - if (p) { - int sync_key = ((sync_mode & 0xff) << 8) | (source & 0xff); - if (p->external_audio_last_sync_key != sync_key || - p->external_audio_history_frame_bytes != frame_bytes) - { - vj_external_audio_history_reset(p, frame_bytes); - p->external_audio_last_sync_key = sync_key; - } - } - if (source_wants_jack_playback) { @@ -7273,9 +7525,60 @@ int vj_perform_queue_audio_chunk_ext( } sync_mode = atomic_load_int(&settings->audio_sync.mode); + sync_source_now = atomic_load_int(&settings->audio_sync.source); + sync_reset_seq_now = atomic_load_int(&settings->audio_sync.reset_seq); + + if (p) { + int key_mode = sync_mode; + int key_reset = sync_reset_seq_now & 0xffff; + + if(sync_mode == VJ_AUDIO_SYNC_MODE_MONITOR || + sync_mode == VJ_AUDIO_SYNC_MODE_MONITOR_TRICKPLAY) + { + key_mode = VJ_AUDIO_SYNC_MODE_MONITOR; + key_reset = 0; + } + + int sync_key = (key_reset << 16) | + ((key_mode & 0xff) << 8) | + (sync_source_now & 0xff); + int old_sync_key = p->external_audio_last_sync_key; + if (old_sync_key != sync_key || + p->external_audio_history_frame_bytes != frame_bytes) + { + vj_external_audio_history_reset(p, frame_bytes); + p->external_audio_last_sync_key = sync_key; + } + } + + { + const int clean_direct_monitor = + vj_audio_sync_mode_is_clean_monitor(sync_mode); + const int direct_monitor_passthrough = clean_direct_monitor; + const int direct_track_align_passthrough = + (sync_mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN && + monitor_neutral_forward); + const int transport_blocks_passthrough = + (p && p->external_audio_transport_active && + !clean_direct_monitor); + + direct_jack_passthrough = + (sync_enabled && + sync_source_now == VJ_AUDIO_SYNC_SOURCE_JACK && + (direct_monitor_passthrough || direct_track_align_passthrough) && + !transport_blocks_passthrough); + + if(direct_jack_passthrough && clean_direct_monitor && p) + p->external_audio_transport_active = 0; + } + + vj_jack_set_input_passthrough(direct_jack_passthrough && + !atomic_load_int(&settings->audio_mute)); + if (sync_mode != VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE && sync_mode != VJ_AUDIO_SYNC_MODE_TRACK_ALIGN && - sync_mode != VJ_AUDIO_SYNC_MODE_MONITOR) + sync_mode != VJ_AUDIO_SYNC_MODE_MONITOR && + sync_mode != VJ_AUDIO_SYNC_MODE_MONITOR_TRICKPLAY) { @@ -7445,10 +7748,7 @@ int vj_perform_queue_audio_chunk_ext( if (external_frames <= 0) { - vj_external_audio_output_latest_or_silence(p, - audio_payload_chunk, - client_frames_to_write, - frame_bytes); + veejay_memset(audio_payload_chunk, 0, out_bytes); if (p && p->external_audio_transport_active) { vj_audio_declick_apply(p, audio_payload_chunk, @@ -7465,7 +7765,7 @@ int vj_perform_queue_audio_chunk_ext( frame_bytes, 1); p->external_audio_transport_active = 0; - } else { + } else if (p) { vj_external_audio_store_tail(p, audio_payload_chunk, client_frames_to_write, @@ -7490,7 +7790,10 @@ int vj_perform_queue_audio_chunk_ext( const double speed_dir = (speed < 0) ? -1.0 : ((speed > 0) ? 1.0 : 0.0); const int speed_mag = (speed_abs < 1) ? 1 : speed_abs; double transport_rate = speed_dir * (double)speed_mag * effective_rate; - const int wants_transport = direct_jack_passthrough ? 0 : external_transport_requested; + const int wants_transport = + (transport_driven_external_playback && !direct_jack_passthrough) + ? external_transport_requested + : 0; if (!wants_transport || !p) { const int was_transporting = (p && p->external_audio_transport_active); @@ -7530,35 +7833,46 @@ int vj_perform_queue_audio_chunk_ext( if (speed == 0 || fabs(transport_rate) < 0.0001) { veejay_memset(audio_payload_chunk, 0, out_bytes); + vj_external_audio_smooth_block_start(p, + audio_payload_chunk, + client_frames_to_write, + frame_bytes, + 1); p->external_audio_transport_active = 0; - p->external_audio_prev_valid = 0; return client_frames_to_write; } - if(sync_mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE && speed < 0) { + if(speed < 0) { double reverse_rate = fabs(transport_rate); int rendered = 0; - + int edge_reset = 0; if(reverse_rate < 1.0) reverse_rate = 1.0; if(reverse_rate > (double)MAX_SPEED) reverse_rate = (double)MAX_SPEED; - rendered = vj_external_audio_render_reverse_deck(p, - audio_payload_chunk, - client_frames_to_write, - frame_bytes, - reverse_rate, - client_rate); + p->external_audio_live_reverse_valid = 0; + + rendered = vj_external_audio_render_reverse_deck( + p, + audio_payload_chunk, + client_frames_to_write, + frame_bytes, + reverse_rate, + client_rate, + VJ_EXTERNAL_AUDIO_REVERSE_LATENCY_MS, + 0, + &edge_reset + ); if(rendered > 0) { vj_external_audio_smooth_block_start(p, audio_payload_chunk, client_frames_to_write, frame_bytes, - 0); + edge_reset); vj_audio_declick_observe(p, audio_payload_chunk, client_frames_to_write, @@ -7994,6 +8308,9 @@ static void vj_external_audio_history_reset(performer_t *p, int frame_bytes) p->external_audio_history_abs_write = 0; p->external_audio_read_pos = 0.0; p->external_audio_read_vel = 0.0; + p->external_audio_live_reverse_valid = 0; + p->external_audio_live_reverse_start = 0; + p->external_audio_live_reverse_end = 0; p->external_audio_transport_active = 0; p->external_audio_last_speed = 0; p->external_audio_last_rate_key = 0; @@ -8309,7 +8626,10 @@ static int vj_external_audio_render_reverse_deck(performer_t *p, int dst_frames, int frame_bytes, double deck_rate, - int sample_rate) + int sample_rate, + int latency_ms, + int min_history_ms, + int *edge_reset) { if(!p || !dst || !p->external_audio_history || dst_frames <= 0 || frame_bytes <= 0 || (frame_bytes & 1) || @@ -8326,6 +8646,17 @@ static int vj_external_audio_render_reverse_deck(performer_t *p, if(sample_rate <= 0) sample_rate = 44100; + if(edge_reset) + *edge_reset = 0; + + if(min_history_ms > 0) { + int min_history_frames = (sample_rate * min_history_ms) / 1000; + if(min_history_frames < dst_frames * 4) + min_history_frames = dst_frames * 4; + if(avail_frames < min_history_frames) + return 0; + } + if(deck_rate < 1.0) deck_rate = 1.0; if(deck_rate > (double)MAX_SPEED) @@ -8338,9 +8669,9 @@ static int vj_external_audio_render_reverse_deck(performer_t *p, return 0; double guard_old = (double)oldest + 8.0; - int latency = sample_rate / 2; - if(latency < dst_frames * 8) - latency = dst_frames * 8; + int latency = (latency_ms > 0) ? ((sample_rate * latency_ms) / 1000) : (sample_rate / 2); + if(latency < dst_frames * 4) + latency = dst_frames * 4; double safe_new = (double)newest - (double)latency; if(safe_new <= guard_old + (double)(dst_frames * 4)) { @@ -8382,10 +8713,10 @@ static int vj_external_audio_render_reverse_deck(performer_t *p, start = safe_new; if(end < guard_old) { - - start = safe_new; end = start + p->external_audio_read_vel * (double)(dst_frames - 1); + if(edge_reset) + *edge_reset = 1; } if(end < guard_old || start > (double)newest - 4.0) @@ -8448,6 +8779,252 @@ static int vj_external_audio_render_reverse_deck(performer_t *p, } + +static int vj_external_audio_render_live_reverse_deck(performer_t *p, + uint8_t *dst, + int dst_frames, + int frame_bytes, + double deck_rate, + int sample_rate, + int latency_ms, + int min_history_ms, + int *edge_reset) +{ + if(!p || !dst || !p->external_audio_history || dst_frames <= 0 || + frame_bytes <= 0 || (frame_bytes & 1) || + p->external_audio_history_frame_bytes != frame_bytes) + return 0; + + const int words = frame_bytes / 2; + const int cap_frames = (int)(p->external_audio_history_capacity / (size_t)frame_bytes); + const int avail_frames = (int)(p->external_audio_history_filled / (size_t)frame_bytes); + + if(cap_frames <= 8 || avail_frames <= dst_frames + 8) + return 0; + + if(sample_rate <= 0) + sample_rate = 44100; + + if(edge_reset) + *edge_reset = 0; + + if(deck_rate < 1.0) + deck_rate = 1.0; + if(deck_rate > (double)MAX_SPEED) + deck_rate = (double)MAX_SPEED; + + if(min_history_ms > 0) { + int min_history_frames = (sample_rate * min_history_ms) / 1000; + if(min_history_frames < dst_frames * 4) + min_history_frames = dst_frames * 4; + if(avail_frames < min_history_frames) + return 0; + } + + const long long newest = p->external_audio_history_abs_write - 1; + const long long oldest = p->external_audio_history_abs_write - (long long)avail_frames; + + if(newest <= oldest + (long long)(dst_frames * 8)) + return 0; + + int latency = (latency_ms > 0) ? ((sample_rate * latency_ms) / 1000) : (sample_rate / 4); + if(latency < dst_frames * 4) + latency = dst_frames * 4; + + const long long guard_old = oldest + 16; + long long safe_new = newest - (long long)latency; + + if(safe_new <= guard_old + (long long)(dst_frames * 4)) { + latency = dst_frames * 4; + safe_new = newest - (long long)latency; + } + + if(safe_new <= guard_old + (long long)(dst_frames * 4)) + return 0; + + int wanted_window = (sample_rate * VJ_EXTERNAL_AUDIO_LIVE_REVERSE_WINDOW_MS) / 1000; + int min_window = (sample_rate * VJ_EXTERNAL_AUDIO_LIVE_REVERSE_MIN_WINDOW_MS) / 1000; + int needed = (int)ceil((double)dst_frames * deck_rate) + 32; + int max_window = (int)(safe_new - guard_old + 1); + int window; + + if(min_window < needed * 3) + min_window = needed * 3; + + if(wanted_window < min_window) + wanted_window = min_window; + + window = wanted_window; + if(window > max_window) + window = max_window; + + if(window < needed * 2) + return 0; + + const int rate_key = (int)(deck_rate * 1000.0 + 0.5); + const int mode_key = -4100; + const double target_vel = -deck_rate; + double next_end = p->external_audio_read_pos + p->external_audio_read_vel * (double)(dst_frames - 1); + + if(!p->external_audio_transport_active || + p->external_audio_last_speed != mode_key || + !p->external_audio_live_reverse_valid || + p->external_audio_read_pos > (double)p->external_audio_live_reverse_end + 2.0 || + next_end < (double)p->external_audio_live_reverse_start - (double)dst_frames || + p->external_audio_live_reverse_start < guard_old || + p->external_audio_live_reverse_end > safe_new + 2) + { + long long seg_end = safe_new; + long long seg_start = seg_end - (long long)window + 1; + if(seg_start < guard_old) + seg_start = guard_old; + + if(seg_end <= seg_start + (long long)(needed * 2)) + return 0; + + p->external_audio_live_reverse_start = seg_start; + p->external_audio_live_reverse_end = seg_end; + p->external_audio_live_reverse_valid = 1; + p->external_audio_read_pos = (double)seg_end; + p->external_audio_read_vel = target_vel; + p->external_audio_transport_active = 1; + p->external_audio_last_speed = mode_key; + p->external_audio_last_rate_key = rate_key; + if(edge_reset) + *edge_reset = 1; + } + else { + double alpha = 0.08; + if(fabs(target_vel - p->external_audio_read_vel) > 1.0) + alpha = 0.18; + p->external_audio_read_vel += (target_vel - p->external_audio_read_vel) * alpha; + p->external_audio_last_rate_key = rate_key; + } + + if(p->external_audio_read_vel > -0.01) + p->external_audio_read_vel = target_vel; + + int splice_frames = (sample_rate * VJ_EXTERNAL_AUDIO_LIVE_REVERSE_EDGE_FADE_MS) / 1000; + if(splice_frames < dst_frames * 2) + splice_frames = dst_frames * 2; + if(splice_frames > window / 4) + splice_frames = window / 4; + if(splice_frames < dst_frames) + splice_frames = dst_frames; + + next_end = p->external_audio_read_pos + p->external_audio_read_vel * (double)(dst_frames - 1); + + long long new_seg_end = safe_new; + long long new_seg_start = new_seg_end - (long long)window + 1; + if(new_seg_start < guard_old) + new_seg_start = guard_old; + + const int can_splice = + (new_seg_end > new_seg_start + (long long)(needed * 2)) && + (new_seg_end > p->external_audio_live_reverse_end + (long long)splice_frames); + + if(can_splice && + next_end < (double)p->external_audio_live_reverse_start + (double)splice_frames) + { + const int16_t *hist = (const int16_t*)p->external_audio_history; + int16_t *out = (int16_t*)dst; + const double old_start = p->external_audio_read_pos; + const double new_start = (double)new_seg_end; + const double denom = (dst_frames > 1) ? (double)(dst_frames - 1) : 1.0; + + for(int i = 0; i < dst_frames; i++) { + double t = (double)i / denom; + double w = t * t * (3.0 - (2.0 * t)); + double old_pos = old_start + p->external_audio_read_vel * (double)i; + double new_pos = new_start + p->external_audio_read_vel * (double)i; + const int bo = i * words; + + if(old_pos < (double)p->external_audio_live_reverse_start) + old_pos = (double)p->external_audio_live_reverse_start; + else if(old_pos > (double)p->external_audio_live_reverse_end) + old_pos = (double)p->external_audio_live_reverse_end; + + if(new_pos < (double)new_seg_start) + new_pos = (double)new_seg_start; + else if(new_pos > (double)new_seg_end) + new_pos = (double)new_seg_end; + + for(int c = 0; c < words; c++) { + int16_t a = vj_external_audio_sample_history_s16( + hist, cap_frames, words, + p->external_audio_live_reverse_start, + p->external_audio_live_reverse_end, + old_pos, c + ); + int16_t b = vj_external_audio_sample_history_s16( + hist, cap_frames, words, + new_seg_start, + new_seg_end, + new_pos, c + ); + out[bo + c] = vj_external_audio_lerp_s16(a, b, w); + } + } + + p->external_audio_live_reverse_start = new_seg_start; + p->external_audio_live_reverse_end = new_seg_end; + p->external_audio_read_pos = new_start + p->external_audio_read_vel * (double)dst_frames; + p->external_audio_read_vel += (target_vel - p->external_audio_read_vel) * 0.08; + p->external_audio_transport_active = 1; + p->external_audio_last_speed = mode_key; + p->external_audio_last_rate_key = rate_key; + return dst_frames; + } + + if(next_end < (double)p->external_audio_live_reverse_start + 2.0) { + long long seg_end = safe_new; + long long seg_start = seg_end - (long long)window + 1; + if(seg_start < guard_old) + seg_start = guard_old; + + if(seg_end <= seg_start + (long long)(needed * 2)) + return 0; + + p->external_audio_live_reverse_start = seg_start; + p->external_audio_live_reverse_end = seg_end; + p->external_audio_read_pos = (double)seg_end; + p->external_audio_read_vel = target_vel; + next_end = p->external_audio_read_pos + p->external_audio_read_vel * (double)(dst_frames - 1); + if(edge_reset) + *edge_reset = 1; + } + + if(next_end < (double)p->external_audio_live_reverse_start || + p->external_audio_read_pos > (double)p->external_audio_live_reverse_end + 2.0) + return 0; + + const int16_t *hist = (const int16_t*)p->external_audio_history; + int16_t *out = (int16_t*)dst; + + for(int i = 0; i < dst_frames; i++) { + double pos = p->external_audio_read_pos + p->external_audio_read_vel * (double)i; + const int bo = i * words; + + if(pos < (double)p->external_audio_live_reverse_start) + pos = (double)p->external_audio_live_reverse_start; + else if(pos > (double)p->external_audio_live_reverse_end) + pos = (double)p->external_audio_live_reverse_end; + + for(int c = 0; c < words; c++) { + out[bo + c] = vj_external_audio_sample_history_s16( + hist, cap_frames, words, + p->external_audio_live_reverse_start, + p->external_audio_live_reverse_end, + pos, c + ); + } + } + + p->external_audio_read_pos += p->external_audio_read_vel * (double)dst_frames; + return dst_frames; +} + + static int vj_external_audio_render_tape_deck(performer_t *p, uint8_t *dst, int dst_frames, @@ -8691,6 +9268,57 @@ static void vj_external_audio_smooth_block_start(performer_t *p, vj_external_audio_store_tail(p, buf, frames, frame_bytes); } + +static void vj_external_audio_smooth_block_start_live_reverse(performer_t *p, + uint8_t *buf, + int frames, + int frame_bytes, + int sample_rate) +{ + if(!p || !buf || frames <= 0 || frame_bytes <= 0 || (frame_bytes & 1)) + return; + + if(sample_rate <= 0) + sample_rate = 44100; + + if(!p->external_audio_prev_valid || + p->external_audio_prev_frame_bytes != frame_bytes || + frame_bytes > (int)sizeof(p->external_audio_prev_frame)) + { + vj_external_audio_store_tail(p, buf, frames, frame_bytes); + return; + } + + const int words = frame_bytes / 2; + const int16_t *prev = (const int16_t*)p->external_audio_prev_frame; + int16_t *out = (int16_t*)buf; + int fade = (sample_rate * VJ_EXTERNAL_AUDIO_LIVE_REVERSE_EDGE_FADE_MS) / 1000; + + if(fade < 160) + fade = 160; + if(fade > frames) + fade = frames; + + const double denom = (fade > 1) ? (double)(fade - 1) : 1.0; + for(int i = 0; i < fade; i++) { + double t = (double)i / denom; + double w = t * t * (3.0 - (2.0 * t)); + const int bo = i * words; + + for(int c = 0; c < words; c++) { + double mixed = ((1.0 - w) * (double)prev[c]) + + (w * (double)out[bo + c]); + int v = (int)((mixed >= 0.0) ? (mixed + 0.5) : (mixed - 0.5)); + if(v < -32768) v = -32768; + else if(v > 32767) v = 32767; + out[bo + c] = (int16_t)v; + } + } + + vj_external_audio_store_tail(p, buf, frames, frame_bytes); +} + + static int vj_external_audio_render_transport(performer_t *p, uint8_t *dst, int dst_frames, @@ -11937,7 +12565,7 @@ static char *osd_drift_indicator(double drift_s, double spvf) static char *osd_performance_indicator(double render_duration, double spvf) { - static char buf[128]; + static char buf[64]; static double ema_duration = -1.0; static double ema_load_pct = -1.0; @@ -11955,21 +12583,27 @@ static char *osd_performance_indicator(double render_duration, double spvf) { const double skip_threshold = 1.5 * spvf; const char *status; - if (ema_duration > skip_threshold) status = "OVLD"; + if (ema_duration > skip_threshold) status = "OVR"; else if (ema_duration > spvf) status = "LAG"; - else if (ema_duration > 0.85*spvf) status = "WARN"; - else status = " OK"; + else if (ema_duration > 0.85*spvf) status = "WRN"; + else status = "OK"; - char spark[11]; - int bars = (int)((ema_load_pct + 5) / 10.0); - if (bars > 10) bars = 10; - if (bars < 0) bars = 0; - for (int i = 0; i < 10; i++) spark[i] = (i < bars) ? '#' : '-'; - spark[10] = '\0'; + double load = ema_load_pct; + if (load < 0.0) + load = 0.0; + else if (load > 999.0) + load = 999.0; + + char spark[7]; + int bars = (int)((load + 8.33) / 16.66); + if (bars > 6) bars = 6; + if (bars < 0) bars = 0; + for (int i = 0; i < 6; i++) spark[i] = (i < bars) ? '#' : '-'; + spark[6] = '\0'; snprintf(buf, sizeof(buf), - "%-4s [%s] %2.4fs (%3.0f%%)", - status, spark, ema_duration, ema_load_pct); + "%s[%s] %.3fs %.0f%%", + status, spark, ema_duration, load); return buf; } @@ -12021,12 +12655,108 @@ static char *osd_xrun_indicator(long underruns, long xruns) } #ifdef HAVE_JACK +static const char *osd_audio_source_name(int source) +{ + switch(source) { + case VJ_RECORD_AUDIO_SOURCE_AUTO: return "auto"; + case VJ_RECORD_AUDIO_SOURCE_ORIGINAL: return "original"; + case VJ_RECORD_AUDIO_SOURCE_BEAT_JACK:return "external"; + case VJ_RECORD_AUDIO_SOURCE_SILENCE: return "silence"; + default: return "unknown"; + } +} + +static const char *osd_sync_mode_name(int mode) +{ + switch(mode) { + case VJ_AUDIO_SYNC_MODE_LIVE_EXTERNAL: return "ana"; + case VJ_AUDIO_SYNC_MODE_MONITOR: return "mon"; + case VJ_AUDIO_SYNC_MODE_MONITOR_TRICKPLAY: return "trick"; + case VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW: return "follow"; + case VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE: return "bridge"; + case VJ_AUDIO_SYNC_MODE_TRACK_ALIGN: return "align"; + case 0: + default: return "off"; + } +} + +static const char *osd_sync_source_name(int source) +{ + switch(source) { + case VJ_AUDIO_SYNC_SOURCE_JACK: return "jack"; + case VJ_AUDIO_SYNC_SOURCE_WAV_FILE: return "wav"; + case VJ_AUDIO_SYNC_SOURCE_PUSH: return "push"; + default: return "none"; + } +} + +static const char *osd_bridge_state_name(int state) +{ + switch(state) { + case VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_SOURCE: return "wait-src"; + case VJ_AUDIO_SYNC_BRIDGE_STATE_WAIT_TARGET: return "wait-tgt"; + case VJ_AUDIO_SYNC_BRIDGE_STATE_LOCKED: return "lock"; + case VJ_AUDIO_SYNC_BRIDGE_STATE_HOLD: return "hold"; + case VJ_AUDIO_SYNC_BRIDGE_STATE_FALLBACK: return "fb"; + case VJ_AUDIO_SYNC_BRIDGE_STATE_IDLE: + default: return "idle"; + } +} + +static const char *osd_track_state_name(int state) +{ + switch(state) { + case VJ_AUDIO_SYNC_TRACK_STATE_WAIT_SOURCE: return "wait-src"; + case VJ_AUDIO_SYNC_TRACK_STATE_WAIT_TARGET: return "wait-tgt"; + case VJ_AUDIO_SYNC_TRACK_STATE_SEARCHING: return "search"; + case VJ_AUDIO_SYNC_TRACK_STATE_LOCKED: return "lock"; + case VJ_AUDIO_SYNC_TRACK_STATE_HOLD: return "hold"; + case VJ_AUDIO_SYNC_TRACK_STATE_FALLBACK: return "fb"; + case VJ_AUDIO_SYNC_TRACK_STATE_IDLE: + default: return "idle"; + } +} + +static void osd_bpm_text(char *dst, size_t dst_len, float bpm) +{ + if(!dst || dst_len == 0) + return; + if(bpm >= 20.0f && bpm <= 300.0f) + snprintf(dst, dst_len, "%.1f", (double)bpm); + else + snprintf(dst, dst_len, "-"); +} + +static double osd_effective_visual_fps(veejay_t *info, const vj_audio_sync_snapshot_t *snap) +{ + double base_fps = 0.0; + double ratio = 1.0; + + if(info && info->settings && info->settings->output_fps > 0.0f) + base_fps = (double)info->settings->output_fps; + else if(info && info->current_edit_list && info->current_edit_list->video_fps > 0.0) + base_fps = info->current_edit_list->video_fps; + + if(base_fps <= 0.0) + base_fps = 25.0; + + if(snap && snap->mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW && + snap->bridge_correction >= 0.25 && snap->bridge_correction <= 4.0) + ratio = snap->bridge_correction; + + return base_fps * ratio; +} + static void osd_audio_clock_line(veejay_t *info, char *dst, size_t dst_len) { video_playback_setup *settings; vj_audio_sync_snapshot_t snap; int have_sync = 0; - char tq[128] = ""; + const int audio_src = info && info->settings ? atomic_load_int(&info->settings->audio_osd.last_src) : 0; + char source_bpm[24]; + char target_bpm[24]; + char engine[192]; + char queue[96]; if(!dst || dst_len == 0) return; @@ -12038,41 +12768,169 @@ static void osd_audio_clock_line(veejay_t *info, char *dst, size_t dst_len) settings = info->settings; have_sync = vj_audio_sync_get_snapshot(&settings->audio_sync, &snap); - if(have_sync && snap.enabled) { - snprintf(tq, sizeof(tq), - " tq=%d/%dms tqdrop=%ld/%ldfr lock=%ld", + const int speed = atomic_load_int(&settings->audio_osd.last_speed); + const int sfd = atomic_load_int(&settings->audio_osd.last_sfd); + const int q_ms = atomic_load_int(&settings->audio_osd.last_qdepth_ms); + const int sleep_ms = atomic_load_int(&settings->audio_osd.last_sleep_ms); + const int written = atomic_load_int(&settings->audio_osd.last_written); + const int decoded = atomic_load_int(&settings->audio_osd.last_decoded); + const int pending = atomic_load_int(&settings->audio_osd.last_pending); + const long long drop_pending = atomic_load_long_long(&settings->audio_osd.prod_pending_drop_frames); + const long long drop_video = atomic_load_long_long(&settings->audio_osd.prod_video_drop_frames); + const long long slow = atomic_load_long_long(&settings->audio_osd.prod_slow_renders); + const long long anomalies = atomic_load_long_long(&settings->audio_osd.prod_anomalies); + + if(drop_pending || drop_video || slow || anomalies) { + snprintf(engine, sizeof(engine), + "A:%s v=%d sf=%d q=%d sl=%d io=%d/%d p=%d d=%lld/%lld slow=%lld err=%lld", + osd_audio_source_name(audio_src), + speed, + sfd, + q_ms, + sleep_ms, + written, + decoded, + pending, + drop_pending, + drop_video, + slow, + anomalies); + } else { + snprintf(engine, sizeof(engine), + "A:%s v=%d sf=%d q=%d sl=%d io=%d/%d p=%d", + osd_audio_source_name(audio_src), + speed, + sfd, + q_ms, + sleep_ms, + written, + decoded, + pending); + } + + if(!have_sync || !snap.enabled || snap.mode == 0) { + snprintf(dst, dst_len, "AUD %s", engine); + return; + } + + osd_bpm_text(source_bpm, sizeof(source_bpm), snap.bpm); + osd_bpm_text(target_bpm, sizeof(target_bpm), snap.target_bpm); + + queue[0] = '\0'; + if(snap.mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW || + snap.mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || + snap.mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN) + { + snprintf(queue, sizeof(queue), + " tq=%d/%d d=%ld/%ld", snap.target_queue_retained_ms, snap.target_queue_ring_ms, snap.target_queue_overflow_events + snap.target_queue_lock_drops, - snap.target_queue_dropped_frames, - snap.target_queue_lock_drops); + snap.target_queue_dropped_frames); } - snprintf(dst, dst_len, - "AUDIO src=%d sync=%d spd=%d sfd=%d w=%d/%d q=%dms slp=%dms wait=%lld z/s=%lld/%lld pend=%d pd=%lld vd=%lld qnull=%lld slow=%lld anom=%lld%s", - atomic_load_int(&settings->audio_osd.last_src), - atomic_load_int(&settings->audio_osd.last_sync), - atomic_load_int(&settings->audio_osd.last_speed), - atomic_load_int(&settings->audio_osd.last_sfd), - atomic_load_int(&settings->audio_osd.last_written), - atomic_load_int(&settings->audio_osd.last_decoded), - atomic_load_int(&settings->audio_osd.last_qdepth_ms), - atomic_load_int(&settings->audio_osd.last_sleep_ms), - atomic_load_long_long(&settings->audio_osd.prod_waits), - atomic_load_long_long(&settings->audio_osd.prod_write_zero), - atomic_load_long_long(&settings->audio_osd.prod_write_short), - atomic_load_int(&settings->audio_osd.last_pending), - atomic_load_long_long(&settings->audio_osd.prod_pending_drop_frames), - atomic_load_long_long(&settings->audio_osd.prod_video_drop_frames), - atomic_load_long_long(&settings->audio_osd.prod_queue_nulls), - atomic_load_long_long(&settings->audio_osd.prod_slow_renders), - atomic_load_long_long(&settings->audio_osd.prod_anomalies), - tq); + switch(snap.mode) { + case VJ_AUDIO_SYNC_MODE_LIVE_EXTERNAL: + snprintf(dst, dst_len, + "S:ana %s %s r=%d ch=%d %dHz bpm=%s c=%d ph=%d l=%d tr=%d | %s", + osd_sync_source_name(snap.source), + snap.open ? "open" : "closed", + snap.running, + snap.channels, + snap.sample_rate, + source_bpm, + (int)(snap.confidence * 100.0f + 0.5f), + (int)(snap.beat_phase * 100.0f + 0.5f), + (int)(snap.level * 100.0f + 0.5f), + (int)(snap.transient * 100.0f + 0.5f), + engine); + break; + + case VJ_AUDIO_SYNC_MODE_MONITOR: + case VJ_AUDIO_SYNC_MODE_MONITOR_TRICKPLAY: + snprintf(dst, dst_len, + "S:%s %s %s r=%d ch=%d %dHz bpm=%s c=%d l=%d tr=%d | %s", + osd_sync_mode_name(snap.mode), + osd_sync_source_name(snap.source), + snap.open ? "open" : "closed", + snap.running, + snap.channels, + snap.sample_rate, + source_bpm, + (int)(snap.confidence * 100.0f + 0.5f), + (int)(snap.level * 100.0f + 0.5f), + (int)(snap.transient * 100.0f + 0.5f), + engine); + break; + + case VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW: + snprintf(dst, dst_len, + "S:follow %s %s src=%s/%d clip=%s/%d r=%.3f pull=%d fps=%.2f %s%s | %s", + osd_sync_source_name(snap.source), + snap.open ? "open" : "closed", + source_bpm, + (int)(snap.confidence * 100.0f + 0.5f), + target_bpm, + (int)(snap.target_confidence * 100.0f + 0.5f), + snap.bridge_correction, + snap.max_correction_pct, + osd_effective_visual_fps(info, &snap), + osd_bridge_state_name(snap.bridge_state), + queue, + engine); + break; + + case VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE: + snprintf(dst, dst_len, + "S:bridge %s %s src=%s/%d tgt=%s/%d r=%.3f max=%d %s%s | %s", + osd_sync_source_name(snap.source), + snap.open ? "open" : "closed", + source_bpm, + (int)(snap.confidence * 100.0f + 0.5f), + target_bpm, + (int)(snap.target_confidence * 100.0f + 0.5f), + snap.bridge_correction, + snap.max_correction_pct, + osd_bridge_state_name(snap.bridge_state), + queue, + engine); + break; + + case VJ_AUDIO_SYNC_MODE_TRACK_ALIGN: + snprintf(dst, dst_len, + "S:align %s %s src=%s/%d clip=%s/%d %s off=%+d c=%d ppm=%+d %s%s | %s", + osd_sync_source_name(snap.source), + snap.open ? "open" : "closed", + source_bpm, + (int)(snap.confidence * 100.0f + 0.5f), + target_bpm, + (int)(snap.target_confidence * 100.0f + 0.5f), + snap.track_align_locked ? "lock" : "srch", + snap.track_align_offset_ms, + snap.track_align_confidence_pct, + snap.track_align_correction_ppm, + osd_track_state_name(snap.track_align_state), + queue, + engine); + break; + + default: + snprintf(dst, dst_len, + "S:%s %s %s r=%d bpm=%s c=%d | %s", + osd_sync_mode_name(snap.mode), + osd_sync_source_name(snap.source), + snap.open ? "open" : "closed", + snap.running, + source_bpm, + (int)(snap.confidence * 100.0f + 0.5f), + engine); + break; + } } static char *vj_perform_audio_clock_osd_status(veejay_t *info) { - char line[512]; + char line[1024]; size_t len; char *status_str; @@ -12569,6 +13427,16 @@ static void vj_perform_end_transition(veejay_t *info, int mode, int sample) int target_id = sample; int target_slot = settings->transition.seq_index; + if (!vj_perform_sequence_transition_still_valid(info)) { + veejay_msg(VEEJAY_MSG_DEBUG, + "[SEQ] stale completed transition ignored slot=%d next=%d", + target_slot, + settings->transition.next_id); + vj_perform_reset_transition(info); + settings->transition.ready = 0; + return; + } + if (info->seq->active && target_slot >= 0 && target_slot < MAX_SEQUENCES && @@ -12927,6 +13795,276 @@ static void vj_perform_color_vibrancy(uint8_t *U, uint8_t *V, const int len, int } } + +#ifndef VJ_AUDIO_BEAT_ACTION_BREAK_BEAT +#define VJ_AUDIO_BEAT_ACTION_BREAK_BEAT 4 +#endif + +static inline int vj_scene_q15_from_255(double v) +{ + if(v <= 0.0) + return 0; + if(v >= 255.0) + return 32767; + return (int)((v * (32767.0 / 255.0)) + 0.5); +} + +static inline int vj_perform_breakbeat_scene_active(video_playback_setup *settings) +{ +#ifdef HAVE_JACK + if(!settings) + return 0; + + if(!atomic_load_int(&settings->audio_beat.enabled)) + return 0; + + return atomic_load_int(&settings->audio_beat.action_mode) == VJ_AUDIO_BEAT_ACTION_BREAK_BEAT; +#else + (void)settings; + return 0; +#endif +} + +static void vj_perform_scene_detect_idle(video_playback_setup *settings) +{ + vj_scene_detect_t *sd; + + if(!settings) + return; + + sd = &settings->scene_detect; + sd->prev_ready = 0; + atomic_store_int(&sd->valid, 0); + atomic_store_int(&sd->hard_cut, 0); + atomic_store_int(&sd->cut_score_q15, 0); + atomic_store_int(&sd->diff_q15, 0); + atomic_store_int(&sd->scene_age_frames, 0); + atomic_store_long_long(&sd->last_cut_frame, -1); +} + +static void vj_perform_scene_detect_frame(video_playback_setup *settings, VJFrame *frame, long long frame_num) +{ + vj_scene_detect_t *sd; + const uint8_t *Y; + uint8_t cur[VJ_SCENE_ANALYSIS_MAX_PIXELS]; + int w; + int h; + int len; + int aw; + int ah; + int count; + int stride; + double sum = 0.0; + + if(!settings || !frame || !frame->data[0] || frame->width <= 0 || frame->height <= 0 || frame->len <= 0) + return; + + if(!vj_perform_breakbeat_scene_active(settings)) + { + vj_perform_scene_detect_idle(settings); + return; + } + + sd = &settings->scene_detect; + Y = frame->data[0]; + w = frame->width; + h = frame->height; + len = frame->len; + stride = (frame->stride[0] > 0 && frame->stride[0] * h <= len) ? frame->stride[0] : w; + + if(w >= h) + { + aw = VJ_SCENE_ANALYSIS_MAX_SIDE; + ah = (h * VJ_SCENE_ANALYSIS_MAX_SIDE + (w / 2)) / w; + } + else + { + ah = VJ_SCENE_ANALYSIS_MAX_SIDE; + aw = (w * VJ_SCENE_ANALYSIS_MAX_SIDE + (h / 2)) / h; + } + + if(aw < 8) + aw = 8; + else if(aw > VJ_SCENE_ANALYSIS_MAX_SIDE) + aw = VJ_SCENE_ANALYSIS_MAX_SIDE; + + if(ah < 8) + ah = 8; + else if(ah > VJ_SCENE_ANALYSIS_MAX_SIDE) + ah = VJ_SCENE_ANALYSIS_MAX_SIDE; + + count = aw * ah; + if(count > VJ_SCENE_ANALYSIS_MAX_PIXELS) + count = VJ_SCENE_ANALYSIS_MAX_PIXELS; + + for(int y = 0; y < ah; y++) + { + int sy = ((y * 2 + 1) * h) / (ah * 2); + if(sy >= h) + sy = h - 1; + + for(int x = 0; x < aw; x++) + { + int sx = ((x * 2 + 1) * w) / (aw * 2); + int idx; + uint8_t v; + + if(sx >= w) + sx = w - 1; + + idx = sy * stride + sx; + if(idx < 0) + idx = 0; + else if(idx >= len) + idx = len - 1; + + v = Y[idx]; + cur[(y * aw) + x] = v; + sum += (double)v; + } + } + + if(sd->prev_ready && + atomic_load_int(&sd->analysis_w) == aw && + atomic_load_int(&sd->analysis_h) == ah) + { + double diff_sum = 0.0; + double prev_sum = 0.0; + double mean; + double prev_mean; + double mean_delta; + double diff; + double texture_diff; + double ema; + double var; + double sigma; + double floor_v; + double threshold; + double score_norm; + int age; + int hard_cut; + int scene_id; + int score_q15; + + for(int i = 0; i < count; i++) + { + int d = (int)cur[i] - (int)sd->prev[i]; + if(d < 0) + d = -d; + diff_sum += (double)d; + prev_sum += (double)sd->prev[i]; + } + + mean = sum / (double)count; + prev_mean = prev_sum / (double)count; + mean_delta = mean >= prev_mean ? mean - prev_mean : prev_mean - mean; + diff = diff_sum / (double)count; + texture_diff = diff - (mean_delta * 0.62); + if(texture_diff < 0.0) + texture_diff = 0.0; + + ema = sd->diff_ema; + var = sd->diff_var; + if(ema <= 0.001) + { + ema = texture_diff; + var = 16.0; + } + + if(var < 4.0) + var = 4.0; + + sigma = sqrt(var); + floor_v = ema * 1.35 + 2.0; + threshold = ema + sigma * 4.20 + 4.0; + if(threshold < 14.0) + threshold = 14.0; + if(threshold < floor_v + 6.0) + threshold = floor_v + 6.0; + + age = atomic_load_int(&sd->scene_age_frames); + scene_id = atomic_load_int(&sd->scene_id); + if(scene_id <= 0) + scene_id = 1; + + hard_cut = age >= 2 && + diff >= 16.0 && + texture_diff > threshold && + texture_diff > ema * 1.42; + + if(texture_diff <= floor_v) + score_norm = 0.0; + else + score_norm = (texture_diff - floor_v) / (threshold - floor_v); + + if(score_norm < 0.0) + score_norm = 0.0; + else if(score_norm > 1.0) + score_norm = 1.0; + + score_q15 = (int)(score_norm * 32767.0 + 0.5); + + if(hard_cut) + { + scene_id++; + age = 0; + score_q15 = 32767; + atomic_store_long_long(&sd->last_cut_frame, frame_num); + } + else + { + age++; + if(age < 0) + age = 0; + } + + { + double alpha = hard_cut ? 0.012 : 0.035; + double d = texture_diff - ema; + ema += alpha * d; + var = (1.0 - alpha) * (var + alpha * d * d); + if(var < 4.0) + var = 4.0; + } + + sd->diff_ema = ema; + sd->diff_var = var; + sd->mean_ema = sd->mean_ema <= 0.001 ? mean : (sd->mean_ema * 0.96 + mean * 0.04); + + atomic_store_int(&sd->scene_id, scene_id); + atomic_store_int(&sd->scene_age_frames, age); + atomic_store_int(&sd->hard_cut, hard_cut); + atomic_store_int(&sd->cut_score_q15, score_q15); + atomic_store_int(&sd->diff_q15, vj_scene_q15_from_255(texture_diff)); + atomic_store_int(&sd->mean_q15, vj_scene_q15_from_255(mean)); + } + else + { + int scene_id = atomic_load_int(&sd->scene_id); + if(scene_id <= 0) + scene_id = 1; + + sd->diff_ema = 0.0; + sd->diff_var = 16.0; + sd->mean_ema = sum / (double)count; + + atomic_store_int(&sd->scene_id, scene_id); + atomic_store_int(&sd->scene_age_frames, 0); + atomic_store_int(&sd->hard_cut, 0); + atomic_store_int(&sd->cut_score_q15, 0); + atomic_store_int(&sd->diff_q15, 0); + atomic_store_int(&sd->mean_q15, vj_scene_q15_from_255(sd->mean_ema)); + atomic_store_long_long(&sd->last_cut_frame, -1); + } + + veejay_memcpy(sd->prev, cur, (size_t)count); + sd->prev_ready = 1; + atomic_store_int(&sd->analysis_w, aw); + atomic_store_int(&sd->analysis_h, ah); + atomic_store_long_long(&sd->frame, frame_num); + atomic_store_int(&sd->valid, 1); +} + int vj_perform_queue_video_frame(veejay_t *info, VJFrame *dst) { performer_global_t *g = (performer_global_t*) info->performer; @@ -12980,6 +14118,15 @@ int vj_perform_queue_video_frame(veejay_t *info, VJFrame *dst) vj_perform_queue_video_frames( info, info->effect_frame1, info->effect_frame2, g->A, info->uc->sample_id, info->uc->playback_mode, cur_frame); int transition_enabled = atomic_load_int(&settings->transition.active) && atomic_load_int(&settings->transition.global_state); + if (transition_enabled && !vj_perform_sequence_transition_still_valid(info)) { + veejay_msg(VEEJAY_MSG_DEBUG, + "[SEQ] stale render transition dropped slot=%d next=%d", + settings->transition.seq_index, + settings->transition.next_id); + vj_perform_reset_transition(info); + transition_enabled = 0; + } + if (transition_enabled) { long long start = atomic_load_long_long(&settings->transition.start); long long end = atomic_load_long_long(&settings->transition.end); @@ -13024,6 +14171,8 @@ int vj_perform_queue_video_frame(veejay_t *info, VJFrame *dst) } } + vj_perform_scene_detect_frame(settings, info->effect_frame1, cur_frame); + vj_perform_render_font( info, settings, info->effect_frame1); if(!settings->composite) @@ -13105,11 +14254,9 @@ static int vj_perform_pingpong_turn_needs_reanchor(veejay_t *info) const int sync_mode = atomic_load_int(&settings->audio_sync.mode); - return sync_mode == VJ_AUDIO_SYNC_MODE_LIVE_EXTERNAL || - sync_mode == VJ_AUDIO_SYNC_MODE_MONITOR || - sync_mode == VJ_AUDIO_SYNC_MODE_TEMPO_BRIDGE || - sync_mode == VJ_AUDIO_SYNC_MODE_TRACK_ALIGN || - sync_mode == VJ_AUDIO_SYNC_MODE_TEMPO_FOLLOW; + return vj_audio_sync_mode_is_control_only(sync_mode) || + vj_audio_sync_mode_is_tempo_follow(sync_mode) || + vj_audio_sync_mode_uses_transport_driven_playback(sync_mode); #else (void)info; return 0; @@ -13124,7 +14271,7 @@ static void vj_perform_set_pingpong_turn_speed(veejay_t *info, int new_speed) video_playback_setup *settings = info->settings; if (vj_perform_pingpong_turn_needs_reanchor(info)) { - veejay_set_speed(info, new_speed, 1); + vj_perform_set_speed_beat_aware(info, new_speed, 1); return; } @@ -13362,7 +14509,7 @@ void vj_perform_inc_frame(veejay_t *info, int num) default: next_frame = end; - veejay_set_speed(info, 0, 1); + vj_perform_set_speed_beat_aware(info, 0, 1); next_dir = 0; edge_type = AUDIO_EDGE_SILENCE; cycle_done = 1; @@ -13399,7 +14546,7 @@ void vj_perform_inc_frame(veejay_t *info, int num) default: next_frame = start; - veejay_set_speed(info, 0, 1); + vj_perform_set_speed_beat_aware(info, 0, 1); next_dir = 0; edge_type = AUDIO_EDGE_SILENCE; cycle_done = 1;