Music.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. namespace EE{
  4. /******************************************************************************
  5. Music currently needs locking 'SongsLock', because 'playSong' can be called on both Sound Thread and by the user.
  6. /******************************************************************************/
  7. enum
  8. {
  9. OLD,
  10. CUR,
  11. };
  12. /******************************************************************************/
  13. struct Song
  14. {
  15. Str name;
  16. Flt time;
  17. Song() {time=0;}
  18. };
  19. static Memb<Song> Songs; // 'Memb' is needed so that elements don't change memory addresses (currently needed for 'SongName')
  20. static SyncLock SongsLock;
  21. /******************************************************************************/
  22. MusicManager Music (VOLUME_MUSIC ),
  23. Ambient(VOLUME_AMBIENT);
  24. /******************************************************************************/
  25. // functions below assume lock
  26. static Bool LockedSongID (Int global_song, UID &id) {return InRange(global_song, Songs) && DecodeFileName(Songs[global_song].name, id);} // !! does not zero 'id' on fail !!
  27. static C Str& LockedSongName(Int global_song ) {return InRange(global_song, Songs) ? Songs[global_song].name : S;}
  28. static Int LockedSongFind(CChar *name ) {if(Is(name))FREPA(Songs)if(EqualPath(name, Songs[i].name))return i; return -1;}
  29. static Int LockedSongGet (CChar *name )
  30. {
  31. if(Is(name))
  32. {
  33. Int i=LockedSongFind(name); if(i>=0)return i; // find existing
  34. Songs.New().name=name; return Songs.elms()-1; // add new one
  35. }
  36. return -1;
  37. }
  38. static Int LockedSongGet(C UID &id) {return id.valid() ? LockedSongGet(_EncodeFileName(id)) : -1;}
  39. static C Str& SongName(Int global_song) // !! since 'Songs' is 'Memb' and never deleted, we can just return a reference !!
  40. {
  41. if(InRange(global_song, Songs))
  42. {
  43. SyncLocker locker(SongsLock); return LockedSongName(global_song);
  44. }
  45. return S;
  46. }
  47. static Int SongGet(CChar *name) {if(Is(name) ){SyncLocker locker(SongsLock); return LockedSongGet(name);} return -1;}
  48. static Int SongGet(C UID &id ) {if(id.valid()){SyncLocker locker(SongsLock); return LockedSongGet(id );} return -1;}
  49. /******************************************************************************/
  50. // PLAYLIST
  51. /******************************************************************************/
  52. Playlist::~Playlist()
  53. {
  54. if(Music .playlist()==this)Music .set(null);
  55. if(Ambient.playlist()==this)Ambient.set(null);
  56. }
  57. Playlist::Playlist()
  58. {
  59. _cur=-1;
  60. }
  61. Str Playlist::song(Int i)C
  62. {
  63. if(InRange(i, _songs))
  64. {
  65. SyncLocker locker(SongsLock); // needed for both '_songs' and 'LockedSongName'
  66. if(InRange(i, _songs))return LockedSongName(_songs[i]);
  67. }
  68. return S;
  69. }
  70. UID Playlist::songID(Int i)C
  71. {
  72. if(InRange(i, _songs))
  73. {
  74. SyncLocker locker(SongsLock); // needed for both '_songs' and 'LockedSongName'
  75. UID id; if(InRange(i, _songs) && LockedSongID(_songs[i], id))return id;
  76. }
  77. return UIDZero;
  78. }
  79. void Playlist::operator+=(C Str &name)
  80. {
  81. if(name.is())
  82. {
  83. SyncLocker locker(SongsLock); // needed for both '_songs' and 'LockedSongGet'
  84. _songs.include(LockedSongGet(name));
  85. }
  86. }
  87. void Playlist::operator-=(C Str &name)
  88. {
  89. if(name.is())
  90. {
  91. SyncLocker locker(SongsLock); // needed for both '_songs' and 'LockedSongFind'
  92. Int song=LockedSongFind(name); if(song>=0)REPA(_songs)if(_songs[i]==song)
  93. {
  94. _songs.remove(i, true);
  95. if(_cur==i)_cur=-1;else
  96. if(_cur> i)_cur-- ;
  97. break;
  98. }
  99. }
  100. }
  101. void Playlist::operator+=(C UID &id) {if(id.valid())T+=_EncodeFileName(id);}
  102. void Playlist::operator-=(C UID &id) {if(id.valid())T-=_EncodeFileName(id);}
  103. Int Playlist::globalSong(Int song, Bool shuffle, Randomizer &random)
  104. {
  105. // !! in the first part we don't access '_songs' so we don't need to lock !!
  106. if(!InRange(song, _songs)) // if not in range
  107. {
  108. song=_cur; // pick previous
  109. if(!InRange(song, _songs)) // if still not in range
  110. {
  111. if(_songs.elms()==0)song=-1; // no songs available
  112. else song=(shuffle ? random(_songs.elms()) : 0); // set random or first
  113. }
  114. }
  115. _cur=song;
  116. // !! now we have to lock !!
  117. if(InRange(song, _songs))
  118. {
  119. SyncLocker locker(SongsLock); // needed for '_songs'
  120. if(InRange(song, _songs))return _songs[song];
  121. }
  122. return -1;
  123. }
  124. Int Playlist::nextSong(Bool shuffle, Randomizer &random)
  125. {
  126. // !! in the first part we don't access '_songs' so we don't need to lock !!
  127. Int song;
  128. if(_songs.elms()==0)song=-1;else // no songs available
  129. if(_songs.elms()==1)song= 0;else // only one song available
  130. if(_cur < 0)song=(shuffle ? random(_songs.elms()) : 0);else // there was no previous so use random or first
  131. {
  132. if(shuffle)
  133. {
  134. #if 1 // works with 'prevSong'
  135. REP(16) // 16 attempts to find a next different song
  136. {
  137. song=random(_songs.elms());
  138. if(song!=_cur)break; // found a different one then stop looking
  139. }
  140. #else // simpler but doesn't work with 'prevSong'
  141. song=random(_songs.elms()-1);
  142. if(song==_cur)song=_songs.elms()-1; // if selected the same song again then switch to last one
  143. #endif
  144. }else
  145. {
  146. song=(_cur+1)%_songs.elms(); // get next in order
  147. }
  148. }
  149. _cur=song;
  150. // !! now we have to lock !!
  151. if(InRange(song, _songs))
  152. {
  153. SyncLocker locker(SongsLock); // needed for '_songs'
  154. if(InRange(song, _songs))return _songs[song];
  155. }
  156. return -1;
  157. }
  158. Int Playlist::prevSong(Bool shuffle, Randomizer &random)
  159. {
  160. // !! in the first part we don't access '_songs' so we don't need to lock !!
  161. Int song;
  162. if(_songs.elms()==0)song=-1;else // no songs available
  163. if(_songs.elms()==1)song= 0;else // only one song available
  164. if(_cur < 0)song=(shuffle ? random.back().back()(_songs.elms()) : 0);else // there was no previous so use random or first
  165. {
  166. if(shuffle)
  167. {
  168. #if 1 // works with 'nextSong'
  169. REP(16) // 16 attempts to find a previous different song
  170. {
  171. song=random.back().back()(_songs.elms());
  172. if(song!=_cur)break; // found a different one then stop looking
  173. }
  174. #else // simpler but doesn't work with 'nextSong'
  175. song=random.back().back()(_songs.elms()-1);
  176. if(song==_cur)song=_songs.elms()-1; // if selected the same song again then switch to last one
  177. #endif
  178. }else
  179. {
  180. song=(_cur+_songs.elms()-1)%_songs.elms(); // get previous in order
  181. }
  182. }
  183. _cur=song;
  184. // !! now we have to lock !!
  185. if(InRange(song, _songs))
  186. {
  187. SyncLocker locker(SongsLock); // needed for '_songs'
  188. if(InRange(song, _songs))return _songs[song];
  189. }
  190. return -1;
  191. }
  192. /******************************************************************************/
  193. // MUSIC MANAGER
  194. /******************************************************************************/
  195. MusicManager::MusicManager(VOLUME_GROUP volume_group)
  196. {
  197. shuffle =true;
  198. fade_curve=FADE_LINEAR;
  199. fade_in =0.5f;
  200. fade_out = 3;
  201. time_reset= 10;
  202. select_song=null;
  203. T._song[0]=-1;
  204. T._song[1]=-1;
  205. T._volume_group=volume_group;
  206. T._playlist =null;
  207. T._callback =null;
  208. T._history_max=T._history_pos=0;
  209. T._random.randomize();
  210. }
  211. /******************************************************************************/
  212. void MusicManager::del()
  213. {
  214. _sound[0].del();
  215. _sound[1].del();
  216. _song[0]=-1;
  217. _song[1]=-1;
  218. _playlist=null;
  219. select_song=null;
  220. }
  221. /******************************************************************************/
  222. Flt MusicManager::lockedTimeLeft()C {return _sound[CUR].timeLeft();} // !! assumes lock !!
  223. // use locks in case '_sound' gets modified on Sound thread inside 'MusicManager.lockedUpdate'
  224. Str MusicManager::name ( )C {SyncLocker locker(SongsLock); return _sound[CUR].name ();}
  225. UID MusicManager::id ( )C {SyncLocker locker(SongsLock); return _sound[CUR].id ();}
  226. Flt MusicManager::length( )C {SyncLocker locker(SongsLock); return _sound[CUR].length();}
  227. Flt MusicManager::time ( )C {SyncLocker locker(SongsLock); return _sound[CUR].time ();}
  228. Flt MusicManager::fade ( )C {SyncLocker locker(SongsLock); return _sound[CUR].fade ();}
  229. Flt MusicManager::frac ( )C {SyncLocker locker(SongsLock); return _sound[CUR].frac ();}
  230. void MusicManager::time (Flt time) {SyncLocker locker(SongsLock); _sound[CUR].time (time);}
  231. void MusicManager::frac (Flt frac) {SyncLocker locker(SongsLock); _sound[CUR].frac (frac);}
  232. /******************************************************************************/
  233. void MusicManager::set(Playlist *playlist)
  234. {
  235. T._playlist=playlist;
  236. if(_song[CUR]<0)play(playlist); // start playing if nothing playing right now
  237. }
  238. void MusicManager::play(Playlist *playlist, Int song)
  239. {
  240. if(playlist!=T._playlist || song>=0) // if different playlist or a song was specified
  241. {
  242. T._playlist=playlist;
  243. playSong(playlist ? playlist->globalSong(song, shuffle, _random) : -1);
  244. }
  245. }
  246. /******************************************************************************/
  247. void MusicManager::play(C UID &song_id ) {playSong(SongGet(song_id ));}
  248. void MusicManager::play(C Str &song_name) {playSong(SongGet(song_name));}
  249. void MusicManager::stop( ) {playSong(-1);}
  250. /******************************************************************************/
  251. // !! methods below assume lock !!
  252. void MusicManager::swap() // !! assumes lock !!
  253. {
  254. Swap(_sound[0], _sound[1]);
  255. Swap(_song [0], _song [1]);
  256. }
  257. void MusicManager::storePos(Bool i) // !! assumes lock !!
  258. {
  259. Int global_song=T._song[i]; if(InRange(global_song, Songs))
  260. {
  261. Flt time=T._sound[i].time();
  262. {
  263. //SyncLocker locker(SongsLock); if(InRange(global_song, Songs)) - no need to check because we assume lock
  264. Songs[global_song].time=time;
  265. }
  266. }
  267. }
  268. void MusicManager::fadeIn (Bool i) {Sound &sound=T._sound[i]; if(sound.playing()){ sound.fadeIn (fade_in );} } // !! assumes lock !!
  269. void MusicManager::fadeOut(Bool i) {Sound &sound=T._sound[i]; if(sound.playing()){storePos(i); sound.fadeOut(fade_out);} } // !! assumes lock !!
  270. void MusicManager::del (Bool i) {Sound &sound=T._sound[i]; if(sound.playing()){storePos(i); sound.del ( );} T._song[i]=-1;} // !! assumes lock !!
  271. void MusicManager::set(Bool i, Int global_song) // !! assumes "InRange(global_song, Songs)" and locked !!
  272. {
  273. T._sound[i].create(Songs[global_song].name, false, 1, _volume_group).fadeCurve(fade_curve).fadeInFromSilence(fade_in);
  274. T._song [i]=global_song;
  275. }
  276. /******************************************************************************/
  277. void MusicManager::playSong(Int global_song)
  278. {
  279. SyncLocker locker(SongsLock); // !! use lock because this method can be called by both Sound and User Thread, this is also needed below for 'Songs' access and various methods !!
  280. // !! setting callbacks should be performed in such a way, that only one sound at a time has the callback !!
  281. if(InRange(global_song, Songs))
  282. {
  283. if(global_song==_song[CUR] && _sound[CUR].playing()) // if the song is already playing on CUR slot
  284. {
  285. Sound &sound=_sound[CUR];
  286. if(sound.timeLeft()<time_reset) // if remaining song time is less than minimum desired
  287. {
  288. del (OLD);
  289. swap ( );
  290. fadeOut (OLD); _sound[OLD].callback(null);
  291. set (CUR, global_song);
  292. _sound[CUR].callback(_callback).play(); // !! don't use 'sound' reference in case 'swap' makes it invalid
  293. }else
  294. {
  295. fadeIn(CUR);
  296. }
  297. }else
  298. if(global_song==_song[OLD] && _sound[OLD].playing()) // if the song is already playing on OLD slot
  299. {
  300. swap ( );
  301. fadeOut(OLD); _sound[OLD].callback(null);
  302. fadeIn (CUR);
  303. Sound &sound=_sound[CUR]; // !! get reference after calling 'swap' in case it would make it invalid
  304. if(sound.timeLeft()<time_reset)sound.time(0); // if remaining song time is less than minimum desired, then start from the beginning
  305. sound.callback(_callback);
  306. }else // song is not on OLD or CUR
  307. {
  308. del (OLD);
  309. swap ( );
  310. fadeOut(OLD); _sound[OLD].callback(null);
  311. set (CUR, global_song);
  312. Sound &sound=_sound[CUR]; // !! get reference after calling 'swap' in case it would make it invalid
  313. Flt time=0;
  314. {
  315. //SyncLocker locker(SongsLock); if(InRange(global_song, Songs)) - lock already called
  316. time=Songs[global_song].time; // play from last position
  317. }
  318. if(sound.length()-time<time_reset)time=0; // if remaining song time is less than minimum desired, then start from the beginning
  319. sound.time(time).callback(_callback).play();
  320. }
  321. if(_history_max)
  322. {
  323. //SyncLocker locker(SongsLock); if(_history_max) - lock already called
  324. if(!InRange(_history_pos-1, _history) || _history[_history_pos-1]!=global_song) // check that previous song is different (if the same then do nothing)
  325. if( InRange(_history_pos , _history) && _history[_history_pos ]==global_song)_history_pos++;else // if next song is the same, then increase position
  326. {
  327. if(_history.elms()>=_history_max) // at the limit
  328. {
  329. if(_history_pos>_history.elms()-_history_pos) // remove undo
  330. {
  331. _history.remove(0, true);
  332. _history_pos--;
  333. //MAX(_history_pos, 0); not needed since we check "_history_pos>" above
  334. }else // remove redo
  335. {
  336. _history.removeLast();
  337. //MIN(_history_pos, _history.elms()); not needed since we check "_history_pos>" above
  338. }
  339. }
  340. _history.NewAt(_history_pos++)=global_song; // don't remove redos, but just insert new history between undos/redos
  341. }
  342. }
  343. }else
  344. {
  345. fadeOut(OLD);
  346. fadeOut(CUR);
  347. }
  348. }
  349. /******************************************************************************/
  350. void MusicManager::next()
  351. {
  352. auto select=T.select_song; // copy first to temp var to avoid multi-threading issues
  353. if(InRange(_history_pos, _history))
  354. {
  355. SyncLocker locker(SongsLock);
  356. if(InRange(_history_pos, _history)) // check again after having lock
  357. {
  358. Int song=_history[_history_pos];
  359. if(select)
  360. {
  361. UID next; if(!LockedSongID(song, next))next.zero();
  362. song=LockedSongGet(select(next));
  363. }
  364. playSong(song);
  365. return;
  366. }
  367. }
  368. if( select )play (select(UIDZero));else
  369. if(Playlist *playlist=T.playlist())playSong(playlist->nextSong(shuffle, _random)); // copy first to temp var to avoid multi-threading issues
  370. else playSong(-1);
  371. }
  372. void MusicManager::prev()
  373. {
  374. if(_history_max) // want to use history
  375. {
  376. if(_history_pos) // have previous
  377. {
  378. SyncLocker locker(SongsLock);
  379. if(_history_pos) // check again after having lock
  380. {
  381. Int song=_history[_history_pos-1]; // get last played song
  382. if( song!=_song[CUR] || !_sound[CUR].playing())playSong( song );else // if it's different than what playing now, or it stopped playing, then play it but don't change history position
  383. if(_history_pos>1 )playSong(_history[--_history_pos-1]); // play the one before that and change history position !! it's important to decrease '_history_pos' before calling 'playSong' so it can detect that previous song is the same !!
  384. }
  385. }
  386. // if want history, but history not available, then do nothing
  387. }else
  388. if(Playlist *playlist=T.playlist())playSong(playlist->prevSong(shuffle, _random)); // copy first to temp var to avoid multi-threading issues
  389. //else playSong(-1); do nothing
  390. }
  391. void MusicManager::lockedUpdate()
  392. {
  393. if(_song[CUR]>=0 && lockedTimeLeft()<=fade_out)next();
  394. }
  395. /******************************************************************************/
  396. void MusicManager::maxHistory(Int max_history)
  397. {
  398. MAX(max_history, 0);
  399. if( max_history!=_history_max)
  400. {
  401. SyncLocker locker(SongsLock); // use lock to avoid multi-thread issues
  402. Int prevs=_history_pos , nexts=_history.elms()-_history_pos, history_2=max_history/2,
  403. new_prevs=Min(prevs, history_2), new_nexts=Min(nexts, history_2), left=max_history-new_prevs-new_nexts;
  404. Int add_prevs=Min(prevs-new_prevs, left); if(add_prevs>0){new_prevs+=add_prevs; left-=add_prevs;}else
  405. {
  406. Int add_nexts=Min(nexts-new_nexts, left); if(add_nexts>0){new_nexts+=add_nexts; left-=add_nexts;}
  407. }
  408. // first remove last nexts if any
  409. _history.setNum(prevs+new_nexts);
  410. // now remove first prevs if any
  411. Int remove_prevs=prevs-new_prevs;
  412. _history.removeNum(0, remove_prevs, true);
  413. _history_pos-=remove_prevs;
  414. _history_max =max_history;
  415. }
  416. }
  417. /******************************************************************************/
  418. MusicManager& MusicManager::callback(SoundDataCallback *callback)
  419. {
  420. if(T._callback!=callback)
  421. {
  422. SyncLocker locker(SongsLock); // use lock to avoid issues when 'playSong' swaps sounds
  423. T._callback=callback;
  424. _sound[CUR].callback(callback);
  425. }
  426. return T;
  427. }
  428. /******************************************************************************/
  429. // MAIN
  430. /******************************************************************************/
  431. void ShutMusic()
  432. {
  433. SyncLocker locker(SongsLock); // lock just in case
  434. // delete Music and Ambient first
  435. Music .del();
  436. Ambient.del();
  437. // now delete Songs
  438. Songs.del();
  439. }
  440. void UpdateMusic()
  441. {
  442. SyncLocker locker(SongsLock); // lock needed for 'lockedUpdate'
  443. Music .lockedUpdate();
  444. Ambient.lockedUpdate();
  445. }
  446. /******************************************************************************/
  447. }
  448. /******************************************************************************/