1. /*
  2. * This file is part of Beads. See http://www.beadsproject.net for all information.
  3. */
  4. package net.beadsproject.beads.ugens;
  5. import net.beadsproject.beads.core.AudioContext;
  6. import net.beadsproject.beads.core.Bead;
  7. import net.beadsproject.beads.core.UGen;
  8. import net.beadsproject.beads.data.Sample;
  9. import net.beadsproject.beads.data.SampleManager;
  10. /**
  11. * SamplePlayer plays back a {@link Sample}. Playback rate and loop points can be controlled by {@link UGen}s. The playback point in the {@link Sample} can also be directly controlled from {@link UGen} to perform scrubbing. The player can be set to a number of different loop modes. If constructed with a {@link Sample} argument, the number of outputs of SamplePlayer is determined by the number of channels of the {@link Sample}. {@link Sample} playback can use either linear or cubic interpolation.
  12. *
  13. * @author ollie
  14. */
  15. public class SamplePlayer extends UGen {
  16. /**
  17. * The Enum InterpolationType.
  18. */
  19. public static enum InterpolationType {
  20. /** Use linear interpolation. */
  21. LINEAR,
  22. /** Use cubic interpolation. */
  23. CUBIC
  24. };
  25. /**
  26. * The Enum LoopType.
  27. */
  28. public static enum LoopType {
  29. /** Play forwards without looping. */
  30. NO_LOOP_FORWARDS,
  31. /** Play backwards without looping. */
  32. NO_LOOP_BACKWARDS,
  33. /** Play forwards with loop. */
  34. LOOP_FORWARDS,
  35. /** Play backwards with loop. */
  36. LOOP_BACKWARDS,
  37. /** Loop alternately forwards and backwards. */
  38. LOOP_ALTERNATING
  39. };
  40. /** The Sample. */
  41. protected Sample buffer;
  42. /** The sample rate, determined by the Sample. */
  43. protected float sampleRate;
  44. /** The position in milliseconds. */
  45. protected double position;
  46. /** The last changed position, used to determine if the position envelope is in use. */
  47. protected double lastChangedPosition;
  48. /** The position envelope. */
  49. protected UGen positionEnvelope;
  50. /** The rate envelope. */
  51. protected UGen rateEnvelope;
  52. /** The millisecond position increment per sample. Calculated from the ratio of the {@link AudioContext}'s sample rate and the {@link Sample}'s sample rate. */
  53. protected double positionIncrement;
  54. /** Flag for alternating loop mode to determine if playback is in forward or reverse phase. */
  55. protected boolean forwards;
  56. /** The interpolation type. */
  57. protected InterpolationType interpolationType;
  58. /** The loop start envelope. */
  59. protected UGen loopStartEnvelope;
  60. /** The loop end envelope. */
  61. protected UGen loopEndEnvelope;
  62. /** The loop type. */
  63. protected LoopType loopType;
  64. /** The loop cross fade in milliseconds. */
  65. protected float loopCrossFade; //TODO loop crossfade behaviour
  66. /** Flag to determine whether playback starts at the beginning of the sample or at the beginning of the loop. */
  67. protected boolean startLoop; //TODO behaviour such that if you're outside the loop points you immediately pop inside them.
  68. /** Flag to determine whether the SamplePlayer should kill itself when it gets to the end of the Sample. */
  69. protected boolean killOnEnd;
  70. /** The rate. Calculated and used internally from the rate envelope. */
  71. protected float rate;
  72. /** The loop start. Calculated and used internally from the loop start envelope. */
  73. protected float loopStart;
  74. /** The loop end. Calculated and used internally from the loop end envelope. */
  75. protected float loopEnd;
  76. /**
  77. * Instantiates a new SamplePlayer with given number of outputs.
  78. *
  79. * @param context the AudioContext.
  80. * @param outs the number of outputs.
  81. */
  82. public SamplePlayer(AudioContext context, int outs) {
  83. super(context, outs);
  84. rateEnvelope = new Static(context, 1.0f);
  85. positionEnvelope = new Static(context, 0.0f);
  86. interpolationType = InterpolationType.LINEAR;
  87. loopType = LoopType.NO_LOOP_FORWARDS;
  88. forwards = true;
  89. killOnEnd = true;
  90. loopStartEnvelope = new Static(context, 0.0f);
  91. loopEndEnvelope = new Static(context, 0.0f);
  92. }
  93. /**
  94. * Instantiates a new SamplePlayer with given Sample. Number of outputs is determined by number of channels in Sample.
  95. *
  96. * @param context the AudioContext.
  97. * @param buffer the Sample.
  98. */
  99. public SamplePlayer(AudioContext context, Sample buffer) {
  100. this(context, buffer.nChannels);
  101. setBuffer(buffer);
  102. loopEndEnvelope.setValue(buffer.length);
  103. }
  104. /**
  105. * Sets the Sample.
  106. *
  107. * @param buffer the new Sample.
  108. */
  109. public void setBuffer(Sample buffer) {
  110. this.buffer = buffer;
  111. sampleRate = buffer.audioFormat.getSampleRate();
  112. updatePositionIncrement();
  113. }
  114. /**
  115. * Gets the Sample.
  116. *
  117. * @return the Sample.
  118. */
  119. public Sample getBuffer() {
  120. return buffer;
  121. }
  122. /**
  123. * Sets the playback position to the end of the Sample.
  124. */
  125. public void setToEnd() {
  126. position = buffer.length;
  127. }
  128. /**
  129. * Determines whether the playback position is within the loop points.
  130. *
  131. * @return true if the playback position is within the loop points.
  132. */
  133. public boolean inLoop() {
  134. return position < Math.max(loopStart, loopEnd) && position > Math.min(loopStart, loopEnd);
  135. }
  136. /**
  137. * Sets the playback position to the loop start point.
  138. */
  139. public void setToLoopStart() {
  140. position = Math.min(loopStart, loopEnd);
  141. forwards = (rate > 0);
  142. }
  143. /**
  144. * Starts the sample at the given position.
  145. *
  146. * @param msPosition the position in milliseconds.
  147. */
  148. public void start(float msPosition) {
  149. position = msPosition;
  150. start();
  151. }
  152. /**
  153. * Resets the position to the start of the Sample.
  154. */
  155. public void reset() {
  156. position = 0f;
  157. }
  158. /**
  159. * Gets the playback position.
  160. *
  161. * @return the position in milliseconds.
  162. */
  163. public double getPosition() {
  164. return position;
  165. }
  166. /**
  167. * Sets the playback position.
  168. *
  169. * @param position the new position in milliseconds.
  170. */
  171. public void setPosition(double position) {
  172. this.position = position;
  173. }
  174. /**
  175. * Gets the position envelope.
  176. *
  177. * @return the position envelope.
  178. */
  179. public UGen getPositionEnvelope() {
  180. return positionEnvelope;
  181. }
  182. /**
  183. * Sets the position envelope. Setting the position envelope means that the position is then controlled by this envelope. If the envelope is null, or unchanging, the position continues to be modified by the SamplePlayer's internal playback or by calls to change the position.
  184. *
  185. * @param positionEnvelope the new position envelope.
  186. */
  187. public void setPositionEnvelope(UGen positionEnvelope) {
  188. this.positionEnvelope = positionEnvelope;
  189. }
  190. /**
  191. * Gets the rate envelope.
  192. *
  193. * @return the rate envelope.
  194. */
  195. public UGen getRateEnvelope() {
  196. return rateEnvelope;
  197. }
  198. /**
  199. * Sets the rate envelope.
  200. *
  201. * @param rateEnvelope the new rate envelope.
  202. */
  203. public void setRateEnvelope(UGen rateEnvelope) {
  204. this.rateEnvelope = rateEnvelope;
  205. }
  206. /**
  207. * Updates the position increment. Called whenever the {@link Sample}'s sample rate or the {@link AudioContext}'s sample rate is modified.
  208. */
  209. private void updatePositionIncrement() {
  210. positionIncrement = context.samplesToMs(sampleRate / context.getSampleRate());
  211. }
  212. /**
  213. * Gets the interpolation type.
  214. *
  215. * @return the interpolation type.
  216. */
  217. public InterpolationType getInterpolationType() {
  218. return interpolationType;
  219. }
  220. /**
  221. * Sets the interpolation type.
  222. *
  223. * @param interpolationType the new interpolation type.
  224. */
  225. public void setInterpolationType(InterpolationType interpolationType) {
  226. this.interpolationType = interpolationType;
  227. }
  228. /**
  229. * Gets the loop cross fade.
  230. *
  231. * @return the loop cross fade in milliseconds.
  232. */
  233. public float getLoopCrossFade() {
  234. return loopCrossFade;
  235. }
  236. /**
  237. * Sets the loop cross fade.
  238. *
  239. * @param loopCrossFade the new loop cross fade in milliseconds.
  240. */
  241. public void setLoopCrossFade(float loopCrossFade) {
  242. this.loopCrossFade = loopCrossFade;
  243. }
  244. /**
  245. * Gets the loop end envelope.
  246. *
  247. * @return the loop end envelope.
  248. */
  249. public UGen getLoopEndEnvelope() {
  250. return loopEndEnvelope;
  251. }
  252. /**
  253. * Sets the loop end envelope.
  254. *
  255. * @param loopEndEnvelope the new loop end envelope.
  256. */
  257. public void setLoopEndEnvelope(UGen loopEndEnvelope) {
  258. this.loopEndEnvelope = loopEndEnvelope;
  259. }
  260. /**
  261. * Gets the loop start envelope.
  262. *
  263. * @return the loop start envelope
  264. */
  265. public UGen getLoopStartEnvelope() {
  266. return loopStartEnvelope;
  267. }
  268. /**
  269. * Sets the loop start envelope.
  270. *
  271. * @param loopStartEnvelope the new loop start envelope.
  272. */
  273. public void setLoopStartEnvelope(UGen loopStartEnvelope) {
  274. this.loopStartEnvelope = loopStartEnvelope;
  275. }
  276. /**
  277. * Sets both loop points to static values as fractions of the Sample length.
  278. *
  279. * @param start the start value, as fraction of the Sample length.
  280. * @param end the end value, as fraction of the Sample length.
  281. */
  282. public void setLoopPointsFraction(float start, float end) {
  283. loopStartEnvelope = new Static(context, start * (float)buffer.length);
  284. loopEndEnvelope = new Static(context, end * (float)buffer.length);
  285. }
  286. /**
  287. * Gets the loop type.
  288. *
  289. * @return the loop type.
  290. */
  291. public LoopType getLoopType() {
  292. return loopType;
  293. }
  294. /**
  295. * Sets the loop type.
  296. *
  297. * @param loopType the new loop type.
  298. */
  299. public void setLoopType(LoopType loopType) {
  300. this.loopType = loopType;
  301. if(loopType != LoopType.LOOP_ALTERNATING) {
  302. if(loopType == LoopType.LOOP_FORWARDS || loopType == LoopType.NO_LOOP_FORWARDS) {
  303. forwards = true;
  304. } else {
  305. forwards = false;
  306. }
  307. }
  308. }
  309. /**
  310. * Gets the sample rate.
  311. *
  312. * @return the sample rate, in samples per second.
  313. */
  314. public float getSampleRate() {
  315. return sampleRate;
  316. }
  317. /* (non-Javadoc)
  318. * @see com.olliebown.beads.core.UGen#calculateBuffer()
  319. */
  320. @Override
  321. public void calculateBuffer() {
  322. if(buffer != null) {
  323. rateEnvelope.update();
  324. positionEnvelope.update();
  325. loopStartEnvelope.update();
  326. loopEndEnvelope.update();
  327. for (int i = 0; i < bufferSize; i++) {
  328. //calculate the samples
  329. double posInSamples = buffer.msToSamples((float)position);
  330. int currentSample = (int) posInSamples;
  331. float fractionOffset = (float)(posInSamples - currentSample);
  332. float[] frame = null;
  333. switch (interpolationType) {
  334. case LINEAR:
  335. frame = buffer.getFrameLinear(currentSample, fractionOffset);
  336. break;
  337. case CUBIC:
  338. frame = buffer.getFrameCubic(currentSample, fractionOffset);
  339. break;
  340. }
  341. for (int j = 0; j < outs; j++) {
  342. bufOut[j][i] = frame[j % buffer.nChannels];
  343. }
  344. //update the position, loop state, direction
  345. calculateNextPosition(i);
  346. if(isPaused()) {
  347. //make sure to zero the remaining outs
  348. while(i < bufferSize) {
  349. for (int j = 0; j < outs; j++) {
  350. bufOut[j][i] = 0.0f;
  351. }
  352. i++;
  353. }
  354. break;
  355. }
  356. }
  357. }
  358. }
  359. /**
  360. * Sets/unsets option for SamplePlayer to kill itself when it reaches the end of the Sample it is playing.
  361. *
  362. * @param killOnEnd true to kill on end.
  363. */
  364. public void setKillOnEnd(boolean killOnEnd) {
  365. this.killOnEnd = killOnEnd;
  366. }
  367. /**
  368. * Determines whether this SamplePlayer will kill itself at the end of the Sample it is playing.
  369. *
  370. * @return true of SamplePlayer will kill itself at the end of the Sample it is playing.
  371. */
  372. public boolean getKillOnEnd() {
  373. return(killOnEnd);
  374. }
  375. /**
  376. * Called when at the end of the Sample, assuming the loop mode is non-looping, or beginning, if the SamplePlayer is playing backwards..
  377. */
  378. private void atEnd () {
  379. if (killOnEnd) {
  380. kill();
  381. }
  382. else {
  383. pause(true);
  384. }
  385. }
  386. /**
  387. * Re trigger the SamplePlayer from the beginning.
  388. */
  389. public void reTrigger() {
  390. reset();
  391. this.pause(false);
  392. }
  393. /**
  394. * Used at each sample in the perform routine to determine the next playback position.
  395. *
  396. * @param i the index within the buffer loop.
  397. */
  398. protected void calculateNextPosition(int i) {
  399. rate = rateEnvelope.getValue(0, i);
  400. loopStart = loopStartEnvelope.getValue(0, i);
  401. loopEnd = loopEndEnvelope.getValue(0, i);
  402. if(lastChangedPosition != positionEnvelope.getValue(0, i)) {
  403. position = positionEnvelope.getValue(0, i);
  404. lastChangedPosition = position;
  405. }
  406. switch(loopType) {
  407. case NO_LOOP_FORWARDS:
  408. position += positionIncrement * rate;
  409. if(position > buffer.length || position < 0) atEnd();
  410. break;
  411. case NO_LOOP_BACKWARDS:
  412. position -= positionIncrement * rate;
  413. if(position > buffer.length || position < 0) atEnd();
  414. break;
  415. case LOOP_FORWARDS:
  416. position += positionIncrement * rate;
  417. if(rate > 0 && position > Math.max(loopStart, loopEnd)) {
  418. position = Math.min(loopStart, loopEnd);
  419. } else if(rate < 0 && position < Math.min(loopStart, loopEnd)) {
  420. position = Math.max(loopStart, loopEnd);
  421. }
  422. break;
  423. case LOOP_BACKWARDS:
  424. position -= positionIncrement * rate;
  425. if(rate > 0 && position < Math.min(loopStart, loopEnd)) {
  426. position = Math.max(loopStart, loopEnd);
  427. } else if(rate < 0 && position > Math.max(loopStart, loopEnd)) {
  428. position = Math.min(loopStart, loopEnd);
  429. }
  430. break;
  431. case LOOP_ALTERNATING:
  432. position += forwards ? positionIncrement * rate : -positionIncrement * rate;
  433. if(forwards ^ (rate < 0)) {
  434. if(position > Math.max(loopStart, loopEnd)) {
  435. forwards = (rate < 0);
  436. position = 2 * Math.max(loopStart, loopEnd) - position;
  437. }
  438. } else if(position < Math.min(loopStart, loopEnd)) {
  439. forwards = (rate > 0);
  440. position = 2 * Math.min(loopStart, loopEnd) - position;
  441. }
  442. break;
  443. }
  444. }
  445. }