1 //  nova server
2 //  Copyright (C) 2008, 2009, 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.
13 //
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.
18 
19 #pragma once
20 
21 #include <atomic>
22 
23 #include "buffer_manager.hpp"
24 #include "memory_pool.hpp"
25 #include "node_graph.hpp"
26 #include "../sc/sc_osc_handler.hpp"
27 #include "server_args.hpp"
28 #include "server_scheduler.hpp"
29 #include "synth_factory.hpp"
30 #include "../sc/sc_ugen_factory.hpp"
31 #include "../utilities/callback_interpreter.hpp"
32 #include "../utilities/static_pooled_class.hpp"
33 #include "../utilities/asynchronous_log.hpp"
34 #include "../../common/server_shm.hpp"
35 
36 #ifdef PORTAUDIO_BACKEND
37 #    include "audio_backend/portaudio_backend.hpp"
38 #elif defined(JACK_BACKEND)
39 #    include "audio_backend/jack_backend.hpp"
40 #endif
41 
42 #include "../../scsynth/SC_TimeDLL.hpp"
43 #include "../../scsynth/SC_Time.hpp"
44 namespace nova {
45 
46 struct realtime_engine_functor {
47     static inline void sync_clock(void);
48     static void init_thread(void);
49     static inline void run_tick(void);
50     static void log_(const char*);
51     static void log_printf_(const char*, ...);
52 };
53 
54 extern class nova_server* instance;
55 
56 /** callback class for audio-thread to system-thread communcation
57  *
58  *  the system_callback uses an internal memory pool for real-time safe
59  *  memory management. objects are only allowed to be allocated from
60  *  the real-time thread.
61  * */
62 
63 class system_callback : public static_pooled_class<system_callback, 1 << 20, /* 1mb pool of realtime memory */
64                                                    false, 5> {
65 public:
66     virtual ~system_callback(void) = default;
67 
68     virtual void run(void) = 0;
69 };
70 
71 /** system_callback to delete object in the system thread. useful to avoid hitting the memory allocator
72  *  from within the real-time threads
73  */
74 template <typename T> class delete_callback : public system_callback {
75 public:
delete_callback(T * ptr)76     delete_callback(T* ptr): ptr_(ptr) {}
77 
78 private:
run(void)79     virtual void run(void) override { delete ptr_; }
80 
81     const T* const ptr_;
82 };
83 
84 
85 /** dsp thread init functor
86  *
87  *  for the real-time use, it should acquire real-time scheduling and pin the thread to a certain CPU
88  *
89  * */
90 struct thread_init_functor {
thread_init_functornova::thread_init_functor91     thread_init_functor(bool real_time): rt(real_time) {}
92 
93     void operator()(int thread_index);
94 
95 private:
96     const bool rt;
97 };
98 
99 struct io_thread_init_functor {
100     void operator()() const;
101 };
102 
103 namespace detail {
104 #if defined(PORTAUDIO_BACKEND)
105 typedef portaudio_backend<realtime_engine_functor, float, false> audio_backend;
106 #elif defined(JACK_BACKEND)
107 typedef jack_backend<realtime_engine_functor, float, false> audio_backend;
108 #else
109 #    error "no audio backend selected"
110 #endif
111 
112 } // detail
113 
114 class nova_server : public asynchronous_log_thread,
115                     public node_graph,
116                     public server_shared_memory_creator,
117                     public scheduler<thread_init_functor>,
118                     public detail::audio_backend,
119                     public synth_factory,
120                     public buffer_manager,
121                     public sc_osc_handler {
122 public:
123     SC_TimeDLL mDLL;
124     bool use_system_clock;
125     double smooth_samplerate;
126 
127     typedef detail::audio_backend audio_backend;
128 
129     /* main nova_server function */
130     nova_server(server_arguments const& args);
131 
132     ~nova_server(void);
133 
134     void prepare_backend(void);
135 
136     /* @{ */
137     /** io interpreter */
add_io_callback(system_callback * cb)138     void add_io_callback(system_callback* cb) { io_interpreter.add_callback(cb); }
139     /* @} */
140 
141     /* @{ */
142     /** system interpreter
143      * \note: should only be called from the main audio thread
144      */
add_system_callback(system_callback * cb)145     void add_system_callback(system_callback* cb) { system_interpreter.add_callback(cb); }
146 
run(void)147     void run(void) {
148         start_dsp_threads();
149         system_interpreter.run();
150     }
151 
prepare_to_terminate()152     void prepare_to_terminate() { server_shared_memory_creator::disconnect(); }
153 
terminate(void)154     void terminate(void) {
155         system_interpreter.terminate();
156         quit_requested_ = true;
157     }
158     /* @} */
159 
160     /** non-rt synthesis */
161     void run_nonrt_synthesis(server_arguments const& arguments);
162 
163 public:
164     /* @{ */
165     /** node control */
166     abstract_synth* add_synth(const char* name, int id, node_position_constraint const& constraints);
167     group* add_group(int id, node_position_constraint const& constraints);
168     parallel_group* add_parallel_group(int id, node_position_constraint const& constraints);
169 
170     void free_node(server_node* node);
171     void group_free_all(abstract_group* group);
172     void group_free_deep(abstract_group* group);
173 
node_pause(server_node * node)174     void node_pause(server_node* node) {
175         node->pause();
176         notification_node_turned_off(node);
177     }
178 
node_resume(server_node * node)179     void node_resume(server_node* node) {
180         node->resume();
181         notification_node_turned_on(node);
182     }
183 
184     void set_node_slot(int node_id, slot_index_t slot, float value);
185     void set_node_slot(int node_id, const char* slot, float value);
186     /* @} */
187 
cpu_load(float & peak,float & average) const188     void cpu_load(float& peak, float& average) const { audio_backend::get_cpuload(peak, average); }
189 
increment_logical_time(void)190     void increment_logical_time(void) { sc_osc_handler::increment_logical_time(time_per_tick); }
191 
set_last_now(time_tag const & lasts,time_tag const & nows)192     void set_last_now(time_tag const& lasts, time_tag const& nows) { sc_osc_handler::set_last_now(lasts, nows); }
193 
compensate_latency(void)194     void compensate_latency(void) {
195         sc_osc_handler::add_last_now(
196             time_tag::from_samples(audio_backend::get_latency(), audio_backend::get_samplerate()));
197     }
198 
199 public:
tick()200     HOT void tick() {
201         sc_factory->apply_done_actions();
202         run_callbacks();
203         execute_scheduled_bundles();
204 
205         if (unlikely(dsp_queue_dirty))
206             rebuild_dsp_queue();
207 
208         compute_audio();
209     }
210 
211     void rebuild_dsp_queue(void);
212 
request_dsp_queue_update(void)213     void request_dsp_queue_update(void) { dsp_queue_dirty = true; }
214 
quit_requested()215     bool quit_requested() { return quit_requested_.load(); }
216 
217 private:
218     void perform_node_add(server_node* node, node_position_constraint const& constraints, bool update_dsp_queue);
219     void finalize_node(server_node& node);
220     std::atomic<bool> quit_requested_ = { false };
221     bool dsp_queue_dirty = false;
222 
223     callback_interpreter<system_callback, false> system_interpreter; // rt to system thread
224     threaded_callback_interpreter<system_callback, io_thread_init_functor> io_interpreter; // for network IO
225 };
226 
run_scheduler_tick(void)227 inline void run_scheduler_tick(void) {
228     const int blocksize = sc_factory->world.mBufLength;
229     const int input_channels = sc_factory->world.mNumInputs;
230     const int output_channels = sc_factory->world.mNumOutputs;
231     const int buf_counter = ++sc_factory->world.mBufCounter;
232 
233     /* touch all input buffers */
234     for (int channel = 0; channel != input_channels; ++channel)
235         sc_factory->world.mAudioBusTouched[output_channels + channel] = buf_counter;
236 
237     instance->tick();
238 
239     /* wipe all untouched output buffers */
240     for (int channel = 0; channel != output_channels; ++channel) {
241         if (sc_factory->world.mAudioBusTouched[channel] != buf_counter)
242             zerovec(sc_factory->world.mAudioBus + blocksize * channel, blocksize);
243     }
244 }
245 
log_printf(const char * fmt,...)246 inline bool log_printf(const char* fmt, ...) {
247     va_list vargs;
248     va_start(vargs, fmt);
249     return instance->log_printf(fmt, vargs);
250 }
251 
252 
sync_clock(void)253 inline void realtime_engine_functor::sync_clock(void) {
254     if (instance->use_system_clock) {
255         double nows = (uint64)(OSCTime(std::chrono::system_clock::now())) * kOSCtoSecs;
256         instance->mDLL.Reset(sc_factory->world.mSampleRate, sc_factory->world.mBufLength, SC_TIME_DLL_BW, nows);
257         time_tag oscTime = time_tag((uint64)((instance->mDLL.PeriodTime()) * kSecondsToOSCunits + .5));
258         time_tag oscInc = time_tag((uint64)((instance->mDLL.Period()) * kSecondsToOSCunits + .5));
259         instance->smooth_samplerate = instance->mDLL.SampleRate();
260         instance->set_last_now(oscTime, oscTime + oscInc);
261     } else
262         instance->update_time_from_system();
263 
264     instance->compensate_latency();
265 }
266 
267 
run_tick(void)268 inline void realtime_engine_functor::run_tick(void) {
269     /* // debug timedll
270     static int count = 0;
271     if(count >= 44100/64){
272         count = 0;
273                 log_printf("DLL: t %.6f p %.9f sr %.6f e %.9f avg(e) %.9f \n",
274                  instance->mDLL.PeriodTime(), instance->mDLL.Period(), instance->mDLL.SampleRate(),
275                  instance->mDLL.Error(), instance->mDLL.AvgError());
276     }
277     count++;
278     */
279     run_scheduler_tick();
280 
281     if (instance->use_system_clock) {
282         // time_tag nows =
283         // time_tag::from_ptime(boost::date_time::microsec_clock<boost::posix_time::ptime>::universal_time());
284         double nows = (uint64)(OSCTime(std::chrono::system_clock::now())) * kOSCtoSecs;
285         instance->mDLL.Update(nows);
286         time_tag oscTime = time_tag((uint64)((instance->mDLL.PeriodTime()) * kSecondsToOSCunits + .5));
287         time_tag oscInc = time_tag((uint64)((instance->mDLL.Period()) * kSecondsToOSCunits + .5));
288         instance->smooth_samplerate = instance->mDLL.SampleRate();
289         instance->set_last_now(oscTime, oscTime + oscInc);
290     } else
291         instance->increment_logical_time();
292 }
293 
294 
log(const char * string)295 inline bool log(const char* string) { return instance->log(string); }
296 
log(const char * string,size_t length)297 inline bool log(const char* string, size_t length) { return instance->log(string, length); }
298 
299 } /* namespace nova */
300