/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /*************************************************************************** * drumgizmo.cc * * Thu Sep 16 10:24:40 CEST 2010 * Copyright 2010 Bent Bisballe Nyeng * deva@aasimon.org ****************************************************************************/ /* * This file is part of DrumGizmo. * * DrumGizmo is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * DrumGizmo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with DrumGizmo; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include "drumgizmo.h" #include #include #include #include #include #include #include #include #include #include "audioinputenginemidi.h" DrumGizmo::DrumGizmo(Settings& settings, AudioOutputEngine& o, AudioInputEngine& i) : loader(settings, kit, i, rand, audio_cache) , oe(o) , ie(i) , audio_cache(settings) , input_processor(settings, kit, events_ds, rand) , settings(settings) , settings_getter(settings) { audio_cache.init(10000); // start thread events.reserve(1000); loader.init(); setSamplerate(44100.0f, settings.resampling_quality.load()); settings_getter.audition_counter.hasChanged(); // Reset audition_counter config.load(); if(config.defaultMidimap != "" && settings.midimap_file.load() == "") { settings.midimap_file.store(config.defaultMidimap); } if(config.defaultKit != "" && settings.drumkit_file.load() == "") { settings.drumkit_file.store(config.defaultKit); } } DrumGizmo::~DrumGizmo() { loader.deinit(); audio_cache.deinit(); // stop thread } bool DrumGizmo::init() { if(!ie.init(kit.instruments)) { return false; } if(!oe.init(kit.channels)) { return false; } return true; } void DrumGizmo::setFrameSize(size_t framesize) { settings.buffer_size.store(framesize); if(this->framesize != framesize) { DEBUG(drumgizmo, "New framesize: %d\n", (int)framesize); this->framesize = framesize; // Update framesize in drumkitloader and cachemanager: loader.setFrameSize(framesize); audio_cache.setFrameSize(framesize); } } void DrumGizmo::setFreeWheel(bool freewheel) { // Freewheel = true means that we are bouncing and therefore running faster // than realtime. this->freewheel = freewheel; audio_cache.setAsyncMode(!freewheel); } void DrumGizmo::setRandomSeed(unsigned int seed) { rand.setSeed(seed); } bool DrumGizmo::run(size_t pos, sample_t *samples, size_t nsamples) { if(settings_getter.enable_resampling.hasChanged()) { enable_resampling = settings_getter.enable_resampling.getValue(); } { auto sample_rate_changed = settings_getter.drumkit_samplerate.hasChanged(); auto resampling_quality_changed = settings_getter.resampling_quality.hasChanged(); if(sample_rate_changed || resampling_quality_changed) { settings_getter.drumkit_samplerate.getValue(); // stage new value setSamplerate(settings.samplerate.load(), settings_getter.resampling_quality.getValue()); } } setFrameSize(nsamples); setFreeWheel(ie.isFreewheeling() && oe.isFreewheeling()); ie.pre(); oe.pre(nsamples); // Clears all output buffers std::memset(samples, 0, nsamples * sizeof(sample_t)); // // Read new events // ie.run(pos, nsamples, events); double resample_ratio = ratio; if(enable_resampling == false) { resample_ratio = 1.0; } if(settings_getter.audition_counter.hasChanged()) { settings_getter.audition_counter.getValue(); auto instrument_name = settings.audition_instrument.load(); auto velocity = settings.audition_velocity.load(); std::size_t instrument_index = 0; for (std::size_t i = 0; i < kit.instruments.size(); ++i) { if (instrument_name == kit.instruments[i]->getName()) { instrument_index = i; } } events.push_back({EventType::OnSet, instrument_index, 0, velocity}); } bool active_events_left = input_processor.process(events, pos, resample_ratio); if(!active_events_left) { return false; } events.clear(); // // Write audio // if(!enable_resampling || ratio == 1.0) { // No resampling needed for(size_t c = 0; c < kit.channels.size(); ++c) { sample_t *buf = samples; bool internal = false; if(oe.getBuffer(c)) { buf = oe.getBuffer(c); internal = true; } if(buf) { std::memset(buf, 0, nsamples * sizeof(sample_t)); getSamples(c, pos, buf, nsamples); if(!internal) { oe.run(c, samples, nsamples); } } } } else { // Resampling needed size_t kitpos = pos * ratio; for(size_t c = 0; c < kit.channels.size(); ++c) { sample_t *buf = samples; bool internal = false; if(oe.getBuffer(c)) { buf = oe.getBuffer(c); internal = true; } zita[c].set_out_data(buf); zita[c].set_out_count(nsamples); if(zita[c].get_inp_count() > 0) { // Samples left from last iteration, process that one first zita[c].process(); } std::memset(resampler_input_buffer[c].get(), 0, MAX_RESAMPLER_BUFFER_SIZE); zita[c].set_inp_data(resampler_input_buffer[c].get()); std::size_t sample_count = std::ceil((nsamples - (nsamples - zita[c].get_out_count())) * ratio); getSamples(c, kitpos, zita[c].get_inp_data(), sample_count); zita[c].set_inp_count(sample_count); zita[c].process(); if(!internal) { oe.run(c, samples, nsamples); } } } ie.post(); oe.post(nsamples); pos += nsamples; return true; } void DrumGizmo::renderSampleEvent(SampleEvent& evt, int pos, sample_t *s, std::size_t sz) { size_t n = 0; // default start point is 0. // If we are not at offset 0 in current buffer: if(evt.offset > (size_t)pos) { n = evt.offset - pos; } size_t end = sz; // default end point is the end of the buffer. // Find the end point intra-buffer if((evt.t + end - n) > evt.sample_size) { end = evt.sample_size - evt.t + n; } // This should not be necessary but make absolutely sure that we do // not write over the end of the buffer. if(end > sz) { end = sz; } size_t t = 0; // Internal buffer counter repeat: float scale = 1.0f; for(; (n < end) && (t < (evt.buffer_size - evt.buffer_ptr)); ++n) { assert(n >= 0); assert(n < sz); assert(t >= 0); assert(t < evt.buffer_size - evt.buffer_ptr); if(evt.rampdownInProgress() && evt.rampdown_offset < (evt.t + t) && evt.rampdown_count > 0) { if(evt.ramp_length > 0) { scale = std::min((float)evt.rampdown_count / evt.ramp_length, 1.f); } else { scale = 0.0f; } evt.rampdown_count--; } s[n] += evt.buffer[evt.buffer_ptr + t] * evt.scale * scale; ++t; } // Add internal buffer counter to "global" event counter. evt.t += t;//evt.buffer_size; evt.buffer_ptr += t; if(n != sz && evt.t < evt.sample_size) { evt.buffer_size = sz - n;// Hint new size // More samples needed for current buffer evt.buffer = audio_cache.next(evt.cache_id, evt.buffer_size); evt.buffer_ptr = 0; t = 0; goto repeat; } } void DrumGizmo::getSamples(int ch, int pos, sample_t* s, size_t sz) { // Store local values of settings to ensure they don't change intra-iteration const auto enable_bleed_control = settings.enable_bleed_control.load(); const auto master_bleed = settings.master_bleed.load(); EventIDs to_remove; for(auto& sample_event : events_ds.iterateOver(ch)) { bool removeevent = false; AudioFile& af = *sample_event.file; if(!af.isLoaded() || !af.isValid() || (s == nullptr)) { removeevent = true; break; } if(sample_event.offset > (pos + sz)) { // Don't handle event now. It is scheduled for a future iteration. continue; } if(sample_event.cache_id == CACHE_NOID) { size_t initial_chunksize = (pos + sz) - sample_event.offset; sample_event.buffer = audio_cache.open(af, initial_chunksize, af.filechannel, sample_event.cache_id); if((af.mainState() == main_state_t::is_not_main) && enable_bleed_control) { sample_event.scale *= master_bleed; } sample_event.buffer_size = initial_chunksize; sample_event.sample_size = af.size; } { // TODO: We should make all audiofiles reference counted and get rid of this lock. std::lock_guard guard(af.mutex); renderSampleEvent(sample_event, pos, s, sz); if((sample_event.t >= sample_event.sample_size) || (sample_event.rampdown_count == 0)) { removeevent = true; } if(sample_event.buffer_ptr >= sample_event.buffer_size && !removeevent) { sample_event.buffer_size = sz; sample_event.buffer = audio_cache.next(sample_event.cache_id, sample_event.buffer_size); sample_event.buffer_ptr = 0; } if(removeevent) { audio_cache.close(sample_event.cache_id); } } if(removeevent) { to_remove.push_back(sample_event.id); // don't delete until we are out of the loop. } } for(auto event_id : to_remove) { events_ds.remove(event_id); } } void DrumGizmo::stop() { // engine.stop(); } std::size_t DrumGizmo::getLatency() const { auto latency = input_processor.getLatency(); if(enable_resampling && ratio != 0.0) { // TODO: latency += zita[0].inpsize(); // resampler latency } return latency; } float DrumGizmo::samplerate() { return settings.samplerate.load(); } void DrumGizmo::setSamplerate(float samplerate, float resampling_quality) { DEBUG(dgeditor, "%s samplerate: %f\n", __PRETTY_FUNCTION__, samplerate); settings.samplerate.store(samplerate); // Notify input engine of the samplerate change. ie.setSampleRate(samplerate); auto input_fs = settings.drumkit_samplerate.load(); auto output_fs = samplerate; ratio = input_fs / output_fs; settings.resampling_recommended.store(ratio != 1.0); // TODO: Only reallocate the actual amount of samples needed based on the // ratio and the framesize. for(auto& buf : resampler_input_buffer) { buf.reset(new sample_t[MAX_RESAMPLER_BUFFER_SIZE]); } for(int c = 0; c < NUM_CHANNELS; ++c) { zita[c].reset(); auto nchan = 1u; // mono // 16 ≤ hlen ≤ 96 - default is 72, q: 0.7f resampling_quality = std::max(0.0f, std::min(1.0f, resampling_quality)); std::size_t hlen = 16 + (96 - 16) * resampling_quality; zita[c].setup(input_fs, output_fs, nchan, hlen); // Prefill auto null_size = zita[c].inpsize() - 1;// / 2 - 1; zita[c].set_inp_data(nullptr); zita[c].set_inp_count(null_size); constexpr auto sz = 4096 * 16; sample_t s[sz]; zita[c].set_out_data(s); zita[c].set_out_count(sz); zita[c].process(); } }