Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
  3.  * It is copyright by its individual contributors, as recorded in the
  4.  * project's Git history.  See COPYING.txt at the top level for license
  5.  * terms and a link to the Git history.
  6.  */
  7. /*
  8.  *
  9.  * CD Audio functions for Win32
  10.  *
  11.  *
  12.  */
  13.  
  14. #include <algorithm>
  15. #include <stdio.h>
  16. #include <stdlib.h>
  17. #include <windows.h>
  18. #include <mmsystem.h>
  19. #include <climits>
  20.  
  21. #include "pstypes.h"
  22. #include "dxxerror.h"
  23. #include "args.h"
  24. #include "rbaudio.h"
  25. #include "console.h"
  26. #include "fwd-gr.h"
  27. #include "timer.h"
  28.  
  29. #if SDL_MAJOR_VERSION == 1
  30. // we don't want this on SDL1 because there we have common/arch/sdl/rbaudio.cpp
  31. // #pragma message("Skipping win32/rbaudio because of SDL1")
  32.  
  33. #elif SDL_MAJOR_VERSION == 2
  34.  
  35. namespace dcx {
  36. #define CD_FPS 75
  37.  
  38. static UINT wCDDeviceID = 0U;
  39. static bool initialised;
  40. static DWORD playEnd;
  41. static DWORD lastFrames;
  42. static bool isPaused;
  43.  
  44. void RBAExit()
  45. {
  46.         if (wCDDeviceID)
  47.         {
  48.                 initialised = false;
  49.                 mciSendCommand(wCDDeviceID, MCI_CLOSE, MCI_WAIT, 0);
  50.                 wCDDeviceID = 0U;
  51.         }
  52. }
  53.  
  54. static bool mci_HasMedia()
  55. {
  56.         if (!wCDDeviceID) return false;
  57.  
  58.         MCIERROR mciError;
  59.         MCI_STATUS_PARMS mciStatusParms;
  60.  
  61.         mciStatusParms.dwItem = MCI_STATUS_MEDIA_PRESENT;
  62.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
  63.         {
  64.                 Warning("RBAudio win32/MCI: cannot determine MCI media status (%lx)", mciError);
  65.                 RBAExit();
  66.                 return false;
  67.         }
  68.         return mciStatusParms.dwReturn != 0;
  69. }
  70.  
  71. static unsigned mci_TotalFramesMsf(const DWORD msf)
  72. {
  73.         return (MCI_MSF_MINUTE(msf) * 60 + MCI_MSF_SECOND(msf)) * CD_FPS + MCI_MSF_FRAME(msf);
  74. }
  75.  
  76. static unsigned mci_FramesToMsf(const int frames)
  77. {
  78.         int m = frames / (CD_FPS * 60);
  79.         int s = (frames / CD_FPS) % 60;
  80.         int f = frames % CD_FPS;
  81.         return MCI_MAKE_MSF(m, s, f);
  82. }
  83.  
  84. static unsigned mci_GetTrackOffset(const int track)
  85. {
  86.         MCIERROR mciError;
  87.         MCI_STATUS_PARMS mciStatusParms;
  88.  
  89.         mciStatusParms.dwItem = MCI_STATUS_POSITION;
  90.         mciStatusParms.dwTrack = track;
  91.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
  92.         {
  93.                 Warning("RBAudio win32/MCI: cannot determine track %i offset (%lx)", track, mciError);
  94.                 return -1;
  95.         }
  96.         // dwReturn is a 32-bit value in MSF format, so DWORD_PTR > DWORD is not a problem
  97.         return mci_TotalFramesMsf(mciStatusParms.dwReturn);
  98. }
  99.  
  100. static unsigned mci_GetTrackLength(const int track)
  101. {
  102.         MCIERROR mciError;
  103.         MCI_STATUS_PARMS mciStatusParms;
  104.  
  105.         mciStatusParms.dwItem = MCI_STATUS_LENGTH;
  106.         mciStatusParms.dwTrack = track;
  107.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
  108.         {
  109.                 Warning("RBAudio win32/MCI: cannot determine track %i length (%lx)", track, mciError);
  110.                 return -1;
  111.         }
  112.         // dwReturn is a 32-bit value in MSF format, so DWORD_PTR > DWORD is not a problem
  113.         return mci_TotalFramesMsf(mciStatusParms.dwReturn);
  114. }
  115.  
  116. static unsigned mci_GetTotalLength()
  117. {
  118.         MCIERROR mciError;
  119.         MCI_STATUS_PARMS mciStatusParms;
  120.  
  121.         mciStatusParms.dwItem = MCI_STATUS_LENGTH;
  122.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
  123.         {
  124.                 Warning("RBAudio win32/MCI: cannot determine media length (%lx)", mciError);
  125.                 return -1;
  126.         }
  127.         return mci_TotalFramesMsf(mciStatusParms.dwReturn);
  128. }
  129.  
  130. void RBAInit()
  131. {
  132.         MCIERROR mciError;
  133.         MCI_OPEN_PARMS mciOpenParms;
  134.         MCI_SET_PARMS mciSetParms;
  135.  
  136.         if (initialised) return;
  137.        
  138.         mciOpenParms.lpstrDeviceType = "cdaudio";
  139.         if ((mciError = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_SHAREABLE, reinterpret_cast<DWORD_PTR>(&mciOpenParms))))
  140.         {
  141.                 con_puts(CON_NORMAL, "RBAudio win32/MCI: cannot find MCI cdaudio (no CD drive?)");
  142.                 return;
  143.         }
  144.  
  145.         wCDDeviceID = mciOpenParms.wDeviceID;
  146.  
  147.         if (!mci_HasMedia())
  148.         {
  149.                 con_puts(CON_NORMAL, "RBAudio win32/MCI: no media in CD drive.");
  150.                 RBAExit();
  151.                 return;
  152.         }
  153.  
  154.         mciSetParms.dwTimeFormat = MCI_FORMAT_MSF;
  155.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, reinterpret_cast<DWORD_PTR>(&mciSetParms))))
  156.         {
  157.                 Warning("RBAudio win32/MCI: cannot set time format for CD to MSF (strange)");
  158.                 RBAExit();
  159.                 return;
  160.         }
  161.  
  162.         initialised = true;
  163.         RBAList();
  164. }
  165.  
  166. int RBAEnabled()
  167. {
  168.         return initialised;
  169. }
  170.  
  171. static void (*redbook_finished_hook)() = nullptr;
  172.  
  173. int RBAPlayTrack(int a)
  174. {
  175.         if (!wCDDeviceID)
  176.                 return 0;
  177.  
  178.         if (mci_HasMedia())
  179.         {
  180.                 MCIERROR mciError;
  181.                 MCI_PLAY_PARMS mciPlayParms;
  182.                 DWORD playStart;
  183.  
  184.                 con_printf(CON_VERBOSE, "RBAudio win32/MCI: Playing track %i", a);
  185.  
  186.                 playStart = mci_GetTrackOffset(a);
  187.                 playEnd = playStart + mci_GetTrackLength(a);
  188.  
  189.                 mciPlayParms.dwFrom = mci_FramesToMsf(playStart);
  190.                 mciPlayParms.dwTo = mci_FramesToMsf(playEnd);
  191.  
  192.                 if ((mciError = mciSendCommand(wCDDeviceID, MCI_PLAY, MCI_FROM | MCI_TO, reinterpret_cast<DWORD_PTR>(&mciPlayParms))))
  193.                 {
  194.                         Warning("RBAudio win32/MCI: could not play track (%lx)", mciError);
  195.                         return 0;
  196.                 }
  197.  
  198.                 return 1;
  199.         }
  200.         return 0;
  201. }
  202.  
  203. // plays tracks first through last, inclusive
  204. int RBAPlayTracks(int first, int last, void (*hook_finished)(void))
  205. {
  206.         if (!wCDDeviceID)
  207.                 return 0;
  208.  
  209.         if (mci_HasMedia())
  210.         {
  211.                 MCIERROR mciError;
  212.                 MCI_PLAY_PARMS mciPlayParms;
  213.  
  214.                 redbook_finished_hook = hook_finished;
  215.  
  216.                 con_printf(CON_VERBOSE, "RBAudio win32/MCI: Playing tracks %i to %i", first, last);
  217.  
  218.                 playEnd = mci_GetTrackOffset(last) + mci_GetTrackLength(last);
  219.  
  220.                 mciPlayParms.dwFrom = mci_FramesToMsf(mci_GetTrackOffset(first));
  221.                 mciPlayParms.dwTo = mci_FramesToMsf(playEnd);
  222.  
  223.                 if ((mciError = mciSendCommand(wCDDeviceID, MCI_PLAY, MCI_FROM | MCI_TO, reinterpret_cast<DWORD_PTR>(&mciPlayParms))))
  224.                 {
  225.                         Warning("RBAudio win32/MCI: could not play tracks (%lx)", mciError);
  226.                         return 0;
  227.                 }
  228.  
  229.                 return 1;
  230.         }
  231.         return 0;
  232. }
  233.  
  234. void RBAStop()
  235. {
  236.         if (!wCDDeviceID) return;
  237.        
  238.         MCIERROR mciError;
  239.  
  240.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_STOP, 0, 0)))
  241.         {
  242.                 Warning("RBAudio win32/MCI: could not stop music (%lx)", mciError);
  243.         }
  244.         redbook_finished_hook = nullptr;
  245. }
  246.  
  247. void RBAEjectDisk()
  248. {
  249.         if (!wCDDeviceID) return;
  250.        
  251.         MCIERROR mciError;
  252.  
  253.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_SET, MCI_SET_DOOR_OPEN | MCI_WAIT, 0)))
  254.         {
  255.                 Warning("RBAudio win32/MCI: could not open CD tray (%lx)", mciError);
  256.         }
  257.         initialised = false;
  258. }
  259.  
  260. void RBASetVolume(int)
  261. {
  262.         // MCI does not support this
  263. }
  264.  
  265. void RBAPause()
  266. {
  267.         if (!wCDDeviceID) return;
  268.        
  269.         MCIERROR mciError;
  270.  
  271.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_PAUSE, 0, 0)))
  272.         {
  273.                 Warning("RBAudio win32/MCI: could not pause music (%lx)", mciError);
  274.                 return;
  275.         }
  276.         con_puts(CON_VERBOSE, "RBAudio win32/MCI: Playback paused");
  277.         isPaused = true;
  278. }
  279.  
  280. int RBAResume()
  281. {
  282.         if (!wCDDeviceID) return -1;
  283.        
  284.         MCIERROR mciError;
  285.  
  286.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_RESUME, 0, 0)))
  287.         {
  288.                 Warning("RBAudio win32/MCI: could not resume music (%lx)", mciError);
  289.                 return -1;
  290.         }
  291.         con_puts(CON_VERBOSE, "RBAudio win32/MCI: Playback resumed");
  292.         isPaused = false;
  293.         return 1;
  294. }
  295.  
  296. int RBAPauseResume()
  297. {
  298.         if (!wCDDeviceID) return 0;
  299.        
  300.         MCIERROR mciError;
  301.         MCI_STATUS_PARMS mciStatusParms;
  302.  
  303.         mciStatusParms.dwItem = MCI_STATUS_MODE;
  304.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
  305.         {
  306.                 Warning("RBAudio win32/MCI: cannot determine MCI media status (%lx)", mciError);
  307.                 return 0;
  308.         }
  309.  
  310.         if (mciStatusParms.dwReturn == MCI_MODE_PLAY)
  311.         {
  312.                 con_puts(CON_VERBOSE, "RBAudio win32/MCI: Toggle Playback pause");
  313.                 RBAPause();
  314.         }
  315.         // MCI may also use MCI_MODE_STOP for pause instead of MCI_MODE_PAUSE
  316.         else if (mciStatusParms.dwReturn == MCI_MODE_PAUSE || (isPaused && mciStatusParms.dwReturn == MCI_MODE_STOP))
  317.         {
  318.                 con_puts(CON_VERBOSE, "RBAudio win32/MCI: Toggle Playback resume");
  319.                 return RBAResume() > 0;
  320.         }
  321.         else
  322.                 return 0;
  323.  
  324.         return 1;
  325. }
  326.  
  327. int RBAGetNumberOfTracks()
  328. {
  329.         if (!wCDDeviceID) return -1;
  330.        
  331.         MCIERROR mciError;
  332.         MCI_STATUS_PARMS mciStatusParms;
  333.  
  334.         mciStatusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
  335.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
  336.         {
  337.                 Warning("RBAudio win32/MCI: could not get track count (%lx)", mciError);
  338.                 return -1;
  339.         }
  340.  
  341.         return static_cast<int>(mciStatusParms.dwReturn);
  342. }
  343.  
  344. // check if we need to call the 'finished' hook
  345. // needs to go in all event loops
  346. // MCI has a hook function via Win32 messages, but it doesn't work for CDs
  347. //   for whatever reason
  348. void RBACheckFinishedHook()
  349. {
  350.         static fix64 last_check_time = 0;
  351.        
  352.         if (!wCDDeviceID) return;
  353.  
  354.         if ((timer_query() - last_check_time) >= F2_0)
  355.         {
  356.                 MCIERROR mciError;
  357.                 MCI_STATUS_PARMS mciStatusParms;
  358.  
  359.                 mciStatusParms.dwItem = MCI_STATUS_POSITION;
  360.                 if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
  361.                 {
  362.                         Warning("RBAudio win32/MCI: cannot determine MCI position (%lx)", mciError);
  363.                         return;
  364.                 }
  365.  
  366.                 // time for a hack. for some reason, MCI sometimes stops
  367.                 // from around a few to few dozen frames before it should,
  368.                 // so we will check if we haven't moved from the last check
  369.                 // and allow a bit of a leeway when checking if so.
  370.  
  371.                 DWORD checkValue = playEnd;
  372.                 // dwReturn is a 32-bit value in MSF format, so DWORD_PTR > DWORD is not a problem
  373.                 DWORD thisFrames = mci_TotalFramesMsf(mciStatusParms.dwReturn);
  374.  
  375.                 if (thisFrames == lastFrames)
  376.                         checkValue = checkValue < 64 ? 0 : checkValue - 64; // prevent underflow
  377.  
  378.                 if (redbook_finished_hook && playEnd > 0 && thisFrames >= checkValue)
  379.                 {
  380.                         con_puts(CON_VERBOSE, "RBAudio win32/MCI: Playback done, calling finished-hook");
  381.                         redbook_finished_hook();
  382.                 }
  383.                 lastFrames = thisFrames;
  384.                 last_check_time = timer_query();
  385.         }
  386. }
  387.  
  388. // return the track number currently playing.  Useful if RBAPlayTracks()
  389. // is called.  Returns 0 if no track playing, else track number
  390. int RBAGetTrackNum()
  391. {
  392.         if (!wCDDeviceID) return 0;
  393.        
  394.         MCIERROR mciError;
  395.         MCI_STATUS_PARMS mciStatusParms;
  396.  
  397.         mciStatusParms.dwItem = MCI_STATUS_MODE;
  398.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
  399.         {
  400.                 Warning("RBAudio win32/MCI: cannot determine MCI media status (%lx)", mciError);
  401.                 return 0;
  402.         }
  403.  
  404.         if (mciStatusParms.dwReturn != MCI_MODE_PLAY)
  405.                 return 0;
  406.  
  407.         mciStatusParms.dwItem = MCI_STATUS_CURRENT_TRACK;
  408.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
  409.         {
  410.                 Warning("RBAudio win32/MCI: cannot determine MCI track number (%lx)", mciError);
  411.                 return 0;
  412.         }
  413.  
  414.         return static_cast<int>(mciStatusParms.dwReturn);
  415. }
  416.  
  417. int RBAPeekPlayStatus()
  418. {
  419.         if (!wCDDeviceID) return 0;
  420.        
  421.         MCIERROR mciError;
  422.         MCI_STATUS_PARMS mciStatusParms;
  423.  
  424.         mciStatusParms.dwItem = MCI_STATUS_MODE;
  425.         if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
  426.         {
  427.                 Warning("RBAudio win32/MCI: cannot determine MCI media status (%lx)", mciError);
  428.                 return 0;
  429.         }
  430.  
  431.         if (mciStatusParms.dwReturn == MCI_MODE_PLAY)
  432.                 return 1;
  433.         // MCI may also use MCI_MODE_STOP for pause instead of MCI_MODE_PAUSE
  434.         else if (mciStatusParms.dwReturn == MCI_MODE_PAUSE || (isPaused && mciStatusParms.dwReturn == MCI_MODE_STOP))
  435.                 return -1; // hack so it doesn't keep restarting paused music; -1 is still truthy, unlike 0
  436.         else
  437.                 return 0;
  438. }
  439.  
  440. static int cddb_sum(int n)
  441. {
  442.         int ret;
  443.  
  444.         /* For backward compatibility this algorithm must not change */
  445.  
  446.         ret = 0;
  447.  
  448.         while (n > 0) {
  449.                 ret = ret + (n % 10);
  450.                 n = n / 10;
  451.         }
  452.  
  453.         return (ret);
  454. }
  455.  
  456. unsigned long RBAGetDiscID()
  457. {
  458.         int i, t = 0, n = 0, trackCount, totalLength;
  459.  
  460.         if (!wCDDeviceID)
  461.                 return 0;
  462.  
  463.         MCIERROR mciError;
  464.         trackCount = RBAGetNumberOfTracks();
  465.         totalLength = mci_GetTotalLength();
  466.         if (totalLength < 0)
  467.         {
  468.                 Warning("RBAudio win32/MCI: cannot determine total length (%lx)", mciError);
  469.                 return 0;
  470.         }
  471.  
  472.         /* For backward compatibility this algorithm must not change */
  473.  
  474.         i = 1;
  475.  
  476.         while (i <= trackCount)
  477.         {
  478.                 int offset = mci_GetTrackOffset(i);
  479.                 if (offset < 0)
  480.                 {
  481.                         Warning("RBAudio win32/MCI: cannot determine track %i offset (%lx)", i, mciError);
  482.                         return 0;
  483.                 }
  484.                 n += cddb_sum(offset / CD_FPS);
  485.                 i++;
  486.         }
  487.  
  488.         t = totalLength / CD_FPS;
  489.  
  490.         return ((n % 0xff) << 24 | t << 8 | trackCount);
  491. }
  492.  
  493. void RBAList(void)
  494. {
  495.         if (!wCDDeviceID) return;
  496.  
  497.         MCIERROR mciError;
  498.         MCI_STATUS_PARMS mciStatusParms;
  499.         int trackCount = RBAGetNumberOfTracks();
  500.  
  501.         for (int i = 1; i <= trackCount; ++i) {
  502.                 int isAudioTrack, length, offset;
  503.                 mciStatusParms.dwTrack = i;
  504.  
  505.                 mciStatusParms.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
  506.                 if ((mciError = mciSendCommand(wCDDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, reinterpret_cast<DWORD_PTR>(&mciStatusParms))))
  507.                 {
  508.                         Warning("RBAudio win32/MCI: cannot determine track %d type (%lx)", i, mciError);
  509.                         continue;
  510.                 }
  511.                 isAudioTrack = mciStatusParms.dwReturn == MCI_CDA_TRACK_AUDIO;
  512.  
  513.                 offset = mci_GetTrackOffset(i);
  514.                 if (offset < 0)
  515.                 {
  516.                         continue;
  517.                 }
  518.  
  519.                 length = mci_GetTrackLength(i);
  520.                 if (length < 0)
  521.                 {
  522.                         continue;
  523.                 }
  524.  
  525.                 con_printf(CON_VERBOSE, "RBAudio win32/MCI: CD track %d, type %s, length %d, offset %d", i, isAudioTrack ? "audio" : "data", length, offset);
  526.         }
  527. }
  528.  
  529. }
  530.  
  531. #endif
  532.