1 /*
2 
3  CsoundBinding.java:
4 
5  Copyright (C) 2011 Victor Lazzarini, Steven Yi
6 
7  This file is part of Csound Android Examples.
8 
9  The Csound Android Examples is free software; you can redistribute it
10  and/or modify it under the terms of the GNU Lesser General Public
11  License as published by the Free Software Foundation; either
12  version 2.1 of the License, or (at your option) any later version.
13 
14  Csound is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  GNU Lesser General Public License for more details.
18 
19  You should have received a copy of the GNU Lesser General Public
20  License along with Csound; if not, write to the Free Software
21  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
22  02111-1307 USA
23 
24  */
25 
26 package com.csounds;
27 
28 import android.media.AudioFormat;
29 import android.media.AudioManager;
30 import android.media.AudioRecord;
31 import android.media.AudioTrack;
32 import android.media.MediaRecorder;
33 import android.util.Log;
34 import android.webkit.JavascriptInterface;
35 
36 import com.csounds.bindings.CsoundBinding;
37 
38 import java.io.File;
39 import java.util.ArrayList;
40 
41 import csnd6.AndroidCsound;
42 import csnd6.Csound;
43 import csnd6.CsoundCallbackWrapper;
44 import csnd6.CsoundMYFLTArray;
45 import csnd6.controlChannelType;
46 
47 public class CsoundObj {
48 	/** Used to post Csound runtime messages to the host. */
49 	public interface MessagePoster {
50 		/** Clear the message display and post the message. */
postMessageClear(String message)51 		public void postMessageClear(String message);
52 
53 		/** Append the message to the message display. */
postMessage(String message)54 		public void postMessage(String message);
55 	};
56 	private Csound csound;
57 	private ArrayList<CsoundBinding> bindings;
58 	private ArrayList<CsoundObjListener> listeners;
59 	private ArrayList<String> scoreMessages;
60 	private boolean muted = false;
61 	private boolean stopped = true;
62 	private Thread thread;
63 	private boolean audioInEnabled = false;
64 	private boolean messageLoggingEnabled = false;
65 	private boolean useAudioTrack = false;
66 	int retVal = 0;
67 	private boolean pause = false;
68 	private CsoundCallbackWrapper callbacks;
69 	private Object mLock = new Object();
70 	public MessagePoster messagePoster = null;
71 	private long stime = 0;
72 	private double systime = System.nanoTime()*1.0e-6;
73 	private double startTime = System.nanoTime()*1.0e-6;
74 	private boolean isAsync = true;
75 
CsoundObj()76 	public CsoundObj() {
77 		this(false);
78 	}
79 
CsoundObj(boolean useAudioTrack)80 	public CsoundObj(boolean useAudioTrack){
81 		  this(useAudioTrack,true);
82 	}
83 
CsoundObj(boolean useAudioTrack, boolean isAsync)84 	public CsoundObj(boolean useAudioTrack, boolean isAsync) {
85 		bindings = new ArrayList<CsoundBinding>();
86 		listeners = new ArrayList<CsoundObjListener>();
87 		scoreMessages = new ArrayList<String>();
88 		this.useAudioTrack = useAudioTrack;
89         this.isAsync = isAsync;
90 
91 		if (useAudioTrack) {
92 			// Log.d("CsoundObj", "audio track");
93 			csound = new Csound();
94 		} else {
95 			Log.d("CsoundObj", "creating new AndroidCsound: " + (isAsync ? 0 : 1));
96 			csound = new AndroidCsound(isAsync);
97 		}
98 	}
99 
isAudioInEnabled()100 	public boolean isAudioInEnabled() {
101 		return audioInEnabled;
102 	}
103 
setAudioInEnabled(boolean audioInEnabled)104 	public void setAudioInEnabled(boolean audioInEnabled) {
105 		this.audioInEnabled = audioInEnabled;
106 	}
107 
isMessageLoggingEnabled()108 	public boolean isMessageLoggingEnabled() {
109 		return messageLoggingEnabled;
110 	}
111 
setMessageLoggingEnabled(boolean messageLoggingEnabled)112 	public void setMessageLoggingEnabled(boolean messageLoggingEnabled) {
113 		this.messageLoggingEnabled = messageLoggingEnabled;
114 	}
115 
getCsound()116 	public Csound getCsound() {
117 		return csound;
118 	}
119 
isMuted()120 	public boolean isMuted() {
121 		return muted;
122 	}
123 
setMuted(boolean muted)124 	public void setMuted(boolean muted) {
125 		this.muted = muted;
126 	}
127 
isPaused()128 	public boolean isPaused() {
129 		return pause;
130 	}
131 
isStopped()132 	public boolean isStopped() {
133 		return stopped;
134 	}
135 
addBinding(CsoundBinding binding)136 	public void addBinding(CsoundBinding binding) {
137 		if (!stopped)
138 			binding.setup(this);
139 		synchronized (mLock) {
140 			bindings.add(binding);
141 		}
142 	}
143 
144 	@JavascriptInterface
inputMessage(String mess)145 	public/* synchronized */void inputMessage(String mess) {
146 		if(isAsync){
147 		synchronized (mLock) {
148 			String message = new String(mess);
149 			scoreMessages.add(message);
150 		}
151 		} else csound.InputMessage(mess);
152 	}
153 
removeBinding(CsoundBinding binding)154 	public/* synchronized */void removeBinding(CsoundBinding binding) {
155 		synchronized (mLock) {
156 			bindings.remove(binding);
157 		}
158 	}
159 
getInputChannelPtr(String channelName, controlChannelType channelType)160 	public CsoundMYFLTArray getInputChannelPtr(String channelName,
161 			controlChannelType channelType) {
162 
163 		int channelSize = (channelType == controlChannelType.CSOUND_AUDIO_CHANNEL) ? getCsound()
164 				.GetKsmps() : 1;
165 		CsoundMYFLTArray ptr = new CsoundMYFLTArray(channelSize);
166 
167 		getCsound().GetChannelPtr(
168 				ptr.GetPtr(),
169 				channelName,
170 				channelType.swigValue()
171 						| controlChannelType.CSOUND_INPUT_CHANNEL.swigValue());
172 		return ptr;
173 	}
174 
getOutputChannelPtr(String channelName, controlChannelType channelType)175 	public CsoundMYFLTArray getOutputChannelPtr(String channelName,
176 			controlChannelType channelType) {
177 		int channelSize = (channelType == controlChannelType.CSOUND_AUDIO_CHANNEL) ? getCsound()
178 				.GetKsmps() : 1;
179 		CsoundMYFLTArray ptr = new CsoundMYFLTArray(channelSize);
180 
181 		getCsound().GetChannelPtr(
182 				ptr.GetPtr(),
183 				channelName,
184 				channelType.swigValue()
185 						| controlChannelType.CSOUND_OUTPUT_CHANNEL.swigValue());
186 		return ptr;
187 	}
188 
sendScore(String score)189 	public void sendScore(String score) {
190 		inputMessage(score);
191 	}
192 
readScore(String score)193 	public void readScore(String score) {
194 		sendScore(score);
195 	}
196 
compileCsdText(String csd_text)197     public void compileCsdText(String csd_text) {
198         csound.CompileCsdText(csd_text);
199     }
updateOrchestra(String orchestraString)200     public void updateOrchestra(String orchestraString) {
201         csound.CompileOrc(orchestraString);
202     }
203 
compileOrc(String orchestraString)204 	public void compileOrc(String orchestraString) {
205 		csound.CompileOrc(orchestraString);
206 	}
207 
addListener(CsoundObjListener listener)208 	public void addListener(CsoundObjListener listener) {
209 		synchronized(mLock) {
210 			listeners.add(listener);
211 		}
212 	}
213 
removeListener(CsoundObjListener listener)214 	public void removeListener(CsoundObjListener listener) {
215 		synchronized(mLock) {
216 			listeners.remove(listener);
217 		}
218 	}
219 
startCsound(final File csdFile)220 	public void startCsound(final File csdFile) {
221 		stopped = false;
222 		thread = new Thread() {
223 			public void run() {
224 				setPriority(Thread.MAX_PRIORITY);
225 				if (useAudioTrack == false) {
226 					// Log.d("CsoundObj", "USING OPENSL");
227 					runCsoundOpenSL(csdFile);
228 				} else {
229 					// Log.d("CsoundObj", "USING AUDIO TRACK");
230 					runCsoundAudioTrack(csdFile);
231 				}
232 			}
233 		};
234 		thread.start();
235 	}
236 
togglePause()237 	public void togglePause() {
238 		pause = !pause;
239 		if (!isAsync) ((AndroidCsound)csound).Pause(pause);
240 	}
241 
pause()242 	public void pause() {
243 		pause = true;
244 		if (!isAsync) ((AndroidCsound)csound).Pause(pause);
245 	}
246 
play()247 	public void play() {
248 		pause = false;
249 		if (!isAsync) ((AndroidCsound)csound).Pause(pause);
250 	}
251 
stop()252 	public synchronized void stop() {
253 		stopped = true;
254 		if (thread != null) {
255 			try {
256 				thread.join();
257 				thread = null;
258 			} catch (InterruptedException e) {
259 				Log.d("CsoundObj", e.toString());
260 				e.printStackTrace();
261 			}
262 		}
263 	 }
264 
getAsyncStatus()265 	public boolean getAsyncStatus() { return isAsync; }
266 
getNumChannels()267 	public int getNumChannels() {
268 		return csound.GetNchnls();
269 	}
270 
getKsmps()271 	public int getKsmps() {
272 		return csound.GetKsmps();
273 	}
274 
getError()275 	public int getError() {
276 		return retVal;
277 	}
278 
279     @JavascriptInterface
SetOption(String option)280     public int SetOption(String option) {
281         return csound.SetOption(option);
282     }
283 
284 	/* Render Methods */
285 
runCsoundOpenSL(File f)286 	private void runCsoundOpenSL(File f) {
287 		Log.d("CsoundObj", "THREAD START");
288 		((AndroidCsound) csound).setOpenSlCallbacks();
289 		if (messageLoggingEnabled) {
290 			callbacks = new CsoundCallbackWrapper(csound) {
291 				@Override
292 				public void MessageCallback(int attr, String msg) {
293 					Log.d("CsoundObj", msg);
294 					if (messagePoster != null) {
295 						messagePoster.postMessage(msg);
296 					}
297 					super.MessageCallback(attr, msg);
298 				}
299 			};
300 			callbacks.SetMessageCallback();
301 		}
302 		if(!isAsync) this.pause();
303 		if(f.getAbsolutePath().toLowerCase().endsWith(".csd")) {
304 			retVal = csound.Compile(f.getAbsolutePath());
305 		} else {
306             csound.SetOption("-odac");
307 			retVal = csound.Start();
308 		}
309 		Log.d("CsoundObj", "Return Value2: " + retVal);
310 		if (retVal == 0) {
311 			for (int i = 0; i < bindings.size(); i++) {
312 				CsoundBinding cacheable = bindings.get(i);
313 				cacheable.setup(this);
314 			}
315 			stopped = false;
316 			for (int i = 0; i < bindings.size(); i++) {
317 				CsoundBinding cacheable = bindings.get(i);
318 				cacheable.updateValuesToCsound();
319 			}
320 
321 			for (int i = 0; i < listeners.size(); i++) {
322 				CsoundObjListener listener = listeners.get(i);
323 				listener.csoundObjStarted(this);
324 			}
325 			startTime = System.nanoTime()*1.0e-6;
326 			//double tmptime = startTime;
327 			if(!isAsync) this.play();
328 			while(!stopped) {
329 			 int ret = 0;
330              if(isAsync){
331             	 ret = csound.PerformKsmps();
332             	 if(ret != 0) break;
333     			 stime += csound.GetKsmps();
334 
335     			 systime = System.nanoTime()*1.0e-6;
336     	         //Log.d("CsoundObj", "java time:" + (systime - startTime));
337     	         //Log.d("CsoundObj", "java diff:" + (systime - tmptime));
338     	         //tmptime = systime;
339 
340     				synchronized (mLock) {
341     					CsoundBinding cacheable;
342     					String mess;
343     					for (int i = 0; i < bindings.size(); i++) {
344     						cacheable = bindings.get(i);
345     						cacheable.updateValuesFromCsound();
346     					}
347     					for (int i = 0; i < scoreMessages.size(); i++) {
348     						mess = scoreMessages.get(i);
349     						csound.InputMessage(mess);
350     					}
351     					scoreMessages.clear();
352     					for (int i = 0; i < bindings.size(); i++) {
353     						cacheable = bindings.get(i);
354     						cacheable.updateValuesToCsound();
355     					}
356     				}
357     				while (pause)
358     					try {
359     						Thread.sleep(1);
360     					} catch (InterruptedException e) {
361     						e.printStackTrace();
362     					}
363              } else {
364 
365             	 try {
366 						Thread.sleep(100);
367 					} catch (InterruptedException e) {
368 						e.printStackTrace();
369 					}
370 
371              }
372 			}
373 			if (!isAsync) {
374 				csound.InputMessage("e 0");
375 				try {
376 					Thread.sleep(100);
377 				} catch (InterruptedException e) {
378 					e.printStackTrace();
379 				}
380 			}
381 			//csound.Stop();
382 			//csound.Cleanup();
383 			csound.Reset();
384 
385 			synchronized (mLock) {
386 				for (int i = 0; i < bindings.size(); i++) {
387 					CsoundBinding cacheable = bindings.get(i);
388 					cacheable.cleanup();
389 				}
390 				for (int i = 0; i < listeners.size(); i++) {
391 					CsoundObjListener listener = listeners.get(i);
392 					listener.csoundObjCompleted(this);
393 				}
394 			}
395 
396 		} else {
397 			synchronized (mLock) {
398 				for (int i = 0; i < listeners.size(); i++) {
399 					CsoundObjListener listener = listeners.get(i);
400 					listener.csoundObjCompleted(this);
401 				}
402 			}
403 		}
404 		Log.d("CsoundObj", "THREAD END");
405 	}
406 
runCsoundAudioTrack(File f)407 	private void runCsoundAudioTrack(File f) {
408 		csound.SetHostImplementedAudioIO(1, 0);
409 
410 		if (messageLoggingEnabled) {
411 			callbacks = new CsoundCallbackWrapper(csound) {
412 				@Override
413 				public void MessageCallback(int attr, String msg) {
414 					Log.d("CsoundObj", msg);
415 					if (messagePoster != null) {
416 						messagePoster.postMessage(msg);
417 					}
418 					super.MessageCallback(attr, msg);
419 				}
420 			};
421 			callbacks.SetMessageCallback();
422 		}
423 		retVal = csound.Compile(f.getAbsolutePath());
424 		Log.d("CsoundObj", "Return Value2: " + retVal);
425 		if (retVal == 0) {
426 			synchronized (mLock) {
427 				for (int i = 0; i < bindings.size(); i++) {
428 					CsoundBinding cacheable = bindings.get(i);
429 					cacheable.setup(this);
430 				}
431 			}
432 			int channelConfig = (csound.GetNchnls() == 2) ? AudioFormat.CHANNEL_OUT_STEREO
433 					: AudioFormat.CHANNEL_OUT_MONO;
434 
435 			int channelInConfig = AudioFormat.CHANNEL_IN_MONO;
436 
437 			int minSize = AudioTrack.getMinBufferSize((int) csound.GetSr(),
438 					channelConfig, AudioFormat.ENCODING_PCM_16BIT);
439 
440 			if (audioInEnabled) {
441 				int recordMinSize = AudioRecord.getMinBufferSize(
442 						(int) csound.GetSr(), channelInConfig,
443 						AudioFormat.ENCODING_PCM_16BIT);
444 				minSize = (minSize > recordMinSize) ? minSize : recordMinSize;
445 			}
446 
447 			AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
448 					(int) csound.GetSr(), channelConfig,
449 					AudioFormat.ENCODING_PCM_16BIT, minSize,
450 					AudioTrack.MODE_STREAM);
451 			Log.d("CsoundObj", "Buffer Size: " + minSize);
452 
453 			AudioRecord audioRecord = null;
454 			CsoundMYFLTArray audioIn = null;
455 
456 			if (audioInEnabled) {
457 
458 				audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
459 						(int) csound.GetSr(), channelInConfig,
460 						AudioFormat.ENCODING_PCM_16BIT, minSize);
461 
462 				if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
463 					Log.d("CsoundObj",
464 							"AudioRecord unable to be initialized. Error "
465 									+ audioRecord.getState());
466 					audioRecord.release();
467 					audioRecord = null;
468 				} else {
469 					try {
470 						audioRecord.startRecording();
471 						if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
472 							Log.d("CsoundObj",
473 									"AudioRecord unable to be initialized. Error "
474 											+ audioRecord.getRecordingState());
475 						}
476 						audioIn = new CsoundMYFLTArray();
477 						audioIn.SetPtr(csound.GetSpin());
478 					} catch (IllegalStateException e) {
479 						audioRecord.release();
480 						audioRecord = null;
481 						audioIn = null;
482 					}
483 				}
484 			}
485 			audioTrack.play();
486 			int counter = 0;
487 			int nchnls = csound.GetNchnls();
488 			int recBufferSize = csound.GetKsmps();
489 			int bufferSize = recBufferSize * nchnls;
490 			short[] samples = new short[bufferSize];
491 			float multiplier = (float) (Short.MAX_VALUE / csound.Get0dBFS());
492 			float recMultiplier = 1 / multiplier;
493 			Log.d("CsoundObj", "Multiplier: " + multiplier + " : "
494 					+ recMultiplier);
495 			stopped = false;
496 			for (CsoundBinding cacheable : bindings) {
497 				cacheable.updateValuesToCsound();
498 			}
499 			short recordSample[] = new short[recBufferSize];
500 			if (audioRecord != null) {
501 				audioRecord.read(recordSample, 0, recBufferSize);
502 				for (int i = 0; i < csound.GetKsmps(); i++) {
503 					short sample = recordSample[i];
504 					if (nchnls == 2) {
505 						int index = i * 2;
506 						audioIn.SetValues(index,
507 								(double) (sample * recMultiplier),
508 								(double) (sample * recMultiplier));
509 					} else {
510 						audioIn.SetValue(i, sample);
511 					}
512 				}
513 			}
514 			while (csound.PerformKsmps() == 0 && !stopped) {
515 				for (int i = 0; i < csound.GetKsmps(); i++) {
516 					samples[counter++] = (short) (csound.GetSpoutSample(i, 0) * multiplier);
517 					if (nchnls > 1) {
518 						samples[counter++] = (short) (csound.GetSpoutSample(i,
519 								1) * multiplier);
520 					}
521 				}
522 				if (counter >= bufferSize) {
523 					audioTrack.write(samples, 0, bufferSize);
524 					counter = 0;
525 				}
526 				synchronized (mLock) {
527 					for (int i = 0; i < bindings.size(); i++) {
528 						CsoundBinding cacheable = bindings.get(i);
529 						cacheable.updateValuesFromCsound();
530 					}
531 					for (int i = 0; i < scoreMessages.size(); i++) {
532 						String mess = scoreMessages.get(i);
533 						csound.InputMessage(mess);
534 					}
535 					scoreMessages.clear();
536 					for (int i = 0; i < bindings.size(); i++) {
537 						CsoundBinding cacheable = bindings.get(i);
538 						cacheable.updateValuesToCsound();
539 					}
540 				}
541 				if (audioRecord != null) {
542 					audioRecord.read(recordSample, 0, recBufferSize);
543 					for (int i = 0; i < csound.GetKsmps(); i++) {
544 						short sample = recordSample[i];
545 						if (nchnls == 2) {
546 							int index = i * 2;
547 							audioIn.SetValues(index,
548 									(double) (sample * recMultiplier),
549 									(double) (sample * recMultiplier));
550 						} else {
551 							audioIn.SetValue(i, sample);
552 						}
553 					}
554 				}
555 				while (pause)
556 					try {
557 						Thread.sleep(1);
558 					} catch (InterruptedException e) {
559 						e.printStackTrace();
560 					}
561 			}
562 			audioTrack.stop();
563 			audioTrack.release();
564 			if (audioRecord != null) {
565 				audioRecord.stop();
566 				audioRecord.release();
567 				audioIn.Clear();
568 			}
569 			csound.Stop();
570 			csound.Cleanup();
571 			csound.Reset();
572 			synchronized (mLock) {
573 				for (int i = 0; i < bindings.size(); i++) {
574 					CsoundBinding cacheable = bindings.get(i);
575 					cacheable.cleanup();
576 				}
577 				for (int i = 0; i < listeners.size(); i++) {
578 					CsoundObjListener listener = listeners.get(i);
579 					listener.csoundObjCompleted(this);
580 				}
581 			}
582 		} else {
583 			synchronized (mLock) {
584 				for (int i = 0; i < listeners.size(); i++) {
585 					CsoundObjListener listener = listeners.get(i);
586 					listener.csoundObjCompleted(this);
587 				}
588 			}
589 		}
590 	}
591 }
592