Advertisement
gray_alli28codesrbx

Untitled

Jun 28th, 2020
375
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.49 KB | None | 0 0
  1. // test.js github
  2. (function(ext) {
  3.  
  4. if (typeof Tone !== 'undefined') {
  5. console.log('Tone library is already loaded');
  6. getTokenAndStart();
  7. } else {
  8. $.getScript('https://ericrosenbaum.github.io/spotify-extension/Tone.min.js', getTokenAndStart);
  9. }
  10.  
  11. function getTokenAndStart() {
  12. getAccessToken().then((token) => {
  13. startExtension(token);
  14. });
  15. }
  16.  
  17. function getAccessToken() {
  18. return new Promise((resolve, reject) => {
  19. $.ajax({
  20. url: 'https://u61j2fb017.execute-api.us-east-1.amazonaws.com/prod/get-spotify-token',
  21. success: function (response) {
  22. var token = {};
  23. token.expirationTime = currentTimeSec() + 3600;
  24. token.value = response.token;
  25. resolve(token);
  26. },
  27. error: function (error) {
  28. console.log(error);
  29. reject();
  30. }
  31. });
  32. });
  33. }
  34.  
  35. function refreshAccessTokenIfNeeded(token) {
  36. return new Promise((resolve, reject) => {
  37. if (currentTimeSec() > token.expirationTime) {
  38. getAccessToken().then((newToken) => {
  39. token = newToken;
  40. console.log('token expired, got a new one')
  41. resolve(token);
  42. });
  43. } else {
  44. resolve(token);
  45. }
  46. });
  47. }
  48.  
  49. function currentTimeSec() {
  50. return new Date().getTime() / 1000;
  51. }
  52.  
  53. function startExtension(token) {
  54.  
  55. var spotifyToken = token;
  56.  
  57. // load multiple tracks at once, to make mashups, by loading multiple copies of the extension
  58. // this works by adding a number to the end of the extension name
  59. var extName = 'Spotify';
  60. var extNum = '';
  61. // if the extension has been loaded once, we'll append a number
  62. if (window.ScratchExtensions.getStatus(extName).status == 2) {
  63. // check for additional numbered copies, starting at 2
  64. for (var i=2; i<=8; i++) {
  65. // if this number has not been loaded, use it
  66. if (window.ScratchExtensions.getStatus(extName + i).status != 2) {
  67. extNum = i;
  68. break;
  69. }
  70. }
  71. }
  72. extName += extNum;
  73.  
  74. // player for playing entire track
  75. var player = new Tone.Player().toMaster();
  76.  
  77. // beat players for playing individual beat at a time
  78. var beatPlayers = [];
  79. var releaseDur = 0.01;
  80. for (var i=0; i<4; i++) {
  81. var beatPlayer = new Tone.Player();
  82. var ampEnv = new Tone.AmplitudeEnvelope({
  83. "attack": 0.01,
  84. "decay": 0,
  85. "sustain": 1.0,
  86. "release": releaseDur
  87. }).toMaster();
  88. beatPlayer.connect(ampEnv);
  89. beatPlayer.ampEnv = ampEnv;
  90. beatPlayers.push(beatPlayer);
  91. }
  92. currentBeatPlayerIndex = 0;
  93.  
  94. // gain node
  95. var gain = new Tone.Gain();
  96. Tone.Master.chain(gain);
  97.  
  98. var audioContext = Tone.context;
  99.  
  100. var trackTimingData;
  101. var currentBeatNum = 0;
  102. var beatFlag = false;
  103. var barFlag = false;
  104. var beatTimeouts = [];
  105. var barTimeouts = [];
  106. var trackTimeout;
  107.  
  108. var trackStartTime;
  109.  
  110. var currentTrackDuration = 0;
  111. var trackTempo = 0;
  112. var currentArtistName = 'none';
  113. var currentTrackName = 'none';
  114. var currentAlbumName = 'none';
  115. var numBeats = 0;
  116.  
  117. var prevQuery = '';
  118.  
  119. // Cleanup function when the extension is unloaded
  120. ext._shutdown = function() {};
  121.  
  122. // Status reporting code
  123. // Use this to report missing hardware, plugin or unsupported browser
  124. ext._getStatus = function() {
  125. if (typeof AudioContext !== "undefined") {
  126. return {status: 2, msg: 'Ready'};
  127. } else {
  128. return {status: 1, msg: 'Browser not supported'};
  129. }
  130. };
  131.  
  132. ext.searchAndPlayAndWait = function(query, callback) {
  133. refreshAccessTokenIfNeeded(spotifyToken).then(function(token) {
  134. spotifyToken = token
  135. requestSearch(query).then(
  136. function() {
  137. playTrack();
  138. trackTimeout = window.setTimeout(function() {
  139. callback();
  140. }, (currentTrackDuration) * 1000);
  141. },
  142. function() {
  143. callback();
  144. }
  145. );
  146. });
  147. };
  148.  
  149. ext.searchAndPlay = function(query, callback) {
  150. refreshAccessTokenIfNeeded(spotifyToken).then(function(token) {
  151. spotifyToken = token
  152. requestSearch(query).then(
  153. function() {
  154. playTrack();
  155. callback();
  156. },
  157. function() {
  158. callback();
  159. }
  160. );
  161. });
  162. };
  163.  
  164. function requestSearch(query) {
  165.  
  166. return new Promise(function (resolve, reject) {
  167.  
  168. if (player) {
  169. player.stop();
  170. clearTimeouts();
  171. }
  172.  
  173. if (query == '') {
  174. resolve();
  175. return;
  176. }
  177.  
  178. currentBeatNum = 0;
  179.  
  180. // if we are making the exact same query again, abort
  181. // keeping the same metadata, no need to reload
  182. if (query == prevQuery) {
  183. resolve();
  184. return;
  185. }
  186.  
  187. $.ajax({
  188. url: 'https://api.spotify.com/v1/search',
  189. headers: {
  190. 'Authorization': 'Bearer ' + spotifyToken.value
  191. },
  192. data: {
  193. q: query,
  194. type: 'track'
  195. },
  196. success: function (response) {
  197.  
  198. prevQuery = query;
  199.  
  200. var trackObjects = response['tracks']['items'];
  201.  
  202. // fail if there are no tracks
  203. if (!trackObjects || trackObjects.length === 0) {
  204. resetTrackData();
  205. reject();
  206. return;
  207. }
  208.  
  209. // find the first result without explicit lyrics
  210. var notExplicit = false;
  211. for (var i=0; i<trackObjects.length; i++) {
  212. if (!trackObjects[i].explicit) {
  213. trackObjects = trackObjects.slice(i);
  214. notExplicit = true;
  215. break;
  216. }
  217. }
  218.  
  219. // fail if there were none without explicit lyrics
  220. if (!notExplicit) {
  221. resetTrackData();
  222. console.log('no results without explicit lyrics');
  223. reject();
  224. return;
  225. }
  226.  
  227. keepTryingToGetTimingData(trackObjects, resolve, reject);
  228.  
  229. },
  230. error: function(error) {
  231. // if the error is a 401 (unauthorized), the token probably has expired,
  232. // so request a new one.
  233. if (error.status == 401) {
  234. getAccessToken().then((token) => {
  235. spotifyToken = token;
  236. reject();
  237. });
  238. }
  239. }
  240. });
  241. });
  242. };
  243.  
  244. function keepTryingToGetTimingData(trackObjects, resolve, reject) {
  245. getTrackTimingData(trackObjects[0].preview_url).then(
  246. function() {
  247. // store track name, artist, album
  248. currentArtistName = trackObjects[0].artists[0].name;
  249. currentTrackName = trackObjects[0].name;
  250. currentAlbumName = trackObjects[0].album.name;
  251. resolve();
  252. },
  253. function() {
  254. console.log('no timing data for ' + trackObjects[0].name +', trying next track');
  255. if (trackObjects.length > 1) {
  256. trackObjects = trackObjects.slice(1);
  257. keepTryingToGetTimingData(trackObjects, resolve, reject);
  258. } else {
  259. console.log('no more results');
  260. resetTrackData();
  261. reject();
  262. }
  263. }
  264. );
  265. }
  266.  
  267. function playTrack() {
  268. if (!player.buffer || !player.buffer.loaded || !trackTimingData) {
  269. return;
  270. }
  271. player.start(Tone.now(), 0, currentTrackDuration);
  272. trackStartTime = Tone.now();
  273. setupTimeouts();
  274. }
  275.  
  276. function setupTimeouts() {
  277. // events on each beat
  278. beatTimeouts = [];
  279. for (var i=0; i<numBeats; i++) {
  280. var t = window.setTimeout(function(i) {
  281. beatFlag = true;
  282. currentBeatNum = i;
  283. }, (trackTimingData.beats[i] - 0.1) * 1000, i);
  284. beatTimeouts.push(t);
  285. }
  286.  
  287. // events on each bar
  288. barTimeouts = [];
  289. for (var i=0; i<trackTimingData.downbeats.length; i++) {
  290. if (trackTimingData.downbeats[i] < trackTimingData.beats[numBeats-1]) {
  291. var t = window.setTimeout(function() {
  292. barFlag = true;
  293. }, (trackTimingData.downbeats[i] - 0.1) * 1000);
  294. barTimeouts.push(t);
  295. }
  296. }
  297. }
  298.  
  299. function resetTrackData() {
  300. player = new Tone.Player().toMaster();
  301. currentArtistName = 'none';
  302. currentTrackName = 'none';
  303. currentAlbumName = 'none';
  304. trackTempo = 0;
  305. }
  306.  
  307. // code adapted from spotify
  308. function getTrackTimingData(url) {
  309.  
  310. return new Promise(function (resolve, reject) {
  311.  
  312. if (!url) {
  313. reject();
  314. return;
  315. }
  316.  
  317. function findString(buffer, string) {
  318. for (var i = 0; i < buffer.length - string.length; i++) {
  319. var match = true;
  320. for (var j = 0; j < string.length; j++) {
  321. var c = String.fromCharCode(buffer[i + j]);
  322. if (c !== string[j]) {
  323. match = false;
  324. break;
  325. }
  326. }
  327. if (match) {
  328. return i;
  329. }
  330. }
  331. return -1;
  332. }
  333.  
  334. function getSection(buffer, start, which) {
  335. var sectionCount = 0;
  336. for (var i = start; i < buffer.length; i++) {
  337. if (buffer[i] == 0) {
  338. sectionCount++;
  339. }
  340. if (sectionCount >= which) {
  341. break;
  342. }
  343. }
  344. i++;
  345. var content = '';
  346. while (i < buffer.length) {
  347. if (buffer[i] == 0) {
  348. break;
  349. }
  350. var c = String.fromCharCode(buffer[i]);
  351. content += c;
  352. i++;
  353. }
  354. var js = '';
  355. try {
  356. js = eval('(' + content + ')');
  357. } catch (e) {
  358. js = '';
  359. }
  360. return js;
  361. }
  362.  
  363. function makeRequest(url, resolve, reject) {
  364.  
  365. if (!url) {
  366. reject();
  367. return;
  368. }
  369.  
  370. var request = new XMLHttpRequest();
  371. request.open('GET', url, true);
  372. request.responseType = 'arraybuffer';
  373. request.onload = function() {
  374. var buffer = new Uint8Array(this.response); // this.response == uInt8Array.buffer
  375. var idx = findString(buffer, 'GEOB');
  376.  
  377. trackTimingData = getSection(buffer, idx + 1, 8);
  378.  
  379. if (!trackTimingData) {
  380. reject();
  381. return;
  382. }
  383.  
  384. // estimate the tempo using the average time interval between beats
  385. var sum =0;
  386. for (var i=0; i<trackTimingData.beats.length-1; i++) {
  387. sum += trackTimingData.beats[i+1] - trackTimingData.beats[i];
  388. }
  389. var beatLength = sum / (trackTimingData.beats.length - 1);
  390. trackTempo = 60 / beatLength;
  391.  
  392. // use the loop duration to set the number of beats
  393. for (var i=0; i<trackTimingData.beats.length; i++) {
  394. if (trackTimingData.loop_duration < trackTimingData.beats[i]) {
  395. numBeats = i;
  396. break;
  397. }
  398. }
  399.  
  400. // decode the audio
  401. audioContext.decodeAudioData(request.response, function(buffer) {
  402. player.buffer.set(buffer);
  403. currentTrackDuration = trackTimingData.loop_duration;
  404. for (var i=0; i<beatPlayers.length; i++) {
  405. beatPlayers[i].buffer.set(buffer);
  406. }
  407. resolve();
  408. });
  409. }
  410. request.send();
  411. }
  412.  
  413. makeRequest(url, resolve, reject);
  414.  
  415. });
  416. }
  417.  
  418. ext.trackData = function(dataType) {
  419. switch (dataType) {
  420. case 'track':
  421. return currentTrackName;
  422. case 'artist':
  423. return currentArtistName;
  424. case 'album':
  425. return currentAlbumName;
  426. case 'full':
  427. return currentTrackName + ' by ' + currentArtistName + ' from ' + currentAlbumName;
  428. default:
  429. return '';
  430. }
  431. };
  432.  
  433. ext.artistName = function() {
  434. return currentArtistName;
  435. };
  436.  
  437. ext.albumName = function() {
  438. return currentAlbumName;
  439. };
  440.  
  441. ext.trackTempo = function() {
  442. return trackTempo;
  443. };
  444.  
  445. ext.playNextBeat = function() {
  446. setCurrentBeatNum(currentBeatNum + 1);
  447. playCurrentBeat();
  448. };
  449.  
  450. ext.playBeat = function(num) {
  451. num -= 1; // one-indexing
  452. setCurrentBeatNum(num);
  453. playCurrentBeat();
  454. };
  455.  
  456. ext.playBeatAndWait = function(num, callback) {
  457. num -= 1; // one-indexing
  458. setCurrentBeatNum(num);
  459. playCurrentBeat(callback);
  460. };
  461.  
  462. function setCurrentBeatNum(num) {
  463. num = Math.round(num);
  464. currentBeatNum = num % numBeats;
  465. if (currentBeatNum < 0) {
  466. currentBeatNum += numBeats;
  467. }
  468. }
  469.  
  470. function playCurrentBeat(callback) {
  471. // if the track is playing, stop it
  472. if (player) {
  473. player.stop();
  474. clearTimeouts();
  475. }
  476.  
  477. var startTime = trackTimingData.beats[currentBeatNum];
  478. var duration;
  479. if ((currentBeatNum + 1) < numBeats) {
  480. var endTime = trackTimingData.beats[currentBeatNum+1];
  481. duration = endTime - startTime;
  482. } else {
  483. duration = currentTrackDuration - startTime;
  484. }
  485.  
  486. beatPlayers[currentBeatPlayerIndex].ampEnv.triggerRelease();
  487. beatPlayers[currentBeatPlayerIndex].stop(releaseDur);
  488. currentBeatPlayerIndex++;
  489. currentBeatPlayerIndex %= beatPlayers.length;
  490. beatPlayers[currentBeatPlayerIndex].ampEnv.triggerAttackRelease(duration-releaseDur);
  491. beatPlayers[currentBeatPlayerIndex].start('+0', startTime, duration);
  492.  
  493. beatFlag = true;
  494. if (callback) {
  495. window.setTimeout(function() {
  496. callback();
  497. }, (duration - (1/30)) * 1000);
  498. }
  499. }
  500.  
  501. ext.currentBeat = function() {
  502. return currentBeatNum + 1; // one-indexing
  503. };
  504.  
  505. ext.stopMusic = function() {
  506. player.stop();
  507. clearTimeouts();
  508. };
  509.  
  510. ext._stop = function() {
  511. ext.stopMusic();
  512. };
  513.  
  514. function clearTimeouts() {
  515. clearTimeout(trackTimeout);
  516. for (var i=0; i<beatTimeouts.length; i++) {
  517. clearTimeout(beatTimeouts[i]);
  518. }
  519. for (var i=0; i<barTimeouts.length; i++) {
  520. clearTimeout(barTimeouts[i]);
  521. }
  522. }
  523.  
  524. ext.everyBeat = function() {
  525. if (beatFlag) {
  526. window.setTimeout(function() {
  527. beatFlag = false;
  528. }, 60);
  529. return true;
  530. }
  531. return false;
  532. };
  533.  
  534. ext.everyBar = function() {
  535. if (barFlag) {
  536. window.setTimeout(function() {
  537. barFlag = false;
  538. }, 60);
  539. return true;
  540. }
  541. return false;
  542. };
  543.  
  544. // if you've loaded multiple copies of the extension, include extension number on each block
  545. num = extNum;
  546.  
  547. // Block and block menu descriptions
  548. var descriptor = {
  549. blocks: [
  550. ['w', '♫'+num+' play music like %s', 'searchAndPlay', 'tacos'],
  551. ['w', '♫'+num+' play music like %s until done', 'searchAndPlayAndWait', 'lauryn hill'],
  552. ['r', '♫'+num+' %m.trackData name', 'trackData', 'full'],
  553. [' ', '♫'+num+' play next beat', 'playNextBeat'],
  554. [' ', '♫'+num+' play beat %n', 'playBeat', 4],
  555. ['w', '♫'+num+' play beat %n until done', 'playBeatAndWait', 4],
  556. ['r', '♫'+num+' current beat', 'currentBeat'],
  557. ['h', '♫'+num+' every beat', 'everyBeat'],
  558. ['h', '♫'+num+' every 4 beats', 'everyBar'],
  559. [' ', '♫'+num+' stop the music', 'stopMusic']
  560. ],
  561. menus: {
  562. trackData: ['track', 'artist', 'album', 'full']
  563. },
  564. url: 'https://ericrosenbaum.github.io/spotify-extension/'
  565. };
  566.  
  567. // Register the extension
  568. ScratchExtensions.register(extName, descriptor, ext);
  569. }
  570.  
  571. })({});
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement