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