PageRenderTime 58ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/Onyx-VJ 4.5.0/Onyx-VJ-Plugins/src/plugins/visualizer/ScratchVisualizer.as

http://onyx-vj.googlecode.com/
ActionScript | 532 lines | 405 code | 103 blank | 24 comment | 28 complexity | f79caa4cb7e0b464c0054852f713d12e MD5 | raw file
  1. /**
  2. * Copyright devon_o ( http://wonderfl.net/user/devon_o )
  3. * MIT License ( http://www.opensource.org/licenses/mit-license.php )
  4. * Downloaded from: http://wonderfl.net/c/iYlE
  5. */
  6. package plugins.visualizer {
  7. import flash.display.*;
  8. import flash.events.*;
  9. import flash.media.Sound;
  10. import onyx.core.*;
  11. import onyx.events.InteractionEvent;
  12. import onyx.parameter.*;
  13. import onyx.plugin.*;
  14. public final class ScratchVisualizer extends Visualizer
  15. {
  16. private var mLoadedSong:Sound;
  17. private var mScratchDisplay:ScratchDisplay;
  18. private var mRightSoundDiplay:ScratchDisplay;
  19. [Embed(source="../assets/batchass.mp3")]
  20. public var soundClass:Class;
  21. public function ScratchVisualizer() {
  22. Console.output('ScratchVisualizer from devon_o ( http://wonderfl.net/user/devon_o )');
  23. Console.output('Adapted by Bruce LANE (http://www.batchass.fr)');
  24. }
  25. public function load():void
  26. {
  27. mLoadedSong = new soundClass() as Sound;
  28. mScratchDisplay = new ScratchDisplay();
  29. mScratchDisplay.loadSound(mLoadedSong);
  30. //mLoadedSong.play();
  31. mScratchDisplay.play();
  32. }
  33. override public function render(info:RenderInfo):void
  34. {
  35. if ( mScratchDisplay )
  36. {
  37. mScratchDisplay.mouseMove(null);
  38. info.render( mScratchDisplay );
  39. }
  40. }
  41. }
  42. }
  43. // ***** SCRATCH DISPLAY **** //
  44. import flash.display.Shape;
  45. import flash.display.Sprite;
  46. import flash.events.Event;
  47. import flash.events.MouseEvent;
  48. import flash.events.SampleDataEvent;
  49. import flash.geom.Rectangle;
  50. import flash.media.Sound;
  51. import flash.media.SoundChannel;
  52. import flash.media.SoundTransform;
  53. import flash.utils.ByteArray;
  54. import flash.utils.getTimer;
  55. class ScratchDisplay extends Sprite
  56. {
  57. private const WRITE_SIZE:int = 2048;
  58. private const PLAYBACK_FREQUENCY:Number = 44100.0;
  59. private const DRAG:Number = .25;
  60. private const SPEED_EASE:Number = 1.0;
  61. private const SPEED_MULT:Number = 24;
  62. private const MOVE_EASE:Number = 1.0;
  63. private const BYTE_POSITION_TO_PIXELS:Number = 0.00004; // original = .0004
  64. public var mSoundData:ByteArray;
  65. private var mSamples:Vector.<Number>;
  66. private var mNumSamples:Number;
  67. private var mSound:Sound;
  68. private var mWaveHolder:Sprite;
  69. private var mPlayingSound:Sound;
  70. private var mDisplay:Shape;
  71. private var mIsScratching:Boolean;
  72. private var mPosition:Number = 0.0;
  73. private var mTargetSpeed:Number;
  74. private var mSpeed:Number = 2.0;
  75. private var mChannel:SoundChannel;
  76. private var mVolume:SoundTransform;
  77. private var mRatio:Number;
  78. private var mCenterX:Number;
  79. // throwing
  80. private var mDiffX:Number;
  81. private var mVelX:Number = 0.0;
  82. private var mCurrX:Number;
  83. private var mPrevX:Number;
  84. private var mFarLeft:Number;
  85. private var mFarRight:Number;
  86. public function ScratchDisplay()
  87. {
  88. mPlayingSound = new Sound();
  89. mVolume = new SoundTransform();
  90. mWaveHolder = new Sprite();
  91. }
  92. public function loadSound(sound:Sound)
  93. {
  94. mSound = sound;
  95. drawWaveForm();
  96. extractSamples();
  97. initSmoothBuffer();
  98. initDisplay();
  99. }
  100. public function play():void
  101. {
  102. mPlayingSound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSample);
  103. mChannel = mPlayingSound.play();
  104. mWaveHolder.buttonMode = true;
  105. mWaveHolder.addEventListener(MouseEvent.MOUSE_DOWN, startScratch);
  106. }
  107. public function stop():void
  108. {
  109. mPlayingSound.removeEventListener(SampleDataEvent.SAMPLE_DATA, onSample);
  110. mChannel.stop();
  111. mWaveHolder.buttonMode = false;
  112. mWaveHolder.removeEventListener(MouseEvent.MOUSE_DOWN, startScratch);
  113. }
  114. public function setVolume(value:Number):void
  115. {
  116. mVolume.volume = value;
  117. mChannel.soundTransform = mVolume;
  118. }
  119. private function initSmoothBuffer():void
  120. {
  121. var i:int = mSmoothBufferSize;
  122. while (i--)
  123. {
  124. mSmoothBuffer[i] = 0.0;
  125. }
  126. }
  127. public function wave_interpolator(x:Number, y:Vector.<Number>):Number
  128. {
  129. const offset:int = 2;
  130. var z:Number = x - 1 / 2.0;
  131. var even1:Number = y[int(offset + 1)] + y[int(offset + 0)], odd1:Number = y[int(offset + 1)] - y[int(offset + 0)];
  132. var even2:Number = y[int(offset + 2)] + y[int(offset + -1)], odd2:Number = y[int(offset + 2)] - y[int(offset + -1)];
  133. var even3:Number = y[int(offset + 3)] + y[int(offset + -2)], odd3:Number = y[int(offset + 3)] - y[int(offset + -2)];
  134. var c0:Number = even1 * 0.42685983409379380 + even2 * 0.07238123511170030
  135. + even3 * 0.00075893079450573;
  136. var c1:Number = odd1 * 0.35831772348893259 + odd2 * 0.20451644554758297
  137. + odd3 * 0.00562658797241955;
  138. var c2:Number = even1 * -0.217009177221292431 + even2 * 0.20051376594086157
  139. + even3 * 0.01649541128040211;
  140. var c3:Number = odd1 * -0.25112715343740988 + odd2 * 0.04223025992200458
  141. + odd3 * 0.02488727472995134;
  142. var c4:Number = even1 * 0.04166946673533273 + even2 * -0.06250420114356986
  143. + even3 * 0.02083473440841799;
  144. var c5:Number = odd1 * 0.08349799235675044 + odd2 * -0.04174912841630993
  145. + odd3 * 0.00834987866042734;
  146. return ((((c5 * z + c4) * z + c3) * z + c2) * z + c1) * z + c0;
  147. }
  148. private function onSample(event:SampleDataEvent):void
  149. {
  150. var data:ByteArray = event.data;
  151. if (mIsScratching)
  152. {
  153. writeScratchStream(data);
  154. } else {
  155. writeNormalStream(data);
  156. mRatio = mPosition / mNumSamples;
  157. mWaveHolder.x = mCenterX - (mWaveHolder.width * mRatio);
  158. }
  159. }
  160. private function writeNormalStream(stream:ByteArray):void
  161. {
  162. for (var i:int = 0; i < WRITE_SIZE; i++)
  163. {
  164. var sample:Number = getNextSample();
  165. stream.writeFloat(sample);
  166. stream.writeFloat(sample);
  167. mPosition += mSpeed;
  168. if (mPosition >= mNumSamples) mPosition = 0;
  169. if (mPosition < 0) mPosition = mNumSamples - 1;
  170. }
  171. }
  172. // http://blog.glowinteractive.com/2011/01/vinyl-scratch-emulation-on-iphone/
  173. private var mAudioSampleSize:uint = 8; // original = sizeof(float) * 2
  174. private var mScratchingBufferSize:uint = 500000;
  175. private var mSmoothBufferSize:uint = 3000;
  176. private var mScratchCircularBufferSamplePosition:int = 0;
  177. private var mScratchingPositionOffset:Number = 0.0;
  178. private var mScratchingPositionSmoothedVelocity:Number = 0.0;
  179. private var mScratchingPositionVelocity:Number = 0.0;
  180. // for smoothing of input
  181. private var mSmoothBuffer:Vector.<Number> = new Vector.<Number>(mSmoothBufferSize, true);
  182. private var mSmoothBufferPositionsUsed:int = 0;
  183. private var mSmoothBufferPosition:int = 0;
  184. private var mLeftInterpData:Vector.<Number> = new Vector.<Number>(6, true);
  185. private var mRightInterpData:Vector.<Number> = new Vector.<Number>(6, true);
  186. // comments from original source. http://blog.glowinteractive.com/2011/01/vinyl-scratch-emulation-on-iphone/
  187. private function writeScratchStream(stream:ByteArray):void
  188. {
  189. var bufferBasePosition:Number = Number(mScratchCircularBufferSamplePosition);
  190. for (var i:int = 0; i < WRITE_SIZE; i++)
  191. {
  192. if (++mSmoothBufferPositionsUsed > int(mSmoothBufferSize))
  193. {
  194. mSmoothBufferPositionsUsed = mSmoothBufferSize;
  195. }
  196. // STEP 1
  197. // find moving average
  198. mScratchingPositionSmoothedVelocity -= mSmoothBuffer[mSmoothBufferPosition];
  199. mSmoothBuffer[mSmoothBufferPosition] = mScratchingPositionVelocity / mSmoothBufferPositionsUsed;
  200. mScratchingPositionSmoothedVelocity += mSmoothBuffer[mSmoothBufferPosition];
  201. mSmoothBufferPosition = (++mSmoothBufferPosition) % mSmoothBufferSize;
  202. var velocity:Number = mScratchingPositionSmoothedVelocity;
  203. // STEP 2
  204. // modify velocity to point to the correct position
  205. var targetOffset:Number = mPosition + mSpeed;
  206. var offsetDiff:Number = targetOffset - mPosition;
  207. velocity += offsetDiff * 10.0;
  208. // STEP 3
  209. // update scratch buffer position
  210. mScratchingPositionOffset += velocity / PLAYBACK_FREQUENCY;
  211. // find absolute scratch buffer position
  212. var fBufferPosition:Number = bufferBasePosition + mScratchingPositionOffset;
  213. // wrap fBufferPosition
  214. //TODO use fmodf but what's faster?
  215. while (fBufferPosition >= mScratchingBufferSize) fBufferPosition -= mScratchingBufferSize;
  216. while (fBufferPosition < 0) fBufferPosition += mScratchingBufferSize;
  217. // STEP 4
  218. // use interpolation to find a sample value
  219. var iBufferPosition:int = int(fBufferPosition);
  220. var rem:Number = fBufferPosition - iBufferPosition;
  221. var pos:int = 2 * (iBufferPosition - 2)
  222. var sample:Number = readSample(pos);
  223. mLeftInterpData[0] = sample;
  224. ++pos;
  225. sample = readSample(pos);
  226. mRightInterpData[0] = sample;
  227. ++pos;
  228. sample = readSample(pos);
  229. mLeftInterpData[1] = sample;
  230. ++pos;
  231. sample = readSample(pos);
  232. mRightInterpData[1] = sample;
  233. ++pos;
  234. sample = readSample(pos);
  235. mLeftInterpData[2] = sample;
  236. ++pos;
  237. sample = readSample(pos);
  238. mRightInterpData[2] = sample;
  239. ++pos;
  240. sample = readSample(pos);
  241. mLeftInterpData[3] = sample;
  242. ++pos;
  243. sample = readSample(pos);
  244. mRightInterpData[3] = sample;
  245. ++pos;
  246. sample = readSample(pos);
  247. mLeftInterpData[4] = sample;
  248. ++pos;
  249. sample = readSample(pos);
  250. mRightInterpData[4] = sample;
  251. ++pos;
  252. sample = readSample(pos);
  253. mLeftInterpData[5] = sample;
  254. ++pos;
  255. sample = readSample(pos);
  256. mRightInterpData[5] = sample;
  257. stream.writeFloat(wave_interpolator(rem, mLeftInterpData));
  258. stream.writeFloat(wave_interpolator(rem, mRightInterpData));
  259. }
  260. }
  261. public function getNextSample():Number
  262. {
  263. var intPos:int = int(mPosition);
  264. return readSample(intPos);
  265. }
  266. private function readSample(pos:int):Number
  267. {
  268. while (pos < 0) pos += mNumSamples;
  269. while (pos >= mNumSamples) pos -= mNumSamples;
  270. return mSamples[pos];
  271. }
  272. private function clamp(target:Number, min:Number, max:Number):Number
  273. {
  274. if (target < min) target = min;
  275. if (target > max) target = max;
  276. return target;
  277. }
  278. private function startScratch(event:MouseEvent):void
  279. {
  280. mIsScratching = true;
  281. mPrevX = mWaveHolder.x
  282. mDiffX = event.localX - mWaveHolder.x;
  283. addEventListener(MouseEvent.MOUSE_UP, endScratch);
  284. addEventListener(Event.ENTER_FRAME, mouseMove);
  285. }
  286. private function endScratch(event:MouseEvent):void
  287. {
  288. removeEventListener(MouseEvent.MOUSE_UP, endScratch);
  289. removeEventListener(Event.ENTER_FRAME, mouseMove);
  290. addEventListener(Event.ENTER_FRAME, slide);
  291. }
  292. // ENTER FRAME FUNCTION
  293. public function mouseMove(event:Event):void
  294. {
  295. var tx:Number = mouseX - mDiffX;
  296. mWaveHolder.x += (tx - mWaveHolder.x) / MOVE_EASE;
  297. if (mWaveHolder.x < mFarLeft) mWaveHolder.x = mFarLeft;
  298. if (mWaveHolder.x > mFarRight) mWaveHolder.x = mFarRight;
  299. mVelX = mWaveHolder.x - mPrevX;
  300. mPrevX = mWaveHolder.x;
  301. setSpeed();
  302. setByteOffset(mSpeed / BYTE_POSITION_TO_PIXELS);
  303. update();
  304. }
  305. private var mPreviousTime:Number = 0.0;
  306. private var mPositionOffset:Number = 0.0;
  307. private var mPreviousPositionOffset:Number = 0.0;
  308. private function update():void
  309. {
  310. var time:int = getTimer();
  311. var dt:Number = time - mPreviousTime;
  312. if (dt == 0.0)
  313. mScratchingPositionVelocity = 0.0;
  314. else
  315. mScratchingPositionVelocity = (mPosition - mScratchingPositionOffset) / dt;
  316. mPreviousTime = time;
  317. }
  318. private function setByteOffset(offset:Number):void
  319. {
  320. mPosition = offset / mAudioSampleSize;
  321. }
  322. private function initDisplay():void
  323. {
  324. mDisplay = new Shape();
  325. mDisplay.graphics.beginFill(0x0, 0);
  326. mDisplay.graphics.lineStyle(0, 0x00FF00);
  327. mDisplay.graphics.drawRect(0, 0, 300, 150);
  328. mDisplay.graphics.lineStyle(0, 0xFF0000);
  329. mDisplay.graphics.moveTo(150, 0);
  330. mDisplay.graphics.lineTo(150, 150);
  331. mDisplay.graphics.endFill();
  332. var waveMask:Shape = new Shape();
  333. waveMask.graphics.beginFill(0xFF00FF);
  334. waveMask.graphics.drawRect(0, 0, 300, 150);
  335. waveMask.graphics.endFill();
  336. mCenterX = mDisplay.width >> 1;
  337. mWaveHolder.y = mDisplay.height >> 1;
  338. mWaveHolder.x = mCenterX;
  339. addChild(mWaveHolder);
  340. var r:Rectangle = new Rectangle( -mWaveHolder.width + mCenterX +2, mWaveHolder.y, mWaveHolder.width -4, 0);
  341. mFarLeft = r.left;
  342. mFarRight = r.right;
  343. mWaveHolder.mask = waveMask;
  344. addChild(waveMask);
  345. addChild(mDisplay);
  346. }
  347. private function drawWaveForm():void
  348. {
  349. mSoundData = new ByteArray();
  350. var max:Number = PLAYBACK_FREQUENCY * (mSound.length / 1000) - WRITE_SIZE;
  351. mSound.extract(mSoundData, max);
  352. mSoundData.position = 0;
  353. var yr:int = 35;
  354. var step:int = 1;
  355. var xp:int = 0;
  356. var waveform:Shape = new Shape();
  357. waveform.graphics.lineStyle(0, 0x00FF00);
  358. while(mSoundData.bytesAvailable > PLAYBACK_FREQUENCY * 2)
  359. {
  360. var minLeft:Number = Number.MAX_VALUE;
  361. var minRight:Number = Number.MAX_VALUE;
  362. var maxRight:Number = Number.MIN_VALUE;
  363. var maxLeft:Number = Number.MIN_VALUE;
  364. for (var i:uint = 0; i < 1024; i++)
  365. {
  366. var left:Number = mSoundData.readFloat();
  367. if (left > maxLeft) maxLeft = left;
  368. if (left < minLeft) minLeft = left;
  369. var right:Number = mSoundData.readFloat();
  370. if (right > maxRight) maxRight = right;
  371. if (right < minRight) minRight = right;
  372. }
  373. minLeft *= yr;
  374. minRight *= yr;
  375. maxLeft *= yr;
  376. maxRight *= yr;
  377. waveform.graphics.moveTo(xp, minLeft);
  378. waveform.graphics.lineTo(xp, maxLeft);
  379. xp++;
  380. waveform.graphics.moveTo(xp, minRight);
  381. waveform.graphics.lineTo(xp, maxRight);
  382. xp++;
  383. }
  384. waveform.cacheAsBitmap = true;
  385. mWaveHolder.graphics.beginFill(0x0);
  386. mWaveHolder.graphics.drawRect(0, -waveform.height * .5, waveform.width, waveform.height);
  387. mWaveHolder.graphics.endFill();
  388. mWaveHolder.cacheAsBitmap = true;
  389. mWaveHolder.addChild(waveform);
  390. }
  391. private function extractSamples():void
  392. {
  393. mNumSamples = Math.floor(mSoundData.length / 4);
  394. mSamples = new Vector.<Number>(mNumSamples, true);
  395. mSoundData.position = 0;
  396. for (var i:int = 0; i < mNumSamples; i++)
  397. {
  398. mSamples[i] = Number(mSoundData.readFloat());
  399. }
  400. }
  401. public function slide(event:Event):void
  402. {
  403. mWaveHolder.x += mVelX;
  404. if (mWaveHolder.x < mFarLeft) mVelX *= -1;
  405. if (mWaveHolder.x > mFarRight) mVelX *= -1;
  406. setSpeed();
  407. mVelX *= DRAG;
  408. // scratch is complete
  409. if (Math.abs(mVelX) <= .025)
  410. {
  411. removeEventListener(Event.ENTER_FRAME, slide);
  412. completeScratch();
  413. }
  414. }
  415. private function completeScratch():void
  416. {
  417. mRatio = (mWaveHolder.x - mCenterX) / mWaveHolder.width;
  418. mPosition = -mRatio * mNumSamples;
  419. mPosition = clamp(mPosition, 0, mNumSamples - 1);
  420. mSpeed = 2.0;
  421. mIsScratching = false;
  422. }
  423. private function setSpeed():void
  424. {
  425. mTargetSpeed = mVelX * SPEED_MULT;
  426. mSpeed += (mTargetSpeed - mSpeed) / SPEED_EASE;
  427. mSpeed *= -1;
  428. }
  429. }