Improved the fade in and fade out effect
This commit is contained in:
		
							parent
							
								
									84a82a8b27
								
							
						
					
					
						commit
						a2c6cba7f4
					
				@ -51,18 +51,22 @@ bool merge::merge_n_sources(void *dest, void **srcs, size_t src_length, size_t c
 | 
				
			|||||||
		srcs++;
 | 
							srcs++;
 | 
				
			||||||
		src_length--;
 | 
							src_length--;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(src_length == 0)
 | 
							if(src_length == 0) {
 | 
				
			||||||
			return false;
 | 
					            return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if(srcs[0] != dest)
 | 
						if(srcs[0] != dest) {
 | 
				
			||||||
	    memcpy(dest, srcs[0], channels * samples * 4);
 | 
					        memcpy(dest, srcs[0], channels * samples * 4);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	srcs++;
 | 
						srcs++;
 | 
				
			||||||
	src_length--;
 | 
						src_length--;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	while(src_length > 0) {
 | 
						while(src_length > 0) {
 | 
				
			||||||
		/* only invoke is srcs is not null! */
 | 
							/* only invoke is srcs is not null! */
 | 
				
			||||||
		if(srcs[0] && !merge::merge_sources(dest, srcs[0], dest, channels, samples))
 | 
							if(srcs[0] && !merge::merge_sources(dest, srcs[0], dest, channels, samples)) {
 | 
				
			||||||
			return false;
 | 
					            return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		srcs++;
 | 
							srcs++;
 | 
				
			||||||
		src_length--;
 | 
							src_length--;
 | 
				
			||||||
 | 
				
			|||||||
@ -12,242 +12,309 @@ using namespace tc;
 | 
				
			|||||||
using namespace tc::audio;
 | 
					using namespace tc::audio;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AudioOutputSource::clear() {
 | 
					void AudioOutputSource::clear() {
 | 
				
			||||||
 | 
					    std::lock_guard buffer_lock{this->buffer_mutex};
 | 
				
			||||||
    this->buffer.clear();
 | 
					    this->buffer.clear();
 | 
				
			||||||
    this->buffering = true;
 | 
					    this->buffer_state = buffer_state::buffering;
 | 
				
			||||||
    this->fade_in_start = this->buffer.write_ptr();
 | 
					    this->fadeout_samples_left = 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AudioOutputSource::do_fade_out(size_t pop_count) {
 | 
					void AudioOutputSource::apply_fadeout() {
 | 
				
			||||||
    if(this->will_buffer_in != -1) return;
 | 
					    const auto samples_available = this->currently_buffered_samples();
 | 
				
			||||||
 | 
					    auto fade_samples = std::min(samples_available, this->fadeout_frame_samples_);
 | 
				
			||||||
    _test_for_fade:
 | 
					    if(!fade_samples) {
 | 
				
			||||||
    const auto samples_left = this->current_latency();
 | 
					        this->fadeout_samples_left = 0;
 | 
				
			||||||
    if(samples_left < this->fadeout_sample_length + pop_count) {
 | 
					 | 
				
			||||||
        if(auto fn = this->on_underflow; fn && fn(0))
 | 
					 | 
				
			||||||
            goto _test_for_fade;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        auto total_samples = std::min(samples_left, this->fadeout_sample_length);
 | 
					 | 
				
			||||||
        if(total_samples == 0) return; //TODO Test against min_buffered_samples
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        auto wptr = (float*) this->buffer.calculate_backward_write_ptr(total_samples * this->channel_count * sizeof(float));
 | 
					 | 
				
			||||||
        for(size_t index{0}; index <= total_samples; index++) {
 | 
					 | 
				
			||||||
            const auto offset = (float) ((float) index / (float) total_samples);
 | 
					 | 
				
			||||||
            const auto volume = log10f(offset) / -2.71828182845904f;
 | 
					 | 
				
			||||||
            for(int channel{0}; channel < this->channel_count; channel++)
 | 
					 | 
				
			||||||
                *wptr++ *= volume;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        log_trace(category::audio, tr("Will buffer due to fade out ({} | {})"), total_samples, *(float*) this->buffer.write_ptr());
 | 
					 | 
				
			||||||
        this->will_buffer_in = total_samples;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void AudioOutputSource::do_fade_in() {
 | 
					 | 
				
			||||||
    if(!this->fade_in_start)
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const auto samples_available = this->current_latency();
 | 
					 | 
				
			||||||
    auto wptr = (float*) this->fade_in_start;
 | 
					 | 
				
			||||||
    auto total_samples = std::min(samples_available, this->fadeout_sample_length);
 | 
					 | 
				
			||||||
    if(total_samples == 0) {
 | 
					 | 
				
			||||||
        log_trace(category::audio, tr("Ignoring fade in 0: {} {}"), samples_available, this->fadeout_sample_length);
 | 
					 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for(size_t index{0}; index < total_samples; index++) {
 | 
					    const auto sample_byte_size = this->channel_count * sizeof(float) * fade_samples;
 | 
				
			||||||
        const auto offset = (float) ((float) index / (float) total_samples);
 | 
					    assert(this->buffer.fill_count() >= sample_byte_size);
 | 
				
			||||||
        const auto volume = log10f(1 - offset) / -2.71828182845904f;
 | 
					    auto write_ptr = (float*) ((char*) this->buffer.read_ptr() + (this->buffer.fill_count() - sample_byte_size));
 | 
				
			||||||
        for(int channel{0}; channel < this->channel_count; channel++)
 | 
					 | 
				
			||||||
            *wptr++ *= volume;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    log_trace(category::audio, tr("Fade in to new buffer ({})"), total_samples);
 | 
					    for(size_t index{0}; index < fade_samples; index++) {
 | 
				
			||||||
 | 
					        const auto offset = (float) ((float) (index + 1) / (float) fade_samples);
 | 
				
			||||||
 | 
					        const auto volume = std::min(log10f(offset) / -2.71828182845904f, 1.f);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this->fade_in_start = nullptr;
 | 
					        for(int channel{0}; channel < this->channel_count; channel++) {
 | 
				
			||||||
}
 | 
					            *write_ptr++ *= volume;
 | 
				
			||||||
 | 
					 | 
				
			||||||
ssize_t AudioOutputSource::pop_samples(void *buffer, size_t samples) {
 | 
					 | 
				
			||||||
    size_t written{0}, written_bytes{0};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    load_buffer:
 | 
					 | 
				
			||||||
    auto available_bytes = this->buffer.fill_count();
 | 
					 | 
				
			||||||
    if(available_bytes < sizeof(float) * this->channel_count) return written;
 | 
					 | 
				
			||||||
    auto available_samples = available_bytes / sizeof(float) / this->channel_count;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(this->buffering && available_samples < this->min_buffered_samples) return -2;
 | 
					 | 
				
			||||||
    this->do_fade_in();
 | 
					 | 
				
			||||||
    this->do_fade_out(samples); /* will also call for underflow */
 | 
					 | 
				
			||||||
    //log_trace(category::audio, tr("Min: {}, Max: {}, Current: {}, Buffering: {} Required: {}, left: {}, will buffer in {}"), this->min_buffered_samples, this->max_buffered_samples, available_samples, this->buffering, samples, (int) available_samples - samples, this->will_buffer_in);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(this->will_buffer_in > 0) {
 | 
					 | 
				
			||||||
        if(samples > (size_t) this->will_buffer_in) {
 | 
					 | 
				
			||||||
            samples = this->will_buffer_in;
 | 
					 | 
				
			||||||
            this->buffering = true;
 | 
					 | 
				
			||||||
            this->fade_in_start = this->buffer.calculate_advanced_write_ptr(samples * sizeof(float) * this->channel_count);
 | 
					 | 
				
			||||||
            this->will_buffer_in = -1;
 | 
					 | 
				
			||||||
            log_trace(category::audio, tr("Start buffering due to fade out. Fade in ptr {}"), (void*) this->fade_in_start);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            this->will_buffer_in -= samples;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        this->buffering = false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(available_samples >= samples - written) {
 | 
					 | 
				
			||||||
        const auto byte_length = (samples - written) * sizeof(float) * this->channel_count;
 | 
					 | 
				
			||||||
        if(buffer)memcpy((char*) buffer + written_bytes, this->buffer.read_ptr(), byte_length);
 | 
					 | 
				
			||||||
        this->buffer.advance_read_ptr(byte_length);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if(this->on_read)
 | 
					 | 
				
			||||||
            this->on_read();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return samples;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        const auto byte_length = available_samples * sizeof(float) * this->channel_count;
 | 
					 | 
				
			||||||
        if(buffer) memcpy((char*) buffer + written_bytes, this->buffer.read_ptr(), byte_length);
 | 
					 | 
				
			||||||
        this->buffer.advance_read_ptr(byte_length);
 | 
					 | 
				
			||||||
        written += available_samples;
 | 
					 | 
				
			||||||
        written_bytes += byte_length;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(auto fn = this->on_underflow; fn)
 | 
					 | 
				
			||||||
        if(fn(samples - written))
 | 
					 | 
				
			||||||
            goto load_buffer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(buffer)
 | 
					 | 
				
			||||||
        memset((char*) buffer + written_bytes, 0, (samples - written) * sizeof(float) * this->channel_count);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this->buffering = true;
 | 
					 | 
				
			||||||
    this->fade_in_start = this->buffer.write_ptr();
 | 
					 | 
				
			||||||
    log_trace(category::audio, tr("Start buffering due to underflow."), (void*) this->fade_in_start);
 | 
					 | 
				
			||||||
    this->will_buffer_in = -1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if(this->on_read)
 | 
					 | 
				
			||||||
		this->on_read();
 | 
					 | 
				
			||||||
	return written; /* return the written samples */
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ssize_t AudioOutputSource::enqueue_silence(size_t samples) {
 | 
					 | 
				
			||||||
    size_t enqueued{0};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto free_bytes = this->buffer.free_count();
 | 
					 | 
				
			||||||
    auto free_samples = free_bytes / sizeof(float) / this->channel_count;
 | 
					 | 
				
			||||||
    if(this->max_buffered_samples && free_samples > this->max_buffered_samples) free_samples = this->max_buffered_samples;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(free_samples >= samples) {
 | 
					 | 
				
			||||||
        const auto byte_length = samples * sizeof(float) * this->channel_count;
 | 
					 | 
				
			||||||
        memset(this->buffer.write_ptr(), 0, byte_length);
 | 
					 | 
				
			||||||
        this->buffer.advance_write_ptr(byte_length);
 | 
					 | 
				
			||||||
        return samples;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        const auto byte_length = free_samples * sizeof(float) * this->channel_count;
 | 
					 | 
				
			||||||
        memset(this->buffer.write_ptr(), 0, byte_length);
 | 
					 | 
				
			||||||
        this->buffer.advance_write_ptr(byte_length);
 | 
					 | 
				
			||||||
        enqueued += free_samples;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(auto fn = this->on_overflow; fn)
 | 
					 | 
				
			||||||
        fn(samples - enqueued);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    switch (this->overflow_strategy) {
 | 
					 | 
				
			||||||
        case overflow_strategy::discard_input:
 | 
					 | 
				
			||||||
            return -2;
 | 
					 | 
				
			||||||
        case overflow_strategy::discard_buffer_all:
 | 
					 | 
				
			||||||
            this->buffer.clear();
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        case overflow_strategy::discard_buffer_half:
 | 
					 | 
				
			||||||
            this->buffer.advance_read_ptr(this->buffer.fill_count() / 2);
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        case overflow_strategy::ignore:
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this->fade_in_start = this->buffer.write_ptr(); /* so we fade in from silence */
 | 
					 | 
				
			||||||
    return enqueued;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ssize_t AudioOutputSource::enqueue_samples(const void *buffer, size_t samples) {
 | 
					 | 
				
			||||||
    size_t enqueued{0};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto free_bytes = this->buffer.free_count();
 | 
					 | 
				
			||||||
    auto free_samples = free_bytes / sizeof(float) / this->channel_count;
 | 
					 | 
				
			||||||
    if(this->max_buffered_samples && free_samples > this->max_buffered_samples) free_samples = this->max_buffered_samples;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(free_samples >= samples) {
 | 
					 | 
				
			||||||
        const auto byte_length = samples * sizeof(float) * this->channel_count;
 | 
					 | 
				
			||||||
        memcpy(this->buffer.write_ptr(), buffer, byte_length);
 | 
					 | 
				
			||||||
        this->buffer.advance_write_ptr(byte_length);
 | 
					 | 
				
			||||||
        return samples;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        const auto byte_length = free_samples * sizeof(float) * this->channel_count;
 | 
					 | 
				
			||||||
        memcpy(this->buffer.write_ptr(), buffer, byte_length);
 | 
					 | 
				
			||||||
        this->buffer.advance_write_ptr(byte_length);
 | 
					 | 
				
			||||||
        enqueued += free_samples;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(auto fn = this->on_overflow; fn)
 | 
					 | 
				
			||||||
        fn(samples - enqueued);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    switch (this->overflow_strategy) {
 | 
					 | 
				
			||||||
        case overflow_strategy::discard_input:
 | 
					 | 
				
			||||||
            return -2;
 | 
					 | 
				
			||||||
        case overflow_strategy::discard_buffer_all:
 | 
					 | 
				
			||||||
            this->buffer.clear();
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        case overflow_strategy::discard_buffer_half:
 | 
					 | 
				
			||||||
            this->buffer.advance_read_ptr(this->buffer.fill_count() / 2);
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        case overflow_strategy::ignore:
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return enqueued;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ssize_t AudioOutputSource::enqueue_samples_no_interleave(const void *buffer, size_t samples) {
 | 
					 | 
				
			||||||
    auto free_bytes = this->buffer.free_count();
 | 
					 | 
				
			||||||
    auto free_samples = free_bytes / sizeof(float) / this->channel_count;
 | 
					 | 
				
			||||||
    if(this->max_buffered_samples && free_samples > this->max_buffered_samples) free_samples = this->max_buffered_samples;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto samples_to_write{samples};
 | 
					 | 
				
			||||||
    if(samples_to_write > free_samples) samples_to_write = free_samples;
 | 
					 | 
				
			||||||
    const auto enqueued{samples_to_write};
 | 
					 | 
				
			||||||
    { //FIXME: This only works for two channels!
 | 
					 | 
				
			||||||
        auto src_buffer = (const float*) buffer;
 | 
					 | 
				
			||||||
        auto target_buffer = (float*) this->buffer.write_ptr();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        while (samples_to_write-- > 0) {
 | 
					 | 
				
			||||||
            *target_buffer = *src_buffer;
 | 
					 | 
				
			||||||
            *(target_buffer + 1) = *(src_buffer + samples);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            target_buffer += 2;
 | 
					 | 
				
			||||||
            src_buffer++;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this->buffer.advance_write_ptr(enqueued * this->channel_count * sizeof(float));
 | 
					 | 
				
			||||||
    if(enqueued == samples) return enqueued;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(auto fn = this->on_overflow; fn)
 | 
					    this->fadeout_samples_left = fade_samples;
 | 
				
			||||||
        fn(samples - enqueued);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    switch (this->overflow_strategy) {
 | 
					 | 
				
			||||||
        case overflow_strategy::discard_input:
 | 
					 | 
				
			||||||
            return -2;
 | 
					 | 
				
			||||||
        case overflow_strategy::discard_buffer_all:
 | 
					 | 
				
			||||||
            this->buffer.clear();
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        case overflow_strategy::discard_buffer_half:
 | 
					 | 
				
			||||||
            this->buffer.advance_read_ptr(this->buffer.fill_count() / 2);
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        case overflow_strategy::ignore:
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return enqueued;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AudioOutput::AudioOutput(size_t channels, size_t rate) : _channel_count(channels), _sample_rate(rate) { }
 | 
					void AudioOutputSource::apply_fadein() {
 | 
				
			||||||
 | 
					    assert(this->currently_buffered_samples() >= this->fadeout_samples_left);
 | 
				
			||||||
 | 
					    const auto samples_available = this->currently_buffered_samples();
 | 
				
			||||||
 | 
					    auto fade_samples = std::min(samples_available - this->fadeout_samples_left, this->fadein_frame_samples_);
 | 
				
			||||||
 | 
					    if(!fade_samples) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					     * Note: We're using the read_ptr() here in order to correctly apply the effect.
 | 
				
			||||||
 | 
					     *       This isn't really best practice but works.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    auto write_ptr = (float*) this->buffer.read_ptr() + this->fadeout_samples_left * this->channel_count;
 | 
				
			||||||
 | 
					    for(size_t index{0}; index < fade_samples; index++) {
 | 
				
			||||||
 | 
					        const auto offset = (float) ((float) (index + 1) / (float) fade_samples);
 | 
				
			||||||
 | 
					        const auto volume = std::min(log10f(1 - offset) / -2.71828182845904f, 1.f);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for(int channel{0}; channel < this->channel_count; channel++) {
 | 
				
			||||||
 | 
					            *write_ptr++ *= volume;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool AudioOutputSource::pop_samples(void *target_buffer, size_t target_sample_count) {
 | 
				
			||||||
 | 
					    std::unique_lock buffer_lock{this->buffer_mutex};
 | 
				
			||||||
 | 
					    auto result = this->pop_samples_(target_buffer, target_sample_count);
 | 
				
			||||||
 | 
					    buffer_lock.unlock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(auto callback{this->on_read}; callback) {
 | 
				
			||||||
 | 
					        callback();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool AudioOutputSource::pop_samples_(void *target_buffer, size_t target_sample_count) {
 | 
				
			||||||
 | 
					    switch(this->buffer_state) {
 | 
				
			||||||
 | 
					        case buffer_state::fadeout: {
 | 
				
			||||||
 | 
					            /* Write as much we can */
 | 
				
			||||||
 | 
					            const auto write_samples = std::min(this->fadeout_samples_left, target_sample_count);
 | 
				
			||||||
 | 
					            const auto write_byte_size = write_samples * this->channel_count * sizeof(float);
 | 
				
			||||||
 | 
					            memcpy(target_buffer, this->buffer.read_ptr(), write_byte_size);
 | 
				
			||||||
 | 
					            this->buffer.advance_read_ptr(write_byte_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /* Fill the rest with silence */
 | 
				
			||||||
 | 
					            const auto empty_samples = target_sample_count - write_samples;
 | 
				
			||||||
 | 
					            const auto empty_byte_size = empty_samples * this->channel_count * sizeof(float);
 | 
				
			||||||
 | 
					            memset((char*) target_buffer + write_byte_size, 0, empty_byte_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this->fadeout_samples_left -= write_samples;
 | 
				
			||||||
 | 
					            if(!this->fadeout_samples_left) {
 | 
				
			||||||
 | 
					                log_trace(category::audio, tr("{} Successfully replayed fadeout sequence."), (void*) this);
 | 
				
			||||||
 | 
					                this->buffer_state = buffer_state::buffering;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case buffer_state::playing: {
 | 
				
			||||||
 | 
					            const auto buffered_samples = this->currently_buffered_samples();
 | 
				
			||||||
 | 
					            if(buffered_samples < target_sample_count + this->fadeout_frame_samples_) {
 | 
				
			||||||
 | 
					                const auto missing_samples = target_sample_count + this->fadeout_frame_samples_ - buffered_samples;
 | 
				
			||||||
 | 
					                if(auto callback{this->on_underflow}; callback) {
 | 
				
			||||||
 | 
					                    if(callback(missing_samples)) {
 | 
				
			||||||
 | 
					                        /* We've been filled up again. Trying again to fill the output buffer. */
 | 
				
			||||||
 | 
					                        return this->pop_samples(target_buffer, target_sample_count);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                /*
 | 
				
			||||||
 | 
					                 * When consuming target_sample_count amount samples of our buffer we could not
 | 
				
			||||||
 | 
					                 * apply the fadeout effect any more. Instead we're applying it now and returning to buffering state.
 | 
				
			||||||
 | 
					                 */
 | 
				
			||||||
 | 
					                this->apply_fadeout();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                /* Write the rest of unmodified buffer */
 | 
				
			||||||
 | 
					                const auto write_samples = buffered_samples - this->fadeout_samples_left;
 | 
				
			||||||
 | 
					                assert(write_samples <= target_sample_count);
 | 
				
			||||||
 | 
					                const auto write_byte_size = write_samples * this->channel_count * sizeof(float);
 | 
				
			||||||
 | 
					                memcpy(target_buffer, this->buffer.read_ptr(), write_byte_size);
 | 
				
			||||||
 | 
					                this->buffer.advance_read_ptr(write_byte_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                log_trace(category::audio, tr("{} Starting stream fadeout. Requested samples {}, Buffered samples: {}, Fadeout frame samples: {}, Returned normal samples: {}"),
 | 
				
			||||||
 | 
					                          (void*) this, target_sample_count, buffered_samples, this->fadeout_frame_samples_, write_samples
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                this->buffer_state = buffer_state::fadeout;
 | 
				
			||||||
 | 
					                if(write_samples < target_sample_count) {
 | 
				
			||||||
 | 
					                    /* Fill the rest of the buffer with the fadeout content */
 | 
				
			||||||
 | 
					                    this->pop_samples((char*) target_buffer + write_byte_size, target_sample_count - write_samples);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                /* We can just normally copy the buffer */
 | 
				
			||||||
 | 
					                const auto write_byte_size = target_sample_count * this->channel_count * sizeof(float);
 | 
				
			||||||
 | 
					                memcpy(target_buffer, this->buffer.read_ptr(), write_byte_size);
 | 
				
			||||||
 | 
					                this->buffer.advance_read_ptr(write_byte_size);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case buffer_state::buffering:
 | 
				
			||||||
 | 
					            /* Nothing to replay */
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            assert(false);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ssize_t AudioOutputSource::enqueue_samples(const void *source_buffer, size_t sample_count) {
 | 
				
			||||||
 | 
					    std::lock_guard buffer_lock{this->buffer_mutex};
 | 
				
			||||||
 | 
					    return this->enqueue_samples_(source_buffer, sample_count);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ssize_t AudioOutputSource::enqueue_samples_(const void *source_buffer, size_t sample_count) {
 | 
				
			||||||
 | 
					    switch(this->buffer_state) {
 | 
				
			||||||
 | 
					        case buffer_state::fadeout:
 | 
				
			||||||
 | 
					        case buffer_state::buffering: {
 | 
				
			||||||
 | 
					            assert(this->currently_buffered_samples() >= this->fadeout_samples_left);
 | 
				
			||||||
 | 
					            assert(this->min_buffered_samples_ >= this->currently_buffered_samples() - this->fadeout_samples_left);
 | 
				
			||||||
 | 
					            const auto missing_samples = this->min_buffered_samples_ - (this->currently_buffered_samples() - this->fadeout_samples_left);
 | 
				
			||||||
 | 
					            const auto write_sample_count = std::min(missing_samples, sample_count);
 | 
				
			||||||
 | 
					            const auto write_byte_size = write_sample_count * this->channel_count * sizeof(float);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            assert(write_sample_count <= this->max_supported_buffering());
 | 
				
			||||||
 | 
					            memcpy(this->buffer.write_ptr(), source_buffer, write_byte_size);
 | 
				
			||||||
 | 
					            this->buffer.advance_write_ptr(write_byte_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if(sample_count < missing_samples) {
 | 
				
			||||||
 | 
					                /* we still need to buffer */
 | 
				
			||||||
 | 
					                return sample_count;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /*
 | 
				
			||||||
 | 
					             * Even though we still have fadeout samples left we don't declare them as such since we've already fulled
 | 
				
			||||||
 | 
					             * our future buffer.
 | 
				
			||||||
 | 
					             */
 | 
				
			||||||
 | 
					            this->fadeout_samples_left = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /* buffering finished */
 | 
				
			||||||
 | 
					            log_trace(category::audio, tr("{} Finished buffering {} samples. Fading them in."), (void*) this, this->min_buffered_samples_);
 | 
				
			||||||
 | 
					            this->apply_fadein();
 | 
				
			||||||
 | 
					            this->buffer_state = buffer_state::playing;
 | 
				
			||||||
 | 
					            if(sample_count > missing_samples) {
 | 
				
			||||||
 | 
					                /* we've more data to write */
 | 
				
			||||||
 | 
					                return this->enqueue_samples((const char*) source_buffer + write_byte_size, sample_count - missing_samples) + write_sample_count;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return write_sample_count;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case buffer_state::playing: {
 | 
				
			||||||
 | 
					            const auto buffered_samples = this->currently_buffered_samples();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const auto write_sample_count = std::min(this->max_supported_buffering() - buffered_samples, sample_count);
 | 
				
			||||||
 | 
					            const auto write_byte_size = write_sample_count * this->channel_count * sizeof(float);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            memcpy(this->buffer.write_ptr(), source_buffer, write_byte_size);
 | 
				
			||||||
 | 
					            this->buffer.advance_write_ptr(write_byte_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if(write_sample_count < sample_count) {
 | 
				
			||||||
 | 
					                if(auto callback{this->on_overflow}; callback) {
 | 
				
			||||||
 | 
					                    callback(sample_count - write_sample_count);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                switch (this->overflow_strategy) {
 | 
				
			||||||
 | 
					                    case overflow_strategy::discard_input:
 | 
				
			||||||
 | 
					                        return -2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    case overflow_strategy::discard_buffer_all:
 | 
				
			||||||
 | 
					                        this->buffer.clear();
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    case overflow_strategy::discard_buffer_half:
 | 
				
			||||||
 | 
					                        /* FIXME: This implementation is wrong! */
 | 
				
			||||||
 | 
					                        this->buffer.advance_read_ptr(this->buffer.fill_count() / 2);
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    case overflow_strategy::ignore:
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return write_sample_count;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            assert(false);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					constexpr static auto kMaxStackBuffer{1024 * 8 * sizeof(float)};
 | 
				
			||||||
 | 
					ssize_t AudioOutputSource::enqueue_samples_no_interleave(const void *source_buffer, size_t samples) {
 | 
				
			||||||
 | 
					    if(this->channel_count == 1) {
 | 
				
			||||||
 | 
					        return this->enqueue_samples(source_buffer, samples);
 | 
				
			||||||
 | 
					    } else if(this->channel_count == 2) {
 | 
				
			||||||
 | 
					        const auto buffer_byte_size = samples * this->channel_count * sizeof(float);
 | 
				
			||||||
 | 
					        if(buffer_byte_size > kMaxStackBuffer) {
 | 
				
			||||||
 | 
					            /* We can't convert to interleave */
 | 
				
			||||||
 | 
					            return 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        uint8_t stack_buffer[kMaxStackBuffer];
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            auto src_buffer = (const float*) source_buffer;
 | 
				
			||||||
 | 
					            auto target_buffer = (float*) stack_buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            auto samples_to_write = samples;
 | 
				
			||||||
 | 
					            while (samples_to_write-- > 0) {
 | 
				
			||||||
 | 
					                *target_buffer = *src_buffer;
 | 
				
			||||||
 | 
					                *(target_buffer + 1) = *(src_buffer + samples);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                target_buffer += 2;
 | 
				
			||||||
 | 
					                src_buffer++;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this->enqueue_samples(stack_buffer, samples);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        /* TODO: Generalize to interleave algo */
 | 
				
			||||||
 | 
					        return 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool AudioOutputSource::set_max_buffered_samples(size_t samples) {
 | 
				
			||||||
 | 
					    samples = std::max(samples, (size_t) this->fadein_frame_samples_);
 | 
				
			||||||
 | 
					    if(samples > this->max_supported_buffering()) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::lock_guard buffer_lock{this->buffer_mutex};
 | 
				
			||||||
 | 
					    if(samples < this->min_buffered_samples_) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this->max_buffered_samples_ = samples;
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool AudioOutputSource::set_min_buffered_samples(size_t samples) {
 | 
				
			||||||
 | 
					    samples = std::max(samples, (size_t) this->fadein_frame_samples_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::lock_guard buffer_lock{this->buffer_mutex};
 | 
				
			||||||
 | 
					    if(samples > this->max_buffered_samples_) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this->min_buffered_samples_ = samples;
 | 
				
			||||||
 | 
					    switch(this->buffer_state) {
 | 
				
			||||||
 | 
					        case buffer_state::fadeout:
 | 
				
			||||||
 | 
					        case buffer_state::buffering: {
 | 
				
			||||||
 | 
					            assert(this->currently_buffered_samples() >= this->fadeout_samples_left);
 | 
				
			||||||
 | 
					            const auto buffered_samples = this->currently_buffered_samples() - this->fadeout_samples_left;
 | 
				
			||||||
 | 
					            if(buffered_samples > this->min_buffered_samples_) {
 | 
				
			||||||
 | 
					                log_trace(category::audio, tr("{} Finished buffering {} samples (due to min buffered sample reduce). Fading them in."), (void*) this, this->min_buffered_samples_);
 | 
				
			||||||
 | 
					                this->apply_fadein();
 | 
				
			||||||
 | 
					                this->buffer_state = buffer_state::playing;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case buffer_state::playing:
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            assert(false);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AudioOutput::AudioOutput(size_t channels, size_t rate) : channel_count_(channels), sample_rate_(rate) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AudioOutput::~AudioOutput() {
 | 
					AudioOutput::~AudioOutput() {
 | 
				
			||||||
	this->close_device();
 | 
						this->close_device();
 | 
				
			||||||
@ -255,25 +322,14 @@ AudioOutput::~AudioOutput() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::shared_ptr<AudioOutputSource> AudioOutput::create_source(ssize_t buf) {
 | 
					std::shared_ptr<AudioOutputSource> AudioOutput::create_source(ssize_t buf) {
 | 
				
			||||||
	auto result = shared_ptr<AudioOutputSource>(new AudioOutputSource(this, this->_channel_count, this->_sample_rate, buf));
 | 
						auto result = std::shared_ptr<AudioOutputSource>(new AudioOutputSource(this->channel_count_, this->sample_rate_, buf));
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		lock_guard lock(this->sources_lock);
 | 
					        std::lock_guard source_lock{this->sources_mutex};
 | 
				
			||||||
		this->_sources.push_back(result);
 | 
							this->sources_.push_back(result);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AudioOutput::delete_source(const std::shared_ptr<tc::audio::AudioOutputSource> &source) {
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		lock_guard lock(this->sources_lock);
 | 
					 | 
				
			||||||
		auto it = find(this->_sources.begin(), this->_sources.end(), source);
 | 
					 | 
				
			||||||
		if(it != this->_sources.end())
 | 
					 | 
				
			||||||
			this->_sources.erase(it);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	source->handle = nullptr;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void AudioOutput::cleanup_buffers() {
 | 
					void AudioOutput::cleanup_buffers() {
 | 
				
			||||||
    free(this->source_buffer);
 | 
					    free(this->source_buffer);
 | 
				
			||||||
    free(this->source_merge_buffer);
 | 
					    free(this->source_merge_buffer);
 | 
				
			||||||
@ -290,50 +346,67 @@ void AudioOutput::cleanup_buffers() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t out_channels) {
 | 
					void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t out_channels) {
 | 
				
			||||||
    if(out_channels != this->_channel_count) {
 | 
					    if(out_channels != this->channel_count_) {
 | 
				
			||||||
        log_critical(category::audio, tr("Channel count miss match (output)! Expected: {} Received: {}. Fixme!"), this->_channel_count, out_channels);
 | 
					        log_critical(category::audio, tr("Channel count miss match (output)! Expected: {} Received: {}. Fixme!"), this->channel_count_, out_channels);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    auto local_frame_count = this->_resampler ? this->_resampler->input_size(out_frame_count) : out_frame_count;
 | 
					    auto local_frame_count = this->resampler_ ? this->resampler_->input_size(out_frame_count) : out_frame_count;
 | 
				
			||||||
    void* const original_output{output};
 | 
					    void* const original_output{output};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(this->resample_overhead_samples > 0) {
 | 
					    if(this->resample_overhead_samples > 0) {
 | 
				
			||||||
        const auto samples_to_write = this->resample_overhead_samples > out_frame_count ? out_frame_count : this->resample_overhead_samples;
 | 
					        const auto samples_to_write = this->resample_overhead_samples > out_frame_count ? out_frame_count : this->resample_overhead_samples;
 | 
				
			||||||
        const auto byte_length = samples_to_write * sizeof(float) * out_channels;
 | 
					        const auto byte_length = samples_to_write * sizeof(float) * out_channels;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(output) memcpy(output, this->resample_overhead_buffer, byte_length);
 | 
					        if(output) {
 | 
				
			||||||
 | 
					            memcpy(output, this->resample_overhead_buffer, byte_length);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if(samples_to_write == out_frame_count) {
 | 
					        if(samples_to_write == out_frame_count) {
 | 
				
			||||||
            this->resample_overhead_samples -= samples_to_write;
 | 
					            this->resample_overhead_samples -= samples_to_write;
 | 
				
			||||||
            memcpy(this->resample_overhead_buffer, (char*) this->resample_overhead_buffer + byte_length, this->resample_overhead_samples * this->_channel_count * sizeof(float));
 | 
					            memcpy(this->resample_overhead_buffer, (char*) this->resample_overhead_buffer + byte_length, this->resample_overhead_samples * this->channel_count_ * sizeof(float));
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            this->resample_overhead_samples = 0;
 | 
					            this->resample_overhead_samples = 0;
 | 
				
			||||||
            output = (char*) output + byte_length;
 | 
					            output = (char*) output + byte_length;
 | 
				
			||||||
            out_frame_count -= samples_to_write;
 | 
					            out_frame_count -= samples_to_write;
 | 
				
			||||||
            local_frame_count -= this->_resampler ? this->_resampler->input_size(samples_to_write) : samples_to_write;
 | 
					            local_frame_count -= this->resampler_ ? this->resampler_->input_size(samples_to_write) : samples_to_write;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(!original_output) {
 | 
					    if(!original_output) {
 | 
				
			||||||
        for(auto& source : this->_sources)
 | 
					        this->sources_.erase(std::remove_if(this->sources_.begin(), this->sources_.end(), [&](const std::weak_ptr<AudioOutputSource>& weak_source) {
 | 
				
			||||||
 | 
					            auto source = weak_source.lock();
 | 
				
			||||||
 | 
					            if(!source) {
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            source->pop_samples(nullptr, local_frame_count);
 | 
					            source->pop_samples(nullptr, local_frame_count);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }), this->sources_.end());
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    } else if(this->_volume <= 0) {
 | 
					    } else if(this->volume_modifier <= 0) {
 | 
				
			||||||
        for(auto& source : this->_sources)
 | 
					        this->sources_.erase(std::remove_if(this->sources_.begin(), this->sources_.end(), [&](const std::weak_ptr<AudioOutputSource>& weak_source) {
 | 
				
			||||||
 | 
					            auto source = weak_source.lock();
 | 
				
			||||||
 | 
					            if(!source) {
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            source->pop_samples(nullptr, local_frame_count);
 | 
					            source->pop_samples(nullptr, local_frame_count);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }), this->sources_.end());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        memset(output, 0, local_frame_count * out_channels * sizeof(float));
 | 
					        memset(output, 0, local_frame_count * out_channels * sizeof(float));
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const size_t local_buffer_length = local_frame_count * 4 * this->_channel_count;
 | 
						const size_t local_buffer_length = local_frame_count * 4 * this->channel_count_;
 | 
				
			||||||
    const size_t out_buffer_length = out_frame_count * 4 * this->_channel_count;
 | 
					    const size_t out_buffer_length = out_frame_count * 4 * this->channel_count_;
 | 
				
			||||||
	size_t sources = 0;
 | 
						size_t sources = 0;
 | 
				
			||||||
	size_t actual_sources = 0;
 | 
						size_t actual_sources;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		lock_guard lock(this->sources_lock);
 | 
							lock_guard sources_lock{this->sources_mutex};
 | 
				
			||||||
		sources = this->_sources.size();
 | 
							sources = this->sources_.size();
 | 
				
			||||||
		actual_sources = sources;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if(sources > 0) {
 | 
							if(sources > 0) {
 | 
				
			||||||
			 /* allocate the required space */
 | 
								 /* allocate the required space */
 | 
				
			||||||
@ -343,59 +416,69 @@ void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t out_c
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if(this->source_buffer_length < required_source_buffer_length || !this->source_buffer) {
 | 
					                if(this->source_buffer_length < required_source_buffer_length || !this->source_buffer) {
 | 
				
			||||||
                    if(this->source_buffer)
 | 
					                    if(this->source_buffer) {
 | 
				
			||||||
                        free(this->source_buffer);
 | 
					                        free(this->source_buffer);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    this->source_buffer = malloc(required_source_buffer_length);
 | 
					                    this->source_buffer = malloc(required_source_buffer_length);
 | 
				
			||||||
                    this->source_buffer_length = required_source_buffer_length;
 | 
					                    this->source_buffer_length = required_source_buffer_length;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if(this->source_merge_buffer_length < required_source_merge_buffer_length || !this->source_merge_buffer) {
 | 
					                if(this->source_merge_buffer_length < required_source_merge_buffer_length || !this->source_merge_buffer) {
 | 
				
			||||||
                    if (this->source_merge_buffer)
 | 
					                    if (this->source_merge_buffer) {
 | 
				
			||||||
                        free(this->source_merge_buffer);
 | 
					                        free(this->source_merge_buffer);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    this->source_merge_buffer = (void **) malloc(required_source_merge_buffer_length);
 | 
					                    this->source_merge_buffer = (void **) malloc(required_source_merge_buffer_length);
 | 
				
			||||||
                    this->source_merge_buffer_length = required_source_merge_buffer_length;
 | 
					                    this->source_merge_buffer_length = required_source_merge_buffer_length;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for(size_t index = 0; index < sources; index++) {
 | 
					            size_t index{0};
 | 
				
			||||||
				auto& source = this->_sources[index];
 | 
					            this->sources_.erase(std::remove_if(this->sources_.begin(), this->sources_.end(), [&](const std::weak_ptr<AudioOutputSource>& weak_source) {
 | 
				
			||||||
 | 
					                auto source = weak_source.lock();
 | 
				
			||||||
 | 
					                if(!source) {
 | 
				
			||||||
 | 
					                    return true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                this->source_merge_buffer[index] = (char*) this->source_buffer + (local_buffer_length * index);
 | 
					                this->source_merge_buffer[index] = (char*) this->source_buffer + (local_buffer_length * index);
 | 
				
			||||||
                auto written_frames = this->_sources[index]->pop_samples(this->source_merge_buffer[index], local_frame_count);
 | 
					                if(!source->pop_samples(this->source_merge_buffer[index], local_frame_count)) {
 | 
				
			||||||
                if(written_frames != local_frame_count) {
 | 
					                    this->source_merge_buffer[index] = nullptr;
 | 
				
			||||||
                    if(written_frames <= 0) {
 | 
					                    return false;
 | 
				
			||||||
                        this->source_merge_buffer[index] = nullptr;
 | 
					 | 
				
			||||||
                        actual_sources--;
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        /* fill up the rest with silence (0) */
 | 
					 | 
				
			||||||
                        auto written = written_frames * this->_channel_count * 4;
 | 
					 | 
				
			||||||
                        memset((char*) this->source_merge_buffer[index] + written, 0, (local_frame_count - written_frames) * this->_channel_count * 4);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
			}
 | 
					
 | 
				
			||||||
		} else
 | 
					                index++;
 | 
				
			||||||
		    goto clear_buffer_exit;
 | 
					                return false;
 | 
				
			||||||
 | 
					            }), this->sources_.end());
 | 
				
			||||||
 | 
					            actual_sources = index;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
					            goto clear_buffer_exit;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(actual_sources > 0) {
 | 
						if(actual_sources > 0) {
 | 
				
			||||||
	    if(local_frame_count == out_frame_count) {
 | 
						    if(local_frame_count == out_frame_count) {
 | 
				
			||||||
            if(!merge::merge_n_sources(output, this->source_merge_buffer, sources, this->_channel_count, local_frame_count))
 | 
						        /* Output */
 | 
				
			||||||
 | 
					            if(!merge::merge_n_sources(output, this->source_merge_buffer, sources, this->channel_count_, local_frame_count)) {
 | 
				
			||||||
                log_warn(category::audio, tr("failed to merge buffers!"));
 | 
					                log_warn(category::audio, tr("failed to merge buffers!"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
	    } else {
 | 
						    } else {
 | 
				
			||||||
            if(!merge::merge_n_sources(this->source_buffer, this->source_merge_buffer, sources, this->_channel_count, local_frame_count))
 | 
					            if(!merge::merge_n_sources(this->source_buffer, this->source_merge_buffer, sources, this->channel_count_, local_frame_count)) {
 | 
				
			||||||
                log_warn(category::audio, tr("failed to merge buffers!"));
 | 
					                log_warn(category::audio, tr("failed to merge buffers!"));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            /* this->source_buffer could hold the amount of resampled data (checked above) */
 | 
					            /* this->source_buffer could hold the amount of resampled data (checked above) */
 | 
				
			||||||
            auto resampled_samples = this->_resampler->process(this->source_buffer, this->source_buffer, local_frame_count);
 | 
					            auto resampled_samples = this->resampler_->process(this->source_buffer, this->source_buffer, local_frame_count);
 | 
				
			||||||
            if(resampled_samples <= 0) {
 | 
					            if(resampled_samples <= 0) {
 | 
				
			||||||
                log_warn(category::audio, tr("Failed to resample audio data for client ({})"));
 | 
					                log_warn(category::audio, tr("Failed to resample audio data for client ({})"));
 | 
				
			||||||
                goto clear_buffer_exit;
 | 
					                goto clear_buffer_exit;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if(resampled_samples != out_frame_count) {
 | 
					            if(resampled_samples != out_frame_count) {
 | 
				
			||||||
                if((size_t) resampled_samples > out_frame_count) {
 | 
					                if((size_t) resampled_samples > out_frame_count) {
 | 
				
			||||||
                    const auto diff_length = resampled_samples - out_frame_count;
 | 
					                    const auto diff_length = resampled_samples - out_frame_count;
 | 
				
			||||||
                    log_warn(category::audio, tr("enqueuing {} samples"), diff_length);
 | 
					                    log_warn(category::audio, tr("enqueuing {} samples"), diff_length);
 | 
				
			||||||
                    const auto overhead_buffer_offset = this->resample_overhead_samples * sizeof(float) * this->_channel_count;
 | 
					                    const auto overhead_buffer_offset = this->resample_overhead_samples * sizeof(float) * this->channel_count_;
 | 
				
			||||||
                    const auto diff_byte_length = diff_length * sizeof(float) * this->_channel_count;
 | 
					                    const auto diff_byte_length = diff_length * sizeof(float) * this->channel_count_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if(this->resample_overhead_buffer_length < diff_byte_length + overhead_buffer_offset) {
 | 
					                    if(this->resample_overhead_buffer_length < diff_byte_length + overhead_buffer_offset) {
 | 
				
			||||||
                        this->resample_overhead_buffer_length = diff_byte_length + overhead_buffer_offset;
 | 
					                        this->resample_overhead_buffer_length = diff_byte_length + overhead_buffer_offset;
 | 
				
			||||||
@ -407,22 +490,23 @@ void AudioOutput::fill_buffer(void *output, size_t out_frame_count, size_t out_c
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    memcpy(
 | 
					                    memcpy(
 | 
				
			||||||
                            (char*) this->resample_overhead_buffer + overhead_buffer_offset,
 | 
					                            (char*) this->resample_overhead_buffer + overhead_buffer_offset,
 | 
				
			||||||
                            (char*) this->source_buffer + out_frame_count * sizeof(float) * this->_channel_count,
 | 
					                            (char*) this->source_buffer + out_frame_count * sizeof(float) * this->channel_count_,
 | 
				
			||||||
                            diff_byte_length
 | 
					                            diff_byte_length
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
                    this->resample_overhead_samples += diff_length;
 | 
					                    this->resample_overhead_samples += diff_length;
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    log_warn(category::audio, tr("Resampled samples does not match requested sampeles: {} <> {}. Sampled from {} to {}"), resampled_samples, out_frame_count, this->_resampler->input_rate(), this->_resampler->output_rate());
 | 
					                    log_warn(category::audio, tr("Resampled samples does not match requested sampeles: {} <> {}. Sampled from {} to {}"), resampled_samples, out_frame_count, this->resampler_->input_rate(), this->resampler_->output_rate());
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            memcpy(output, this->source_buffer, out_frame_count * sizeof(float) * this->_channel_count);
 | 
					
 | 
				
			||||||
 | 
					            memcpy(output, this->source_buffer, out_frame_count * sizeof(float) * this->channel_count_);
 | 
				
			||||||
	    }
 | 
						    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	    /* lets apply the volume */
 | 
						    /* lets apply the volume */
 | 
				
			||||||
        audio::apply_gain(output, this->_channel_count, out_frame_count, this->_volume);
 | 
					        audio::apply_gain(output, this->channel_count_, out_frame_count, this->volume_modifier);
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
        clear_buffer_exit:
 | 
					        clear_buffer_exit:
 | 
				
			||||||
		memset(output, 0, this->_channel_count * sizeof(float) * out_frame_count);
 | 
							memset(output, 0, this->channel_count_ * sizeof(float) * out_frame_count);
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -437,13 +521,13 @@ void AudioOutput::set_device(const std::shared_ptr<AudioDevice> &new_device) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void AudioOutput::close_device() {
 | 
					void AudioOutput::close_device() {
 | 
				
			||||||
    lock_guard lock(this->device_lock);
 | 
					    lock_guard lock(this->device_lock);
 | 
				
			||||||
    if(this->_playback) {
 | 
					    if(this->playback_) {
 | 
				
			||||||
        this->_playback->remove_source(this);
 | 
					        this->playback_->remove_source(this);
 | 
				
			||||||
        this->_playback->stop_if_possible();
 | 
					        this->playback_->stop_if_possible();
 | 
				
			||||||
        this->_playback.reset();
 | 
					        this->playback_.reset();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this->_resampler = nullptr;
 | 
					    this->resampler_ = nullptr;
 | 
				
			||||||
    this->device = nullptr;
 | 
					    this->device = nullptr;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -453,23 +537,23 @@ bool AudioOutput::playback(std::string& error) {
 | 
				
			|||||||
	    error = "invalid device handle";
 | 
						    error = "invalid device handle";
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
    if(this->_playback) return true;
 | 
					    if(this->playback_) return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this->_playback = this->device->playback();
 | 
						this->playback_ = this->device->playback();
 | 
				
			||||||
	if(!this->_playback) {
 | 
						if(!this->playback_) {
 | 
				
			||||||
	    error = "failed to allocate memory";
 | 
						    error = "failed to allocate memory";
 | 
				
			||||||
        return false;
 | 
					        return false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(this->_playback->sample_rate() != this->sample_rate()) {
 | 
					    if(this->playback_->sample_rate() != this->sample_rate()) {
 | 
				
			||||||
        this->_resampler = std::make_unique<AudioResampler>(this->sample_rate(), this->_playback->sample_rate(), this->channel_count());
 | 
					        this->resampler_ = std::make_unique<AudioResampler>(this->sample_rate(), this->playback_->sample_rate(), this->channel_count());
 | 
				
			||||||
        if(!this->_resampler->valid()) {
 | 
					        if(!this->resampler_->valid()) {
 | 
				
			||||||
            error = "failed to allocate a resampler";
 | 
					            error = "failed to allocate a resampler";
 | 
				
			||||||
            this->_playback = nullptr;
 | 
					            this->playback_ = nullptr;
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this->_playback->register_source(this);
 | 
						this->playback_->register_source(this);
 | 
				
			||||||
    return this->_playback->start(error);
 | 
					    return this->playback_->start(error);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -25,61 +25,96 @@ namespace tc::audio {
 | 
				
			|||||||
				discard_input
 | 
									discard_input
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class AudioOutputSource {
 | 
					        class AudioOutputSource {
 | 
				
			||||||
			friend class AudioOutput;
 | 
								friend class AudioOutput;
 | 
				
			||||||
			public:
 | 
								public:
 | 
				
			||||||
				AudioOutput* handle;
 | 
									size_t const channel_count;
 | 
				
			||||||
 | 
									size_t const sample_rate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				size_t const channel_count = 0;
 | 
									/**
 | 
				
			||||||
				size_t const sample_rate = 0;
 | 
									 * The maximum amount of samples which could be buffered.
 | 
				
			||||||
 | 
									 * @return
 | 
				
			||||||
				[[nodiscard]] inline size_t max_supported_latency() const {
 | 
									 */
 | 
				
			||||||
 | 
									[[nodiscard]] inline size_t max_supported_buffering() const {
 | 
				
			||||||
                    return this->buffer.capacity() / this->channel_count / sizeof(float);
 | 
					                    return this->buffer.capacity() / this->channel_count / sizeof(float);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                [[nodiscard]] inline size_t max_latency() const {
 | 
					                [[nodiscard]] inline size_t max_buffering() const {
 | 
				
			||||||
				    const auto max_samples = this->max_supported_latency();
 | 
									    const auto max_samples = this->max_supported_buffering();
 | 
				
			||||||
				    if(this->max_buffered_samples && this->max_buffered_samples <= max_samples) return this->max_buffered_samples;
 | 
									    if(this->max_buffered_samples_ && this->max_buffered_samples_ <= max_samples) {
 | 
				
			||||||
 | 
					                        return this->max_buffered_samples_;
 | 
				
			||||||
 | 
									    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    return max_samples;
 | 
					                    return max_samples;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                /* samples which needs to be played*/
 | 
					                /**
 | 
				
			||||||
                [[nodiscard]] inline size_t current_latency() const {
 | 
					                 * Sample count which still need to be replayed before newly emplaced buffers will be played.
 | 
				
			||||||
 | 
					                 * @return
 | 
				
			||||||
 | 
					                 */
 | 
				
			||||||
 | 
					                [[nodiscard]] inline size_t currently_buffered_samples() const {
 | 
				
			||||||
				    return this->buffer.fill_count() / this->channel_count / sizeof(float);
 | 
									    return this->buffer.fill_count() / this->channel_count / sizeof(float);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				bool buffering{true};
 | 
					 | 
				
			||||||
                char* fade_in_start{nullptr};
 | 
					 | 
				
			||||||
                ssize_t will_buffer_in{-1};
 | 
					 | 
				
			||||||
				size_t min_buffered_samples{0};
 | 
					 | 
				
			||||||
				size_t max_buffered_samples{0};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				size_t fadeout_sample_length{360};
 | 
									[[nodiscard]] inline size_t min_buffered_samples() const { return this->min_buffered_samples_; }
 | 
				
			||||||
 | 
					                [[nodiscard]] inline size_t max_buffered_samples() const { return this->max_buffered_samples_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				overflow_strategy::value overflow_strategy = overflow_strategy::discard_buffer_half;
 | 
					                bool set_min_buffered_samples(size_t /* target samples */);
 | 
				
			||||||
 | 
					                bool set_max_buffered_samples(size_t /* target samples */);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									overflow_strategy::value overflow_strategy{overflow_strategy::discard_buffer_half};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				/* if it returns true then the it means that the buffer has been refilled, we have to test again */
 | 
									/* if it returns true then the it means that the buffer has been refilled, we have to test again */
 | 
				
			||||||
				std::function<bool(size_t /* sample count */)> on_underflow;
 | 
									std::function<bool(size_t /* sample count */)> on_underflow;
 | 
				
			||||||
				std::function<void(size_t /* sample count */)> on_overflow;
 | 
									std::function<void(size_t /* sample count */)> on_overflow;
 | 
				
			||||||
				std::function<void()> on_read; /* will be invoked after sample read, e.g. for buffer fullup */
 | 
									std::function<void()> on_read; /* will be invoked after sample read, e.g. for buffer fullup */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				void clear();
 | 
					                void clear();
 | 
				
			||||||
				ssize_t pop_samples(void* /* output buffer */, size_t /* sample count */);
 | 
					 | 
				
			||||||
				ssize_t enqueue_silence(size_t /* sample count */);
 | 
					 | 
				
			||||||
				ssize_t enqueue_samples(const void * /* input buffer */, size_t /* sample count */);
 | 
									ssize_t enqueue_samples(const void * /* input buffer */, size_t /* sample count */);
 | 
				
			||||||
				ssize_t enqueue_samples_no_interleave(const void * /* input buffer */, size_t /* sample count */);
 | 
									ssize_t enqueue_samples_no_interleave(const void * /* input buffer */, size_t /* sample count */);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									/* Consume N samples */
 | 
				
			||||||
 | 
					                bool pop_samples(void* /* output buffer */, size_t /* sample count */);
 | 
				
			||||||
			private:
 | 
								private:
 | 
				
			||||||
				AudioOutputSource(AudioOutput* handle, size_t channel_count, size_t sample_rate, ssize_t max_buffer_sample_count = -1) :
 | 
							        enum struct buffer_state {
 | 
				
			||||||
				    handle(handle), channel_count(channel_count), sample_rate(sample_rate),
 | 
							            /* Awaiting enough samples to replay and apply the fadein effect */
 | 
				
			||||||
				    buffer{max_buffer_sample_count == -1 ? channel_count * sample_rate * sizeof(float) : max_buffer_sample_count * channel_count * sizeof(float)} {
 | 
							            buffering,
 | 
				
			||||||
 | 
							            /* We have encountered a buffer underflow. Applying fadeout effect and changing state to buffering. */
 | 
				
			||||||
 | 
							            fadeout,
 | 
				
			||||||
 | 
							            /* We're just normally replaying audio */
 | 
				
			||||||
 | 
							            playing
 | 
				
			||||||
 | 
							        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									AudioOutputSource(size_t channel_count, size_t sample_rate, ssize_t max_buffer_sample_count = -1) :
 | 
				
			||||||
 | 
									    channel_count{channel_count}, sample_rate{sample_rate},
 | 
				
			||||||
 | 
									    buffer{max_buffer_sample_count == -1 ? channel_count * sample_rate * sizeof(float) : max_buffer_sample_count * channel_count * sizeof(float)}
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
					this->clear();
 | 
										this->clear();
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				void do_fade_out(size_t /* pop count */);
 | 
									std::recursive_mutex buffer_mutex{};
 | 
				
			||||||
				void do_fade_in();
 | 
									enum buffer_state buffer_state{buffer_state::buffering};
 | 
				
			||||||
 | 
					 | 
				
			||||||
				tc::ring_buffer buffer;
 | 
									tc::ring_buffer buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                size_t min_buffered_samples_{0};
 | 
				
			||||||
 | 
					                size_t max_buffered_samples_{0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									/*
 | 
				
			||||||
 | 
									 * Fadeout and fadein properties.
 | 
				
			||||||
 | 
									 * The fadeout sample count should always be lower than the fade in sample count.
 | 
				
			||||||
 | 
									 */
 | 
				
			||||||
 | 
					                size_t fadein_frame_samples_{960};
 | 
				
			||||||
 | 
									size_t fadeout_frame_samples_{(size_t) (960 * .9)};
 | 
				
			||||||
 | 
					                size_t fadeout_samples_left{0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									/* Methods bellow do not acquire the buffer_mutex mutex */
 | 
				
			||||||
 | 
					                ssize_t enqueue_samples_(const void * /* input buffer */, size_t /* sample count */);
 | 
				
			||||||
 | 
					                bool pop_samples_(void* /* output buffer */, size_t /* sample count */);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                void apply_fadeout();
 | 
				
			||||||
 | 
					                void apply_fadein();
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class AudioOutput : public AudioDevicePlayback::Source {
 | 
					        class AudioOutput : public AudioDevicePlayback::Source {
 | 
				
			||||||
@ -92,36 +127,35 @@ namespace tc::audio {
 | 
				
			|||||||
				void close_device();
 | 
									void close_device();
 | 
				
			||||||
                std::shared_ptr<AudioDevice> current_device() { return this->device; }
 | 
					                std::shared_ptr<AudioDevice> current_device() { return this->device; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				std::deque<std::shared_ptr<AudioOutputSource>> sources() {
 | 
									std::deque<std::weak_ptr<AudioOutputSource>> sources() {
 | 
				
			||||||
					std::lock_guard lock(this->sources_lock);
 | 
										std::lock_guard sources_lock{this->sources_mutex};
 | 
				
			||||||
					return this->_sources;
 | 
										return this->sources_;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				std::shared_ptr<AudioOutputSource> create_source(ssize_t /* buffer sample size */ = -1);
 | 
									std::shared_ptr<AudioOutputSource> create_source(ssize_t /* buffer sample size */ = -1);
 | 
				
			||||||
				void delete_source(const std::shared_ptr<AudioOutputSource>& /* source */);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				inline size_t channel_count() { return this->_channel_count; }
 | 
									[[nodiscard]] inline size_t channel_count() const { return this->channel_count_; }
 | 
				
			||||||
				inline size_t sample_rate() { return this->_sample_rate; }
 | 
									[[nodiscard]] inline size_t sample_rate() const { return this->sample_rate_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				inline float volume() { return this->_volume; }
 | 
									[[nodiscard]] inline float volume() const { return this->volume_modifier; }
 | 
				
			||||||
				inline void set_volume(float value) { this->_volume = value; }
 | 
									inline void set_volume(float value) { this->volume_modifier = value; }
 | 
				
			||||||
			private:
 | 
								private:
 | 
				
			||||||
				void fill_buffer(void *, size_t out_frame_count, size_t out_channels) override;
 | 
									void fill_buffer(void *, size_t out_frame_count, size_t out_channels) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				size_t const _channel_count;
 | 
									size_t const channel_count_;
 | 
				
			||||||
				size_t const _sample_rate;
 | 
									size_t const sample_rate_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				std::mutex sources_lock;
 | 
									std::mutex sources_mutex{};
 | 
				
			||||||
				std::deque<std::shared_ptr<AudioOutputSource>> _sources;
 | 
									std::deque<std::weak_ptr<AudioOutputSource>> sources_{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				std::recursive_mutex device_lock;
 | 
									std::recursive_mutex device_lock{};
 | 
				
			||||||
                std::shared_ptr<AudioDevice> device{nullptr};
 | 
					                std::shared_ptr<AudioDevice> device{nullptr};
 | 
				
			||||||
                std::shared_ptr<AudioDevicePlayback> _playback{nullptr};
 | 
					                std::shared_ptr<AudioDevicePlayback> playback_{nullptr};
 | 
				
			||||||
                std::unique_ptr<AudioResampler> _resampler{nullptr};
 | 
					                std::unique_ptr<AudioResampler> resampler_{nullptr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                /* only access there buffers within the audio loop! */
 | 
					                /* only access there buffers within the audio loop! */
 | 
				
			||||||
				void* source_buffer = nullptr;
 | 
									void* source_buffer{nullptr};
 | 
				
			||||||
				void** source_merge_buffer = nullptr;
 | 
									void** source_merge_buffer{nullptr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                void* resample_overhead_buffer{nullptr};
 | 
					                void* resample_overhead_buffer{nullptr};
 | 
				
			||||||
                size_t resample_overhead_buffer_length{0};
 | 
					                size_t resample_overhead_buffer_length{0};
 | 
				
			||||||
@ -131,6 +165,6 @@ namespace tc::audio {
 | 
				
			|||||||
				size_t source_merge_buffer_length = 0;
 | 
									size_t source_merge_buffer_length = 0;
 | 
				
			||||||
				void cleanup_buffers();
 | 
									void cleanup_buffers();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				float _volume = 1.f;
 | 
									float volume_modifier{1.f};
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -11,8 +11,8 @@ namespace tc::audio {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            void process(const void* /* source */, size_t /* samples */);
 | 
					            void process(const void* /* source */, size_t /* samples */);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            inline size_t channels() { return this->_channels; }
 | 
					            inline size_t channels() const { return this->_channels; }
 | 
				
			||||||
            inline size_t frame_size() { return this->_frame_size; }
 | 
					            inline size_t frame_size() const { return this->_frame_size; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            std::function<void(const void* /* buffer */)> on_frame;
 | 
					            std::function<void(const void* /* buffer */)> on_frame;
 | 
				
			||||||
        private:
 | 
					        private:
 | 
				
			||||||
 | 
				
			|||||||
@ -16,16 +16,16 @@ namespace tc::audio {
 | 
				
			|||||||
				AudioResampler(size_t /* input rate */, size_t /* output rate */, size_t /* channels */);
 | 
									AudioResampler(size_t /* input rate */, size_t /* output rate */, size_t /* channels */);
 | 
				
			||||||
				virtual ~AudioResampler();
 | 
									virtual ~AudioResampler();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                [[nodiscard]] inline size_t channels() { return this->_channels; }
 | 
					                [[nodiscard]] inline size_t channels() const { return this->_channels; }
 | 
				
			||||||
                [[nodiscard]] inline size_t input_rate() { return this->_input_rate; }
 | 
					                [[nodiscard]] inline size_t input_rate() const { return this->_input_rate; }
 | 
				
			||||||
                [[nodiscard]] inline size_t output_rate() { return this->_output_rate; }
 | 
					                [[nodiscard]] inline size_t output_rate() const { return this->_output_rate; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                [[nodiscard]] inline long double io_ratio() { return (long double) this->_output_rate / (long double) this->_input_rate; }
 | 
					                [[nodiscard]] inline long double io_ratio() const { return (long double) this->_output_rate / (long double) this->_input_rate; }
 | 
				
			||||||
                [[nodiscard]] inline size_t estimated_output_size(size_t input_length) {
 | 
					                [[nodiscard]] inline size_t estimated_output_size(size_t input_length) {
 | 
				
			||||||
                    if(!this->soxr_handle) return input_length; /* no resembling needed */
 | 
					                    if(!this->soxr_handle) return input_length; /* no resembling needed */
 | 
				
			||||||
					return (size_t) ceill(this->io_ratio() * input_length + *soxr_num_clips(this->soxr_handle)) + 1;
 | 
										return (size_t) ceill(this->io_ratio() * input_length + *soxr_num_clips(this->soxr_handle)) + 1;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
                [[nodiscard]] inline size_t input_size(size_t output_length) {
 | 
					                [[nodiscard]] inline size_t input_size(size_t output_length) const {
 | 
				
			||||||
                    return (size_t) ceill((long double) this->_input_rate / (long double) this->_output_rate * output_length);
 | 
					                    return (size_t) ceill((long double) this->_input_rate / (long double) this->_output_rate * output_length);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -3,38 +3,35 @@
 | 
				
			|||||||
#include <cstdint>
 | 
					#include <cstdint>
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace tc {
 | 
					namespace tc::audio {
 | 
				
			||||||
	namespace audio {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifdef WIN32
 | 
					#ifdef WIN32
 | 
				
			||||||
        #pragma pack(push,1)
 | 
					    #pragma pack(push,1)
 | 
				
			||||||
        #define __attribute__packed_1
 | 
					    #define __attribute__packed_1
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
        #define __attribute__packed_1 __attribute__((packed, aligned(1)))
 | 
					    #define __attribute__packed_1 __attribute__((packed, aligned(1)))
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
		/* Every sample is a float (4byte) */
 | 
					    /* Every sample is a float (4byte) */
 | 
				
			||||||
		struct __attribute__packed_1 SampleBuffer {
 | 
					    struct __attribute__packed_1 SampleBuffer {
 | 
				
			||||||
			static constexpr size_t HEAD_LENGTH = 4;
 | 
					        static constexpr size_t HEAD_LENGTH = 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			uint16_t sample_size;
 | 
					        uint16_t sample_size;
 | 
				
			||||||
			uint16_t sample_index;
 | 
					        uint16_t sample_index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			char sample_data[
 | 
					        char sample_data[
 | 
				
			||||||
#ifndef WIN32
 | 
					#ifndef WIN32
 | 
				
			||||||
					0
 | 
					                0
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
					1 /* windows does not allow zero sized arrays */
 | 
					                1 /* windows does not allow zero sized arrays */
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
			];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			static std::shared_ptr<SampleBuffer> allocate(uint8_t /* channels */, uint16_t /* samples */);
 | 
					        static std::shared_ptr<SampleBuffer> allocate(uint8_t /* channels */, uint16_t /* samples */);
 | 
				
			||||||
		};
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef WIN32
 | 
					#ifndef WIN32
 | 
				
			||||||
		static_assert(sizeof(SampleBuffer) == 4, "Invalid SampleBuffer packaging!");
 | 
					    static_assert(sizeof(SampleBuffer) == 4, "Invalid SampleBuffer packaging!");
 | 
				
			||||||
#else
 | 
					#else
 | 
				
			||||||
        #pragma pack(pop)
 | 
					    #pragma pack(pop)
 | 
				
			||||||
		static_assert(sizeof(SampleBuffer) == 5, "Invalid SampleBuffer packaging!");
 | 
					    static_assert(sizeof(SampleBuffer) == 5, "Invalid SampleBuffer packaging!");
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -50,10 +50,6 @@ AudioOutputStreamWrapper::~AudioOutputStreamWrapper() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void AudioOutputStreamWrapper::drop_stream() {
 | 
					void AudioOutputStreamWrapper::drop_stream() {
 | 
				
			||||||
	if(this->_own_handle) {
 | 
						if(this->_own_handle) {
 | 
				
			||||||
		auto handle = this->_own_handle->handle;
 | 
					 | 
				
			||||||
		if(handle) {
 | 
					 | 
				
			||||||
			handle->delete_source(this->_own_handle);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		this->_own_handle->on_underflow = nullptr;
 | 
							this->_own_handle->on_underflow = nullptr;
 | 
				
			||||||
		this->_own_handle->on_overflow = nullptr;
 | 
							this->_own_handle->on_overflow = nullptr;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -112,7 +108,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_clear) {
 | 
				
			|||||||
NAN_METHOD(AudioOutputStreamWrapper::_deleted) {
 | 
					NAN_METHOD(AudioOutputStreamWrapper::_deleted) {
 | 
				
			||||||
	auto client = ObjectWrap::Unwrap<AudioOutputStreamWrapper>(info.Holder());
 | 
						auto client = ObjectWrap::Unwrap<AudioOutputStreamWrapper>(info.Holder());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	info.GetReturnValue().Set(!client->_own_handle || !client->_own_handle->handle);
 | 
						info.GetReturnValue().Set(!client->_own_handle);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NAN_METHOD(AudioOutputStreamWrapper::_delete) {
 | 
					NAN_METHOD(AudioOutputStreamWrapper::_delete) {
 | 
				
			||||||
@ -121,10 +117,11 @@ NAN_METHOD(AudioOutputStreamWrapper::_delete) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ssize_t AudioOutputStreamWrapper::write_data(const std::shared_ptr<AudioOutputSource>& handle, void *source, size_t samples, bool interleaved) {
 | 
					ssize_t AudioOutputStreamWrapper::write_data(const std::shared_ptr<AudioOutputSource>& handle, void *source, size_t samples, bool interleaved) {
 | 
				
			||||||
	if(interleaved)
 | 
						if(interleaved) {
 | 
				
			||||||
		return handle->enqueue_samples(source, samples);
 | 
					        return handle->enqueue_samples(source, samples);
 | 
				
			||||||
    else
 | 
						} else {
 | 
				
			||||||
	    return handle->enqueue_samples_no_interleave(source, samples);
 | 
					        return handle->enqueue_samples_no_interleave(source, samples);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NAN_METHOD(AudioOutputStreamWrapper::_write_data) {
 | 
					NAN_METHOD(AudioOutputStreamWrapper::_write_data) {
 | 
				
			||||||
@ -223,7 +220,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_latency) {
 | 
				
			|||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	info.GetReturnValue().Set((float) handle->min_buffered_samples / (float) handle->sample_rate);
 | 
						info.GetReturnValue().Set((float) handle->min_buffered_samples() / (float) handle->sample_rate);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_latency) {
 | 
					NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_latency) {
 | 
				
			||||||
@ -240,7 +237,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_latency) {
 | 
				
			|||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	handle->min_buffered_samples = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
 | 
						handle->set_min_buffered_samples((size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0)));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_max_latency) {
 | 
					NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_max_latency) {
 | 
				
			||||||
@ -252,7 +249,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_max_latency) {
 | 
				
			|||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	info.GetReturnValue().Set((float) handle->max_latency() / (float) handle->sample_rate);
 | 
						info.GetReturnValue().Set((float) handle->max_buffering() / (float) handle->sample_rate);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_max_latency) {
 | 
					NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_max_latency) {
 | 
				
			||||||
@ -269,7 +266,7 @@ NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_max_latency) {
 | 
				
			|||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	handle->max_buffered_samples = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
 | 
						handle->set_max_buffered_samples((size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0)));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NAN_METHOD(AudioOutputStreamWrapper::_flush_buffer) {
 | 
					NAN_METHOD(AudioOutputStreamWrapper::_flush_buffer) {
 | 
				
			||||||
 | 
				
			|||||||
@ -72,8 +72,7 @@ namespace tc::audio::sounds {
 | 
				
			|||||||
            } state_{PLAYER_STATE_UNSET};
 | 
					            } state_{PLAYER_STATE_UNSET};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            void finalize(bool is_destructor_call) {
 | 
					            void finalize(bool is_destructor_call) {
 | 
				
			||||||
                if(this->output_source && global_audio_output)
 | 
					                this->output_source = nullptr;
 | 
				
			||||||
                    global_audio_output->delete_source(this->output_source);
 | 
					 | 
				
			||||||
                if(this->file_handle)
 | 
					                if(this->file_handle)
 | 
				
			||||||
                    this->file_handle = nullptr;
 | 
					                    this->file_handle = nullptr;
 | 
				
			||||||
                if(auto buffer{std::exchange(this->cache_buffer, nullptr)}; buffer)
 | 
					                if(auto buffer{std::exchange(this->cache_buffer, nullptr)}; buffer)
 | 
				
			||||||
@ -160,7 +159,7 @@ namespace tc::audio::sounds {
 | 
				
			|||||||
                    this->state_ = PLAYER_STATE_UNSET;
 | 
					                    this->state_ = PLAYER_STATE_UNSET;
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                auto filled_samples = this->output_source->current_latency();
 | 
					                auto filled_samples = this->output_source->currently_buffered_samples();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            void initialize_playback() {
 | 
					            void initialize_playback() {
 | 
				
			||||||
@ -170,8 +169,8 @@ namespace tc::audio::sounds {
 | 
				
			|||||||
                const auto max_buffer = (size_t) ceil(global_audio_output->sample_rate() * kBufferChunkTimespan * 3);
 | 
					                const auto max_buffer = (size_t) ceil(global_audio_output->sample_rate() * kBufferChunkTimespan * 3);
 | 
				
			||||||
                this->output_source = global_audio_output->create_source(max_buffer);
 | 
					                this->output_source = global_audio_output->create_source(max_buffer);
 | 
				
			||||||
                this->output_source->overflow_strategy = audio::overflow_strategy::ignore;
 | 
					                this->output_source->overflow_strategy = audio::overflow_strategy::ignore;
 | 
				
			||||||
                this->output_source->max_buffered_samples = max_buffer;
 | 
					                this->output_source->set_max_buffered_samples(max_buffer);
 | 
				
			||||||
                this->output_source->min_buffered_samples = (size_t) floor(this->output_source->sample_rate * 0.04);
 | 
					                this->output_source->set_min_buffered_samples((size_t) floor(this->output_source->sample_rate * 0.04));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                auto weak_this = this->weak_from_this();
 | 
					                auto weak_this = this->weak_from_this();
 | 
				
			||||||
                this->output_source->on_underflow = [weak_this](size_t sample_count){
 | 
					                this->output_source->on_underflow = [weak_this](size_t sample_count){
 | 
				
			||||||
@ -208,8 +207,8 @@ namespace tc::audio::sounds {
 | 
				
			|||||||
            [[nodiscard]] inline bool could_enqueue_next_buffer() const {
 | 
					            [[nodiscard]] inline bool could_enqueue_next_buffer() const {
 | 
				
			||||||
                if(!this->output_source) return false;
 | 
					                if(!this->output_source) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const auto current_size = this->output_source->current_latency();
 | 
					                const auto current_size = this->output_source->currently_buffered_samples();
 | 
				
			||||||
                const auto max_size = this->output_source->max_buffered_samples;
 | 
					                const auto max_size = this->output_source->max_buffered_samples();
 | 
				
			||||||
                if(current_size > max_size) return false;
 | 
					                if(current_size > max_size) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const auto size_left = max_size - current_size;
 | 
					                const auto size_left = max_size - current_size;
 | 
				
			||||||
 | 
				
			|||||||
@ -223,7 +223,7 @@ VoiceClient::~VoiceClient() {
 | 
				
			|||||||
	this->cancel_replay(); /* cleanup all buffers */
 | 
						this->cancel_replay(); /* cleanup all buffers */
 | 
				
			||||||
	if(this->output_source) {
 | 
						if(this->output_source) {
 | 
				
			||||||
        this->output_source->on_underflow = nullptr; /* to ensure */
 | 
					        this->output_source->on_underflow = nullptr; /* to ensure */
 | 
				
			||||||
        global_audio_output->delete_source(this->output_source);
 | 
					        this->output_source = nullptr;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -232,19 +232,21 @@ void VoiceClient::initialize() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    audio::initialize([weak_this]{
 | 
					    audio::initialize([weak_this]{
 | 
				
			||||||
        auto client = weak_this.lock();
 | 
					        auto client = weak_this.lock();
 | 
				
			||||||
        if(!client) return;
 | 
					        if(!client) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assert(global_audio_output);
 | 
					        assert(global_audio_output);
 | 
				
			||||||
        client->output_source = global_audio_output->create_source();
 | 
					        client->output_source = global_audio_output->create_source();
 | 
				
			||||||
        client->output_source->overflow_strategy = audio::overflow_strategy::ignore;
 | 
					        client->output_source->overflow_strategy = audio::overflow_strategy::ignore;
 | 
				
			||||||
        client->output_source->max_buffered_samples = (size_t) ceil(client->output_source->sample_rate * 0.5);
 | 
					        client->output_source->set_max_buffered_samples((size_t) ceil(client->output_source->sample_rate * 0.5));
 | 
				
			||||||
        client->output_source->min_buffered_samples = (size_t) ceil(client->output_source->sample_rate * 0.04);
 | 
					        client->output_source->set_min_buffered_samples((size_t) ceil(client->output_source->sample_rate * 0.04));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const auto client_ptr = &*client;
 | 
					        const auto client_ptr = &*client;
 | 
				
			||||||
        client->output_source->on_underflow = [client_ptr](size_t sample_count){ /* this callback will never be called when the client has been deallocated */
 | 
					        client->output_source->on_underflow = [client_ptr](size_t sample_count){ /* this callback will never be called when the client has been deallocated */
 | 
				
			||||||
            if(client_ptr->state_ == state::stopping)
 | 
					            if(client_ptr->state_ == state::stopping) {
 | 
				
			||||||
                client_ptr->set_state(state::stopped);
 | 
					                client_ptr->set_state(state::stopped);
 | 
				
			||||||
            else if(client_ptr->state_ != state::stopped) {
 | 
					            } else if(client_ptr->state_ != state::stopped) {
 | 
				
			||||||
                if(client_ptr->_last_received_packet + chrono::seconds{1} < chrono::system_clock::now()) {
 | 
					                if(client_ptr->_last_received_packet + chrono::seconds{1} < chrono::system_clock::now()) {
 | 
				
			||||||
                    client_ptr->set_state(state::stopped);
 | 
					                    client_ptr->set_state(state::stopped);
 | 
				
			||||||
                    log_warn(category::audio, tr("Client {} has a audio buffer underflow for {} samples and not received any data for one second. Stopping replay."), client_ptr->client_id_, sample_count);
 | 
					                    log_warn(category::audio, tr("Client {} has a audio buffer underflow for {} samples and not received any data for one second. Stopping replay."), client_ptr->client_id_, sample_count);
 | 
				
			||||||
@ -406,21 +408,25 @@ void VoiceClient::process_packet(uint16_t packet_id, const pipes::buffer_view& b
 | 
				
			|||||||
void VoiceClient::cancel_replay() {
 | 
					void VoiceClient::cancel_replay() {
 | 
				
			||||||
	log_trace(category::voice_connection, tr("Cancel replay for client {}"), this->client_id_);
 | 
						log_trace(category::voice_connection, tr("Cancel replay for client {}"), this->client_id_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(output_source) this->output_source->clear();
 | 
						auto output = this->output_source;
 | 
				
			||||||
 | 
						if(output) {
 | 
				
			||||||
 | 
					        output->clear();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this->set_state(state::stopped);
 | 
						this->set_state(state::stopped);
 | 
				
			||||||
	audio::decode_event_loop->cancel(static_pointer_cast<event::EventEntry>(this->ref()));
 | 
						audio::decode_event_loop->cancel(static_pointer_cast<event::EventEntry>(this->ref()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto execute_lock = this->execute_lock(true);
 | 
						auto execute_lock = this->execute_lock(true);
 | 
				
			||||||
	for(auto& codec : this->codec) {
 | 
						for(auto& codec_entry : this->codec) {
 | 
				
			||||||
		auto head = codec.pending_buffers;
 | 
							auto head = codec_entry.pending_buffers;
 | 
				
			||||||
		while(head) {
 | 
							while(head) {
 | 
				
			||||||
			auto tmp = head->next;
 | 
								auto tmp = head->next;
 | 
				
			||||||
			delete head;
 | 
								delete head;
 | 
				
			||||||
			head = tmp;
 | 
								head = tmp;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		codec.pending_buffers = nullptr;
 | 
					        codec_entry.pending_buffers = nullptr;
 | 
				
			||||||
		codec.force_replay = nullptr;
 | 
					        codec_entry.force_replay = nullptr;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -437,8 +443,9 @@ void VoiceClient::event_execute(const std::chrono::system_clock::time_point &sch
 | 
				
			|||||||
	auto timeout = chrono::system_clock::now() + max_time;
 | 
						auto timeout = chrono::system_clock::now() + max_time;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for(auto& audio_codec : this->codec) {
 | 
						for(auto& audio_codec : this->codec) {
 | 
				
			||||||
		if(!audio_codec.process_pending)
 | 
							if(!audio_codec.process_pending) {
 | 
				
			||||||
			continue;
 | 
					            continue;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		unique_lock lock{audio_codec.pending_lock};
 | 
							unique_lock lock{audio_codec.pending_lock};
 | 
				
			||||||
		do {
 | 
							do {
 | 
				
			||||||
@ -568,8 +575,9 @@ void VoiceClient::event_execute(const std::chrono::system_clock::time_point &sch
 | 
				
			|||||||
							log_warn(category::audio, tr("Failed to decode lost packets for client {}: {}"), this->_client_id, error);
 | 
												log_warn(category::audio, tr("Failed to decode lost packets for client {}: {}"), this->_client_id, error);
 | 
				
			||||||
						*/
 | 
											*/
 | 
				
			||||||
						auto decoded = this->decode_buffer(audio_codec.codec, replay_head->buffer, true);
 | 
											auto decoded = this->decode_buffer(audio_codec.codec, replay_head->buffer, true);
 | 
				
			||||||
						if(decoded)
 | 
											if(decoded) {
 | 
				
			||||||
							this->output_source->enqueue_samples(decoded->sample_data, decoded->sample_size);
 | 
					                            this->output_source->enqueue_samples(decoded->sample_data, decoded->sample_size);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					const auto is_new_audio_stream = this->state_ != state::buffering && this->state_ != state::playing;
 | 
										const auto is_new_audio_stream = this->state_ != state::buffering && this->state_ != state::playing;
 | 
				
			||||||
@ -716,7 +724,8 @@ std::shared_ptr<audio::SampleBuffer> VoiceClient::decode_buffer(const codec::val
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void VoiceClient::event_execute_dropped(const std::chrono::system_clock::time_point &point) {
 | 
					void VoiceClient::event_execute_dropped(const std::chrono::system_clock::time_point &point) {
 | 
				
			||||||
	if(audio_decode_event_dropped.exchange(true))
 | 
						if(audio_decode_event_dropped.exchange(true)) {
 | 
				
			||||||
		//Is not really a warning, it happens all the time and isn't really an issue
 | 
					        //Is not really a warning, it happens all the time and isn't really an issue
 | 
				
			||||||
		;//log_warn(category::voice_connection, tr("Dropped auto enqueue event execution two or more times in a row for client {}"), this->_client_id);
 | 
					        //log_warn(category::voice_connection, tr("Dropped auto enqueue event execution two or more times in a row for client {}"), this->_client_id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -69,7 +69,7 @@ namespace tc::connection {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            void initialize();
 | 
					            void initialize();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            inline uint16_t client_id() { return this->client_id_; }
 | 
					            inline uint16_t client_id() const { return this->client_id_; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            void initialize_js_object();
 | 
					            void initialize_js_object();
 | 
				
			||||||
            void finalize_js_object();
 | 
					            void finalize_js_object();
 | 
				
			||||||
@ -153,11 +153,14 @@ namespace tc::connection {
 | 
				
			|||||||
            std::chrono::system_clock::time_point _last_received_packet;
 | 
					            std::chrono::system_clock::time_point _last_received_packet;
 | 
				
			||||||
            state::value state_ = state::stopped;
 | 
					            state::value state_ = state::stopped;
 | 
				
			||||||
            inline void set_state(state::value value) {
 | 
					            inline void set_state(state::value value) {
 | 
				
			||||||
                if(value == this->state_)
 | 
					                if(value == this->state_) {
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                this->state_ = value;
 | 
					                this->state_ = value;
 | 
				
			||||||
                if(this->on_state_changed)
 | 
					                if(this->on_state_changed) {
 | 
				
			||||||
                    this->on_state_changed();
 | 
					                    this->on_state_changed();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            std::atomic_bool audio_decode_event_dropped{false};
 | 
					            std::atomic_bool audio_decode_event_dropped{false};
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user