enable direct passthrough when source has no audio (write silence instead), keep audio producer alive for external monitor output, skip empty sequencer ids, ignore sample speed 0 by restoring previous speed or 1 in midi/sequencer mode, resume from stuck play, reset stats/loops type changes

This commit is contained in:
niels
2026-06-06 23:38:47 +02:00
parent 573f5b8a8a
commit 24f9e82c4d
3 changed files with 300 additions and 70 deletions
@@ -2137,6 +2137,9 @@ void veejay_sample_set_initial_positions(veejay_t *info)
int id = info->seq->samples[i].sample_id;
if(id <= 0 || !sample_exists(id))
continue;
sample_set_loops(id, -1);
sample_set_resume_override(id, -1);
@@ -2182,10 +2185,14 @@ void veejay_reset_sample_positions(veejay_t *info, int sample_id)
if( info->seq->samples[i].type != 0 )
continue;
int id = info->seq->samples[i].sample_id;
if(id <= 0 || !sample_exists(id))
continue;
veejay_prepare_sample_positions(id);
}
}
else {
if(sample_id <= 0 || !sample_exists(sample_id))
return;
veejay_prepare_sample_positions(sample_id);
}
}
@@ -4553,6 +4560,9 @@ static inline int vj_audio_is_external_jack_monitor_source(
source != VJ_AUDIO_SYNC_SOURCE_WAV_FILE)
return 0;
if(source == VJ_AUDIO_SYNC_SOURCE_WAV_FILE)
return audio_source != VJ_RECORD_AUDIO_SOURCE_SILENCE;
return (audio_source == VJ_RECORD_AUDIO_SOURCE_BEAT_JACK ||
audio_source == VJ_RECORD_AUDIO_SOURCE_AUTO);
}
@@ -4973,11 +4983,27 @@ void *veejay_audio_producer_thread(void *arg)
veejay_t *info = (veejay_t *)arg;
video_playback_setup *settings = info->settings;
editlist *el = info->current_edit_list;
int audio_channels = 2;
int BPS = 4;
int media_audio = 0;
int has_audio = (info->current_edit_list->has_audio &&
vj_perform_init_audio(info,0) &&
vj_perform_init_audio(info, 1) &&
info->audio == AUDIO_PLAY);
if(el) {
if(el->audio_chans > 0 && el->audio_chans <= 2)
audio_channels = el->audio_chans;
if(el->audio_bps > 0)
BPS = el->audio_bps;
}
media_audio = (el && el->has_audio &&
vj_perform_init_audio(info,0) &&
vj_perform_init_audio(info,1) &&
info->audio == AUDIO_PLAY);
#ifdef HAVE_JACK
int has_audio = (info->audio == AUDIO_PLAY);
#else
int has_audio = media_audio;
#endif
#ifndef HAVE_JACK
has_audio = 0;
#endif
@@ -5004,7 +5030,6 @@ void *veejay_audio_producer_thread(void *arg)
double slow_video_phase = 0.0;
int last_dynamic_slow = 0;
unsigned long long loop_count = 0;
const int BPS = el->audio_bps;
const int MAX_CLIENT_FRAMES = (int)(((double)CLIENT_RATE / VJ_DYNAMIC_ALLOC_MIN_FPS) + 4096.0);
uint8_t *audio_chunk = NULL;
@@ -5154,6 +5179,18 @@ void *veejay_audio_producer_thread(void *arg)
) &&
external_tape_neutral_dbg &&
!audio_mute_dbg;
int external_monitor_audio =
vj_audio_is_external_jack_monitor_source(
settings,
audio_source_dbg,
audio_sync_mode_dbg,
audio_sync_enabled_dbg
) &&
!audio_mute_dbg;
int external_direct_passthrough =
external_monitor_audio &&
external_live_source_dbg &&
atomic_load_int(&settings->audio_sync.source) == VJ_AUDIO_SYNC_SOURCE_JACK;
if(vj_jack_xrun_flag()) {
@@ -5171,7 +5208,28 @@ void *veejay_audio_producer_thread(void *arg)
}
int tx_active = atomic_load_int(&settings->transition.active) && atomic_load_int(&settings->transition.global_state);
if(!tx_active) {
if(external_monitor_audio) {
#ifdef HAVE_JACK
vj_jack_set_input_passthrough(external_direct_passthrough ? 1 : 0);
#endif
if(external_direct_passthrough) {
veejay_memset(audio_chunk, 0, (size_t)needed * (size_t)BPS);
decoded = needed;
} else {
decoded = vj_audio_sync_render_monitor_s16(&settings->audio_sync,
audio_chunk,
needed,
BPS,
audio_channels,
CLIENT_RATE);
if(decoded <= 0) {
veejay_memset(audio_chunk, 0, (size_t)needed * (size_t)BPS);
decoded = needed;
}
}
}
else if(media_audio && !tx_active) {
decoded = vj_perform_queue_audio_chunk_ext(info, needed, media_frame, 0, audio_chunk);
if (decoded <= 0) {
@@ -5192,12 +5250,19 @@ void *veejay_audio_producer_thread(void *arg)
}
}
}
else {
else if(media_audio) {
long long b_frame = vj_calc_next_subframe(info, settings->transition.next_id);
long long start = atomic_load_long_long(&settings->transition.start);
long long end = atomic_load_long_long(&settings->transition.end);
decoded = vj_perform_queue_audio_chunk_crossfade(info, needed, media_frame, b_frame, audio_chunk, settings->transition.next_id,start,end);
}
else {
#ifdef HAVE_JACK
vj_jack_set_input_passthrough(0);
#endif
veejay_memset(audio_chunk, 0, (size_t)needed * (size_t)BPS);
decoded = needed;
}
int frames_written = 0;
int write_pos = 0;
@@ -7496,13 +7561,34 @@ int veejay_toggle_audio(veejay_t * info, int audio)
(void) audio;
if( !(el->has_audio) ) {
veejay_msg(VEEJAY_MSG_WARNING,
#ifdef HAVE_JACK
int external_audio = vj_audio_is_external_jack_monitor_source(
settings,
atomic_load_int(&settings->record_audio_source),
atomic_load_int(&settings->audio_sync.mode),
vj_audio_sync_is_enabled(&settings->audio_sync)
);
#else
int external_audio = 0;
#endif
int silent_output = (info->audio == AUDIO_PLAY &&
el &&
!el->has_audio &&
el->audio_rate > 0 &&
el->audio_chans > 0 &&
el->audio_bps > 0);
if((!el || !(el->has_audio)) && !external_audio && !silent_output) {
veejay_msg(VEEJAY_MSG_WARNING,
"Audio playback has not been enabled");
info->audio = NO_AUDIO;
return 0;
}
if(external_audio || silent_output)
info->audio = AUDIO_PLAY;
old_mute = atomic_load_int(&settings->audio_mute) ? 1 : 0;
new_mute = old_mute ? 0 : 1;
@@ -4315,6 +4315,11 @@ void vj_event_sample_set_rand_loop(void *ptr, const char format[], va_list ap)
SAMPLE_DEFAULTS(args[0]);
if(v->seq && v->seq->active && sample_exists(args[0])) {
sample_set_loop_stats(args[0], 0);
sample_set_loops(args[0], -1);
}
if( args[1] == -1 )
{
if( sample_exists(args[0]) )
@@ -4351,6 +4356,13 @@ void vj_event_sample_set_loop_type(void *ptr, const char format[], va_list ap)
return;
}
if(v->seq && v->seq->active) {
sample_set_loop_stats(args[0], 0);
sample_set_loops(args[0], -1);
if(args[1] == 0)
args[1] = 4;
}
if(args[1] == -1) {
int lp = sample_get_looptype(args[0]);
if( lp == 1 && args[1] == -1 )
@@ -4458,6 +4470,12 @@ void vj_event_sample_set_speed(void *ptr, const char format[], va_list ap)
SAMPLE_DEFAULTS(args[0]);
if(v->seq && v->seq->active && args[1] == 0) {
args[1] = v->settings->previous_playback_speed;
if(args[1] == 0)
args[1] = 1;
}
if( sample_set_speed(args[0], args[1]) != -1)
{
veejay_msg(VEEJAY_MSG_INFO, "Changed speed of sample %d to %d",args[0],args[1]);
@@ -12651,6 +12669,13 @@ void vj_event_sample_sequencer_active(void *ptr, const char format[], va_list ap
vj_tag_set_loops(cur_id, -1);
}
if(v->settings && v->settings->current_playback_speed == 0) {
int resume_speed = v->settings->previous_playback_speed;
if(resume_speed == 0)
resume_speed = 1;
veejay_set_speed(v, resume_speed, 0);
}
veejay_msg(VEEJAY_MSG_INFO,"Sample sequencer disabled; current source keeps looping");
return;
}
@@ -12688,6 +12713,13 @@ void vj_event_sample_sequencer_active(void *ptr, const char format[], va_list ap
v->seq->active = 1;
if(v->settings && v->settings->current_playback_speed == 0) {
int resume_speed = v->settings->previous_playback_speed;
if(resume_speed == 0)
resume_speed = 1;
veejay_set_speed(v, resume_speed, 0);
}
veejay_reset_sample_positions(v, -1);
veejay_change_playback_mode(v, playback_mode, next_sample_id);
@@ -12952,5 +12984,4 @@ void vj_event_alpha_composite(void *ptr, const char format[], va_list ap)
v->settings->alpha_value = 255;
veejay_msg(VEEJAY_MSG_INFO,"New alpha mask every frame (default alpha value is %d)", v->settings->alpha_value);
}
}
}
@@ -475,6 +475,62 @@ static inline int vj_perform_clampi(int v, int lo, int hi)
return (v < lo) ? lo : ((v > hi) ? hi : v);
}
#ifndef VJ_SILENCE_AUDIO_RATE
#define VJ_SILENCE_AUDIO_RATE 48000
#endif
#ifndef VJ_SILENCE_AUDIO_CHANS
#define VJ_SILENCE_AUDIO_CHANS 2
#endif
#ifndef VJ_SILENCE_AUDIO_BITS
#define VJ_SILENCE_AUDIO_BITS 16
#endif
static inline editlist *vj_perform_audio_editlist(veejay_t *info)
{
if(!info)
return NULL;
return info->current_edit_list ? info->current_edit_list : info->edit_list;
}
static inline int vj_perform_audio_params_valid(const editlist *el)
{
return el &&
el->video_fps > 0.0 &&
el->audio_rate > 0 &&
el->audio_chans > 0 &&
el->audio_bits > 0 &&
el->audio_bps > 0;
}
static inline int vj_perform_audio_media_valid(const editlist *el)
{
return el && el->has_audio && vj_perform_audio_params_valid(el);
}
#ifdef HAVE_JACK
static int vj_perform_prepare_silence_audio_params(editlist *el)
{
if(!el || el->video_fps <= 0.0)
return 0;
if(el->audio_rate <= 0)
el->audio_rate = VJ_SILENCE_AUDIO_RATE;
if(el->audio_chans <= 0)
el->audio_chans = VJ_SILENCE_AUDIO_CHANS;
else if(el->audio_chans > 2)
el->audio_chans = 2;
if(el->audio_bits <= 0 || (el->audio_bits % 8) != 0)
el->audio_bits = VJ_SILENCE_AUDIO_BITS;
if(el->audio_bps <= 0)
el->audio_bps = el->audio_chans * (el->audio_bits / 8);
return vj_perform_audio_params_valid(el);
}
#endif
#define CACHE_TOP 0
#define CACHE 1
@@ -1888,37 +1944,51 @@ static void vj_perform_record_buffer_free(performer_global_t *g)
int vj_init_audio_fader_luts(veejay_t *info) {
performer_global_t *global = (performer_global_t*) info->performer;
editlist *el = vj_perform_audio_editlist(info);
const int audio_rate = info->current_edit_list->audio_rate;
const double video_fps = info->current_edit_list->video_fps;
if(!global)
return 0;
if(!vj_perform_audio_media_valid(el)) {
if(el && !el->has_audio)
veejay_msg(VEEJAY_MSG_INFO, "[AUDIO] No embedded audio stream; audio mixer runs silent");
else
veejay_msg(VEEJAY_MSG_WARNING, "[AUDIO] Invalid embedded audio parameters; audio mixer disabled");
return 1;
}
const int audio_rate = el->audio_rate;
const double video_fps = el->video_fps;
const int frame_bytes = el->audio_bps;
const int max_samples_per_frame = (int)ceil((double)audio_rate / video_fps);
size_t buffer_size = max_samples_per_frame * info->current_edit_list->audio_bps;
global->fade_lut = (float*) vj_calloc( sizeof(float) * buffer_size);
if(!global->fade_lut) {
return 0;
}
global->gain_lut[0] = (float*) vj_calloc( (sizeof(float) * buffer_size));
if(!global->gain_lut[0]) {
return 0;
if(max_samples_per_frame < 2 || frame_bytes <= 0) {
veejay_msg(VEEJAY_MSG_WARNING, "[AUDIO] Invalid mixer geometry; audio mixer disabled");
return 1;
}
global->gain_lut[1] = (float*) vj_calloc( (sizeof(float) * buffer_size));
if(!global->gain_lut[1]) {
size_t buffer_size = (size_t)max_samples_per_frame * (size_t)frame_bytes;
global->fade_lut = (float*) vj_calloc(sizeof(float) * buffer_size);
if(!global->fade_lut)
return 0;
global->gain_lut[0] = (float*) vj_calloc(sizeof(float) * buffer_size);
if(!global->gain_lut[0])
return 0;
global->gain_lut[1] = (float*) vj_calloc(sizeof(float) * buffer_size);
if(!global->gain_lut[1])
return 0;
}
for (int i = 0; i < max_samples_per_frame; i++) {
float t = (float)i / (float)(max_samples_per_frame - 1);
global->gain_lut[0][i] = cosf(t * (M_PI / 2.0f));
global->gain_lut[1][i] = sinf(t * (M_PI / 2.0f));
global->fade_lut[i] = t;
}
global->gain_lut[0][max_samples_per_frame - 1] = 0.0f;
global->gain_lut[0][max_samples_per_frame - 2] = 0.0f;
@@ -1933,6 +2003,7 @@ int vj_init_audio_fader_luts(veejay_t *info) {
veejay_msg(VEEJAY_MSG_DEBUG, "[AudioMix] LUT[0] : out=%.3f in=%.3f (start)", global->gain_lut[0][0], global->gain_lut[1][0]);
veejay_msg(VEEJAY_MSG_DEBUG, "[AudioMix] LUT[%d] : out=%.3f in=%.3f (mid)", mid, global->gain_lut[0][mid], global->gain_lut[1][mid]);
veejay_msg(VEEJAY_MSG_DEBUG, "[AudioMix] LUT[%d] : out=%.3f in=%.3f (end)", last, global->gain_lut[0][last], global->gain_lut[1][last]);
float energy_mid = global->gain_lut[0][mid] * global->gain_lut[0][mid] +
global->gain_lut[1][mid] * global->gain_lut[1][mid];
@@ -2286,10 +2357,15 @@ static void vj_perform_close_audio(performer_t *p) {
int init_audio_resampler(veejay_t *info, performer_t *p) {
#ifdef HAVE_JACK
const int chans = info->edit_list->audio_chans;
const int rate = info->edit_list->audio_rate;
editlist *el = vj_perform_audio_editlist(info);
p->audio_scratcher = vj_scratch_init( chans, rate, info->edit_list->video_fps );
if(!vj_perform_audio_media_valid(el))
return 1;
const int chans = el->audio_chans;
const int rate = el->audio_rate;
p->audio_scratcher = vj_scratch_init(chans, rate, el->video_fps);
#endif
return 1;
}
@@ -2307,7 +2383,13 @@ int vj_perform_init_audio(veejay_t * info, int AorB)
}
performer_global_t *global = (performer_global_t*) info->performer;
editlist *el = info->edit_list;
editlist *el = vj_perform_audio_editlist(info);
if(!vj_perform_audio_media_valid(el)) {
veejay_msg(VEEJAY_MSG_WARNING, "[AUDIO] Skipping media audio buffers: no valid embedded audio stream");
return 0;
}
performer_t *p = (AorB ? global->A : global->B);
double samples_per_frame = (double)el->audio_rate / (double)el->video_fps;
const uint32_t sample_len = ceil(samples_per_frame) * el->audio_bps;
@@ -2568,54 +2650,85 @@ int vj_perform_preview_max_height(veejay_t *info) {
int vj_perform_audio_start(veejay_t * info)
{
editlist *el = info->edit_list;
editlist *el = info ? info->edit_list : NULL;
if (el->has_audio)
{
#ifdef HAVE_JACK
vj_jack_initialize();
int res = vj_jack_init_duplex(el);
if( res <= 0 ) {
veejay_msg(VEEJAY_MSG_WARNING,
"[AUDIO] Jack duplex start failed; falling back to playback-only mode");
vj_jack_initialize();
res = vj_jack_init(el);
}
if( res <= 0 ) {
veejay_msg(0, "[AUDIO] Audio playback disabled");
info->audio = NO_AUDIO;
return 0;
}
if ( res == 2 )
{
vj_jack_stop();
info->audio = NO_AUDIO;
veejay_msg(VEEJAY_MSG_ERROR,"Please run jackd with a sample rate of %ld",el->audio_rate );
return 0;
}
if(vj_jack_has_input())
veejay_msg(VEEJAY_MSG_DEBUG,"[AUDIO] Jack audio playback started with capture input ports");
else
veejay_msg(VEEJAY_MSG_WARNING,"[AUDIO] Jack audio playback started without capture input ports");
return 1;
#else
veejay_msg(VEEJAY_MSG_WARNING, "[AUDIO] Jack support not compiled in (no audio)");
if(!info || !el)
return 0;
#ifdef HAVE_JACK
if(el->has_audio && !vj_perform_audio_params_valid(el)) {
veejay_msg(VEEJAY_MSG_WARNING, "[AUDIO] Embedded audio stream has invalid parameters; audio playback disabled");
return 0;
#endif
}
const int silent_output = !el->has_audio;
int saved_has_audio = el->has_audio;
if(silent_output) {
if(!vj_perform_prepare_silence_audio_params(el)) {
veejay_msg(VEEJAY_MSG_WARNING, "[AUDIO] Unable to prepare silent playback parameters");
return 0;
}
if(info->current_edit_list && info->current_edit_list != el)
vj_perform_prepare_silence_audio_params(info->current_edit_list);
veejay_msg(VEEJAY_MSG_INFO,
"[AUDIO] No embedded audio stream; starting JACK silence output (%ld Hz, %d channels, %d bit)",
(long)el->audio_rate,
el->audio_chans,
el->audio_bits);
el->has_audio = 1;
}
vj_jack_initialize();
int res = vj_jack_init_duplex(el);
if( res <= 0 ) {
veejay_msg(VEEJAY_MSG_WARNING,
"[AUDIO] Jack duplex start failed; falling back to playback-only mode");
vj_jack_initialize();
res = vj_jack_init(el);
}
if(silent_output)
el->has_audio = saved_has_audio;
if( res <= 0 ) {
veejay_msg(0, "[AUDIO] Audio playback disabled");
info->audio = NO_AUDIO;
return 0;
}
if ( res == 2 )
{
vj_jack_stop();
info->audio = NO_AUDIO;
veejay_msg(VEEJAY_MSG_ERROR,"Please run jackd with a sample rate of %ld",(long)el->audio_rate );
return 0;
}
if(vj_jack_has_input())
veejay_msg(VEEJAY_MSG_DEBUG,"[AUDIO] Jack audio playback started with capture input ports");
else
veejay_msg(VEEJAY_MSG_WARNING,"[AUDIO] Jack audio playback started without capture input ports");
return 1;
#else
if(el->has_audio)
veejay_msg(VEEJAY_MSG_WARNING, "[AUDIO] Jack support not compiled in (no audio)");
return 0;
#endif
}
void vj_perform_audio_stop(veejay_t * info)
{
if (info->edit_list->has_audio) {
if(!info)
return;
if (info->audio == AUDIO_PLAY || (info->edit_list && info->edit_list->has_audio)) {
#ifdef HAVE_JACK
vj_jack_stop();
#endif