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