1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /***************************************************************************
3 * audiocacheeventhandler.cc
4 *
5 * Sun Jan 3 19:57:55 CET 2016
6 * Copyright 2016 Bent Bisballe Nyeng
7 * deva@aasimon.org
8 ****************************************************************************/
9
10 /*
11 * This file is part of DrumGizmo.
12 *
13 * DrumGizmo is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU Lesser General Public License as published by
15 * the Free Software Foundation; either version 3 of the License, or
16 * (at your option) any later version.
17 *
18 * DrumGizmo is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License for more details.
22 *
23 * You should have received a copy of the GNU Lesser General Public License
24 * along with DrumGizmo; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
26 */
27 #include "audiocacheeventhandler.h"
28
29 #include <assert.h>
30
31 #include <hugin.hpp>
32
33 #include "audiocachefile.h"
34 #include "audiocache.h"
35 #include "audiocacheidmanager.h"
36
37 enum class EventType {
38 LoadNext,
39 Close,
40 };
41
42 class CacheEvent {
43 public:
44 EventType event_type;
45
46 // For close event:
47 cacheid_t id;
48
49 // For load next event:
50 size_t pos;
51 AudioCacheFile* afile;
52 CacheChannels channels;
53 };
54
AudioCacheEventHandler(AudioCacheIDManager & id_manager)55 AudioCacheEventHandler::AudioCacheEventHandler(AudioCacheIDManager& id_manager)
56 : id_manager(id_manager)
57 {
58 }
59
~AudioCacheEventHandler()60 AudioCacheEventHandler::~AudioCacheEventHandler()
61 {
62 // Close all ids already enqueued to be closed.
63 clearEvents();
64
65 auto active_ids = id_manager.getActiveIDs();
66 for(auto id : active_ids)
67 {
68 handleCloseCache(id);
69 }
70 }
71
start()72 void AudioCacheEventHandler::start()
73 {
74 if(running)
75 {
76 return;
77 }
78
79 running = true;
80 run();
81 sem_run.wait();
82 }
83
stop()84 void AudioCacheEventHandler::stop()
85 {
86 if(!running)
87 {
88 return;
89 }
90
91 running = false;
92
93 sem.post();
94 wait_stop();
95 }
96
setThreaded(bool threaded)97 void AudioCacheEventHandler::setThreaded(bool threaded)
98 {
99 this->threaded.store(threaded);
100 }
101
isThreaded() const102 bool AudioCacheEventHandler::isThreaded() const
103 {
104 return threaded.load();
105 }
106
lock()107 void AudioCacheEventHandler::lock()
108 {
109 mutex.lock();
110 }
111
unlock()112 void AudioCacheEventHandler::unlock()
113 {
114 mutex.unlock();
115 }
116
pushLoadNextEvent(AudioCacheFile * afile,size_t channel,size_t pos,sample_t * buffer,volatile bool * ready)117 void AudioCacheEventHandler::pushLoadNextEvent(AudioCacheFile* afile,
118 size_t channel,
119 size_t pos, sample_t* buffer,
120 volatile bool* ready)
121 {
122 CacheEvent cache_event;
123 cache_event.event_type = EventType::LoadNext;
124 cache_event.pos = pos;
125 cache_event.afile = afile;
126
127 CacheChannel c;
128 c.channel = channel;
129 c.samples = buffer;
130
131 *ready = false;
132 c.ready = ready;
133
134 cache_event.channels.insert(cache_event.channels.end(), c);
135
136 pushEvent(cache_event);
137 }
138
pushCloseEvent(cacheid_t id)139 void AudioCacheEventHandler::pushCloseEvent(cacheid_t id)
140 {
141 CacheEvent cache_event;
142 cache_event.event_type = EventType::Close;
143 cache_event.id = id;
144
145 pushEvent(cache_event);
146 }
147
setChunkSize(size_t chunksize)148 void AudioCacheEventHandler::setChunkSize(size_t chunksize)
149 {
150 DEBUG(cache, "%s\n", __PRETTY_FUNCTION__);
151
152 // We should already locked when this method is called.
153 //assert(!mutex.try_lock());
154
155 if(this->chunksize == chunksize)
156 {
157 return;
158 }
159
160 DEBUG(cache, "setChunkSize 1\n");
161
162 // Remove all events from event queue.
163 clearEvents();
164
165 DEBUG(cache, "setChunkSize 2\n");
166
167 // Skip all active cacheids and make their buffers point at nodata.
168 id_manager.disableActive();
169
170 DEBUG(cache, "setChunkSize 3\n");
171
172 this->chunksize = chunksize;
173 }
174
getChunkSize() const175 size_t AudioCacheEventHandler::getChunkSize() const
176 {
177 return chunksize;
178 }
179
openFile(const std::string & filename)180 AudioCacheFile& AudioCacheEventHandler::openFile(const std::string& filename)
181 {
182 std::lock_guard<std::mutex> lock(mutex);
183 return files.getFile(filename);
184 }
185
clearEvents()186 void AudioCacheEventHandler::clearEvents()
187 {
188 // Iterate all events ignoring load events and handling close events.
189 for(auto& event : eventqueue)
190 {
191 if(event.event_type == EventType::Close)
192 {
193 handleCloseCache(event.id); // This method does not lock.
194 }
195 }
196
197 eventqueue.clear();
198 }
199
handleLoadNextEvent(CacheEvent & cache_event)200 void AudioCacheEventHandler::handleLoadNextEvent(CacheEvent& cache_event)
201 {
202 assert(cache_event.afile); // Assert that we have an audio file
203
204 cache_event.afile->readChunk(cache_event.channels, cache_event.pos,
205 chunksize);
206 }
207
handleCloseEvent(CacheEvent & cache_event)208 void AudioCacheEventHandler::handleCloseEvent(CacheEvent& cache_event)
209 {
210 std::lock_guard<std::mutex> lock(mutex);
211 handleCloseCache(cache_event.id);
212 }
213
handleCloseCache(cacheid_t id)214 void AudioCacheEventHandler::handleCloseCache(cacheid_t id)
215 {
216 auto& cache = id_manager.getCache(id);
217
218 // Only close the file if we have also opened it.
219 if(cache.afile)
220 {
221 files.releaseFile(cache.afile->getFilename());
222 }
223
224 delete[] cache.front;
225 delete[] cache.back;
226
227 id_manager.releaseID(id);
228 }
229
handleEvent(CacheEvent & cache_event)230 void AudioCacheEventHandler::handleEvent(CacheEvent& cache_event)
231 {
232 switch(cache_event.event_type)
233 {
234 case EventType::LoadNext:
235 handleLoadNextEvent(cache_event);
236 break;
237 case EventType::Close:
238 handleCloseEvent(cache_event);
239 break;
240 }
241 }
242
thread_main()243 void AudioCacheEventHandler::thread_main()
244 {
245 sem_run.post(); // Signal that the thread has been started
246
247 while(running)
248 {
249 sem.wait();
250
251 mutex.lock();
252 if(eventqueue.empty())
253 {
254 mutex.unlock();
255 continue;
256 }
257
258 CacheEvent cache_event = eventqueue.front();
259 eventqueue.pop_front();
260 mutex.unlock();
261
262 handleEvent(cache_event);
263 }
264 }
265
pushEvent(CacheEvent & cache_event)266 void AudioCacheEventHandler::pushEvent(CacheEvent& cache_event)
267 {
268 if(!threaded.load())
269 {
270 handleEvent(cache_event);
271 return;
272 }
273
274 {
275 std::lock_guard<std::mutex> lock(mutex);
276
277 bool found = false;
278
279 if(cache_event.event_type == EventType::LoadNext)
280 {
281 for(auto& queued_event : eventqueue)
282 {
283 if(queued_event.event_type == EventType::LoadNext)
284 {
285
286 assert(cache_event.afile); // Assert that we have an audio file
287 assert(queued_event.afile); // Assert that we have an audio file
288
289 if((cache_event.afile->getFilename() ==
290 queued_event.afile->getFilename()) &&
291 (cache_event.pos == queued_event.pos))
292 {
293 // Append channel and buffer to the existing event.
294 queued_event.channels.insert(queued_event.channels.end(),
295 cache_event.channels.begin(),
296 cache_event.channels.end());
297 found = true;
298 break;
299 }
300 }
301 }
302 }
303
304 if(!found)
305 {
306 // The event was not already on the list, create a new one.
307 eventqueue.push_back(cache_event);
308 }
309 }
310
311 sem.post();
312 }
313