Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / server / supernova / audio_backend / sndfile_backend.hpp
blob19b67e83dfe2dca48ddde7c0be9c806997161b23
1 // file-based backend (via libsndfile)
2 // Copyright (C) 2010 Tim Blechmann
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with this program; see the file COPYING. If not, write to
16 // the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 // Boston, MA 02111-1307, USA.
19 #ifndef AUDIO_BACKEND_SNDFILE_BACKEND_HPP
20 #define AUDIO_BACKEND_SNDFILE_BACKEND_HPP
22 #include <cmath>
23 #include <string>
24 #include <thread>
26 #include <boost/atomic.hpp>
27 #include <boost/lockfree/spsc_queue.hpp>
29 #include <sndfile.hh>
31 #include "nova-tt/semaphore.hpp"
32 #include "utilities/branch_hints.hpp"
34 #include "audio_backend_common.hpp"
36 namespace nova {
38 /** sndfile backend
40 * audio backend, reading/writing sound files via libsndfile
43 template <typename engine_functor,
44 typename sample_type = float,
45 bool blocking = false
47 class sndfile_backend:
48 public detail::audio_delivery_helper<sample_type, float, blocking, false>,
49 public detail::audio_settings_basic,
50 private engine_functor
52 typedef detail::audio_delivery_helper<sample_type, float, blocking, false> super;
53 typedef std::size_t size_t;
55 public:
56 sndfile_backend(void):
57 read_frames(65536), write_frames(65536), running(false), reader_running(false), writer_running(false)
60 size_t get_audio_blocksize(void)
62 return 64;
65 public:
66 void open_client(std::string const & input_file_name, std::string const & output_file_name,
67 float samplerate, int format, uint32_t output_channel_count)
69 output_channels = output_channel_count;
70 samplerate_ = samplerate = std::floor(samplerate);
72 if (!input_file_name.empty()) {
73 input_file = SndfileHandle(input_file_name.c_str(), SFM_READ);
74 if (!input_file)
75 throw std::runtime_error("cannot open input file");
77 if (input_file.samplerate() != samplerate)
78 throw std::runtime_error("input file: samplerate mismatch");
80 input_channels = input_file.channels();
81 super::input_samples.resize(input_channels);
83 else
84 input_channels = 0;
85 read_position = 0;
87 output_file = SndfileHandle(output_file_name.c_str(), SFM_WRITE, format, output_channel_count, samplerate);
88 if (!output_file)
89 throw std::runtime_error("cannot open output file");
91 output_file.command(SFC_SET_CLIPPING, NULL, SF_TRUE);
93 super::output_samples.resize(output_channel_count);
95 temp_buffer.reset(calloc_aligned<float>(std::max(input_channels, output_channels) * 64));
99 void close_client(void)
101 output_file.writeSync();
102 input_file = output_file = SndfileHandle();
105 bool audio_is_opened(void)
107 return output_file;
110 bool audio_is_active(void)
112 return running.load(boost::memory_order_acquire);
115 void activate_audio(void)
117 running.store(true);
119 if (input_file)
121 reader_running.store(true);
122 reader_thread = std::thread(std::bind(&sndfile_backend::sndfile_read_thread, this));
125 writer_running.store(true);
126 writer_thread = std::thread(std::bind(&sndfile_backend::sndfile_write_thread, this));
129 void deactivate_audio(void)
131 running.store(false);
133 if (input_file)
135 reader_running.store(false);
136 reader_thread.join();
139 writer_running.store(false);
140 write_semaphore.post();
141 writer_thread.join();
144 private:
145 /* read input fifo from the rt context */
146 void read_input_buffers(size_t frames_per_tick)
148 if (reader_running.load(boost::memory_order_acquire))
150 const size_t total_samples = input_channels * frames_per_tick;
151 size_t remaining = total_samples;
153 read_semaphore.wait();
154 do {
155 remaining -= read_frames.pop(temp_buffer.get(), remaining);
157 if (unlikely(read_frames.empty() &&
158 !reader_running.load(boost::memory_order_acquire)))
160 /* at the end, we are not able to read a full sample block, clear the final parts */
161 const size_t last_frame = (total_samples - remaining) / input_channels;
162 const size_t remaining_per_channel = remaining / input_channels;
163 assert(remaining % input_channels == 0);
164 assert(remaining_per_channel % input_channels == 0);
166 for (uint16_t channel = 0; channel != input_channels; ++channel)
167 zerovec(super::input_samples[channel].get() + last_frame, remaining_per_channel);
169 break;
171 } while (remaining);
173 const size_t frames = (total_samples - remaining) / input_channels;
174 for (size_t frame = 0; frame != frames; ++frame)
176 for (uint16_t channel = 0; channel != input_channels; ++channel)
177 super::input_samples[channel].get()[frame] = temp_buffer.get()[frame * input_channels + channel];
180 else
181 super::clear_inputs(frames_per_tick);
184 void sndfile_read_thread(void)
186 assert(input_file);
188 const size_t frames_per_tick = get_audio_blocksize();
189 sized_array<sample_type, aligned_allocator<sample_type> > data_to_read(input_channels * frames_per_tick, 0.f);
191 for (;;) {
192 if (unlikely(reader_running.load(boost::memory_order_acquire) == false))
193 return;
195 if (read_position < (size_t)input_file.frames())
197 size_t frames = input_file.frames() - read_position;
198 if (frames > frames_per_tick)
199 frames = frames_per_tick;
201 input_file.readf(data_to_read.c_array(), frames);
202 read_position += frames;
204 const size_t item_to_enqueue = input_channels * frames;
205 size_t remaining = item_to_enqueue;
207 do {
208 remaining -= read_frames.push(data_to_read.c_array(), remaining);
209 } while(remaining);
210 read_semaphore.post();
212 else
213 reader_running.store(false, boost::memory_order_release);
217 /* write output fifo from rt context */
218 void write_output_buffers(size_t frames_per_tick)
220 for (size_t frame = 0; frame != frames_per_tick; ++frame) {
221 for (uint16_t channel = 0; channel != output_channels; ++channel)
222 temp_buffer.get()[frame * output_channels + channel] = super::output_samples[channel].get()[frame];
225 const size_t total_samples = output_channels * frames_per_tick;
226 sample_type * buffer = temp_buffer.get();
228 size_t count = total_samples;
229 do {
230 size_t consumed = write_frames.push(buffer, count);
231 count -= consumed;
232 buffer += consumed;
233 write_semaphore.post();
234 } while (count);
237 void sndfile_write_thread(void)
239 const size_t frames_per_tick = get_audio_blocksize();
240 sized_array<sample_type, aligned_allocator<sample_type> > data_to_write(output_channels * frames_per_tick, 0.f);
242 for (;;) {
243 write_semaphore.wait();
244 for (;;) {
245 size_t dequeued = write_frames.pop(data_to_write.c_array(), data_to_write.size());
247 if (dequeued == 0)
248 break;
249 output_file.write(data_to_write.c_array(), dequeued);
251 if (unlikely(writer_running.load(boost::memory_order_acquire) == false))
252 return;
257 public:
258 void audio_fn_noinput(size_t frames_per_tick)
260 engine_functor::run_tick();
261 write_output_buffers(frames_per_tick);
264 void audio_fn(size_t frames_per_tick)
266 super::clear_outputs(frames_per_tick);
267 read_input_buffers(frames_per_tick);
268 engine_functor::run_tick();
269 write_output_buffers(frames_per_tick);
272 private:
273 SndfileHandle input_file, output_file;
274 std::size_t read_position;
276 aligned_storage_ptr<sample_type> temp_buffer;
278 std::thread reader_thread, writer_thread;
279 boost::lockfree::spsc_queue< sample_type > read_frames, write_frames;
280 nova::semaphore read_semaphore, write_semaphore;
281 boost::atomic<bool> running, reader_running, writer_running;
284 } /* namespace nova */
286 #endif /* AUDIO_BACKEND_SNDFILE_BACKEND_HPP */