You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

335 lines
8.8KB

  1. #define MYVERSION "1.3"
  2. /*
  3. changelog
  4. 2018-01-14 03:04 UTC - kode54
  5. - Updated to version 1.4 SDK
  6. - Version is now 1.3
  7. 2017-02-04 03:54 UTC - kode54
  8. - Add link to about string
  9. - Version is now 1.2
  10. 2012-02-19 19:52 UTC - kode54
  11. - Added abort check to decoder
  12. - Version is now 1.1
  13. 2010-06-20 07:05 UTC - kode54
  14. - Initial release
  15. - Version is now 1.0
  16. */
  17. #include <foobar2000.h>
  18. #include "../helpers/dropdown_helper.h"
  19. #include "../ATLHelpers/ATLHelpersLean.h"
  20. #include "../ATLHelpers/misc.h"
  21. #include "kdmeng.h"
  22. #include "resource.h"
  23. // {C14DBCDB-676E-4348-93DA-1BAB92A98727}
  24. static const GUID guid_cfg_loop_count =
  25. { 0xc14dbcdb, 0x676e, 0x4348, { 0x93, 0xda, 0x1b, 0xab, 0x92, 0xa9, 0x87, 0x27 } };
  26. // {CDBDF0A4-3333-42EB-A445-DD1D423E9AA9}
  27. static const GUID guid_cfg_sample_rate =
  28. { 0xcdbdf0a4, 0x3333, 0x42eb, { 0xa4, 0x45, 0xdd, 0x1d, 0x42, 0x3e, 0x9a, 0xa9 } };
  29. // {B35C089B-3285-48CC-94CF-461B520E5328}
  30. static const GUID guid_cfg_history_rate =
  31. { 0xb35c089b, 0x3285, 0x48cc, { 0x94, 0xcf, 0x46, 0x1b, 0x52, 0xe, 0x53, 0x28 } };
  32. enum
  33. {
  34. default_cfg_loop_count = 2,
  35. default_cfg_sample_rate = 44100
  36. };
  37. static cfg_int cfg_loop_count( guid_cfg_loop_count, default_cfg_loop_count );
  38. static cfg_int cfg_sample_rate( guid_cfg_sample_rate, default_cfg_sample_rate );
  39. class input_kdm : public input_stubs
  40. {
  41. kdmeng * m_player;
  42. t_filestats m_stats;
  43. unsigned srate, loop_count;
  44. long length;
  45. bool first_block, eof;
  46. pfc::array_t< t_int16 > sample_buffer;
  47. public:
  48. input_kdm()
  49. {
  50. m_player = 0;
  51. }
  52. ~input_kdm()
  53. {
  54. delete m_player;
  55. }
  56. void open( service_ptr_t<file> m_file, const char * p_path, t_input_open_reason p_reason, abort_callback & p_abort )
  57. {
  58. if ( p_reason == input_open_info_write ) throw exception_io_data();
  59. input_open_file_helper( m_file, p_path, p_reason, p_abort );
  60. m_stats = m_file->get_stats( p_abort );
  61. srate = cfg_sample_rate;
  62. m_player = new kdmeng( srate, 2, 2 );
  63. length = m_player->load( m_file, p_path, p_abort );
  64. if ( !length ) throw exception_io_data();
  65. }
  66. void get_info( file_info & p_info, abort_callback & p_abort )
  67. {
  68. p_info.info_set( "codec", "KDM" );
  69. p_info.info_set_int( "channels", 2 );
  70. p_info.set_length( (double) length / 1000. );
  71. }
  72. t_filestats get_file_stats( abort_callback & p_abort )
  73. {
  74. return m_stats;
  75. }
  76. void decode_initialize( unsigned p_flags, abort_callback & p_abort )
  77. {
  78. loop_count = cfg_loop_count;
  79. if ( p_flags & input_flag_no_looping && !loop_count ) loop_count++;
  80. m_player->musicoff();
  81. m_player->musicon();
  82. first_block = true;
  83. eof = false;
  84. }
  85. bool decode_run( audio_chunk & p_chunk, abort_callback & p_abort )
  86. {
  87. p_abort.check();
  88. if ( eof ) return false;
  89. unsigned samples_to_do = srate / 120;
  90. sample_buffer.grow_size( samples_to_do * 2 );
  91. long repeatcount = m_player->rendersound( sample_buffer.get_ptr(), samples_to_do * 4 );
  92. p_chunk.set_data_fixedpoint( sample_buffer.get_ptr(), samples_to_do * 4, srate, 2, 16, audio_chunk::channel_config_stereo );
  93. if ( loop_count && repeatcount >= loop_count ) eof = true;
  94. return true;
  95. }
  96. void decode_seek( double p_seconds,abort_callback & p_abort )
  97. {
  98. long seek_ms = audio_math::time_to_samples( p_seconds, 1000 );
  99. m_player->seek( seek_ms );
  100. first_block = true;
  101. }
  102. bool decode_can_seek()
  103. {
  104. return true;
  105. }
  106. bool decode_get_dynamic_info( file_info & p_out, double & p_timestamp_delta )
  107. {
  108. if ( first_block )
  109. {
  110. first_block = false;
  111. p_out.info_set_int( "samplerate", srate );
  112. return true;
  113. }
  114. return false;
  115. }
  116. bool decode_get_dynamic_info_track( file_info & p_out, double & p_timestamp_delta )
  117. {
  118. return false;
  119. }
  120. void decode_on_idle( abort_callback & p_abort ) { }
  121. void retag( const file_info & p_info, abort_callback & p_abort )
  122. {
  123. throw exception_io_data();
  124. }
  125. static bool g_is_our_content_type( const char * p_content_type )
  126. {
  127. return false;
  128. }
  129. static bool g_is_our_path( const char * p_full_path, const char * p_extension )
  130. {
  131. return !stricmp( p_extension, "kdm" );
  132. }
  133. static GUID g_get_guid()
  134. {
  135. static const GUID guid = { 0xeec47436, 0xc23c, 0x48fe,{ 0x94, 0xa0, 0x89, 0x26, 0xe8, 0x20, 0x8d, 0x95 } };
  136. return guid;
  137. }
  138. static const char * g_get_name()
  139. {
  140. return "KDM Decoder";
  141. }
  142. static GUID g_get_preferences_guid()
  143. {
  144. static const GUID guid = { 0x518b5be8, 0x34dd, 0x4adf,{ 0x82, 0x2c, 0xeb, 0x36, 0x8, 0x55, 0x37, 0xf6 } };
  145. return guid;
  146. }
  147. };
  148. static cfg_dropdown_history cfg_history_rate( guid_cfg_history_rate, 16 );
  149. static const int srate_tab[]={8000,11025,16000,22050,24000,32000,44100,48000};
  150. class CMyPreferences : public CDialogImpl<CMyPreferences>, public preferences_page_instance {
  151. public:
  152. //Constructor - invoked by preferences_page_impl helpers - don't do Create() in here, preferences_page_impl does this for us
  153. CMyPreferences(preferences_page_callback::ptr callback) : m_callback(callback) {}
  154. //Note that we don't bother doing anything regarding destruction of our class.
  155. //The host ensures that our dialog is destroyed first, then the last reference to our preferences_page_instance object is released, causing our object to be deleted.
  156. //dialog resource ID
  157. enum {IDD = IDD_CONFIG};
  158. // preferences_page_instance methods (not all of them - get_wnd() is supplied by preferences_page_impl helpers)
  159. t_uint32 get_state();
  160. void apply();
  161. void reset();
  162. //WTL message map
  163. BEGIN_MSG_MAP(CMyPreferences)
  164. MSG_WM_INITDIALOG(OnInitDialog)
  165. COMMAND_HANDLER_EX(IDC_SAMPLERATE, CBN_EDITCHANGE, OnEditChange)
  166. COMMAND_HANDLER_EX(IDC_SAMPLERATE, CBN_SELCHANGE, OnSelectionChange)
  167. DROPDOWN_HISTORY_HANDLER(IDC_SAMPLERATE, cfg_history_rate)
  168. END_MSG_MAP()
  169. private:
  170. BOOL OnInitDialog(CWindow, LPARAM);
  171. void OnEditChange(UINT, int, CWindow);
  172. void OnSelectionChange(UINT, int, CWindow);
  173. bool HasChanged();
  174. void OnChanged();
  175. void enable_vgm_loop_count(BOOL);
  176. const preferences_page_callback::ptr m_callback;
  177. };
  178. BOOL CMyPreferences::OnInitDialog(CWindow, LPARAM) {
  179. CWindow w;
  180. char temp[16];
  181. int n;
  182. for(n=tabsize(srate_tab);n--;)
  183. {
  184. if (srate_tab[n] != cfg_sample_rate)
  185. {
  186. itoa(srate_tab[n], temp, 10);
  187. cfg_history_rate.add_item(temp);
  188. }
  189. }
  190. itoa(cfg_sample_rate, temp, 10);
  191. cfg_history_rate.add_item(temp);
  192. w = GetDlgItem( IDC_SAMPLERATE );
  193. cfg_history_rate.setup_dropdown( w );
  194. ::SendMessage( w, CB_SETCURSEL, 0, 0 );
  195. w = GetDlgItem( IDC_LOOPCOUNT );
  196. uSendMessageText( w, CB_ADDSTRING, 0, "Indefinite" );
  197. for ( n = 1; n <= 10; n++ )
  198. {
  199. itoa( n, temp, 10 );
  200. uSendMessageText( w, CB_ADDSTRING, 0, temp );
  201. }
  202. ::SendMessage( w, CB_SETCURSEL, cfg_loop_count, 0 );
  203. return FALSE;
  204. }
  205. void CMyPreferences::OnEditChange(UINT, int, CWindow) {
  206. OnChanged();
  207. }
  208. void CMyPreferences::OnSelectionChange(UINT, int, CWindow) {
  209. OnChanged();
  210. }
  211. t_uint32 CMyPreferences::get_state() {
  212. t_uint32 state = preferences_state::resettable;
  213. if (HasChanged()) state |= preferences_state::changed;
  214. return state;
  215. }
  216. void CMyPreferences::reset() {
  217. SetDlgItemInt( IDC_SAMPLERATE, default_cfg_sample_rate );
  218. SendDlgItemMessage( IDC_LOOPCOUNT, CB_SETCURSEL, default_cfg_loop_count );
  219. OnChanged();
  220. }
  221. void CMyPreferences::apply() {
  222. char temp[16];
  223. int t = GetDlgItemInt( IDC_SAMPLERATE, NULL, FALSE );
  224. if ( t < 6000 ) t = 6000;
  225. else if ( t > 96000 ) t = 96000;
  226. SetDlgItemInt( IDC_SAMPLERATE, t, FALSE );
  227. itoa( t, temp, 10 );
  228. cfg_history_rate.add_item( temp );
  229. cfg_sample_rate = t;
  230. cfg_loop_count = SendDlgItemMessage( IDC_LOOPCOUNT, CB_GETCURSEL );
  231. OnChanged(); //our dialog content has not changed but the flags have - our currently shown values now match the settings so the apply button can be disabled
  232. }
  233. bool CMyPreferences::HasChanged() {
  234. return GetDlgItemInt( IDC_SAMPLERATE, NULL, FALSE ) != cfg_sample_rate
  235. || SendDlgItemMessage( IDC_LOOPCOUNT, CB_GETCURSEL ) != cfg_loop_count;
  236. }
  237. void CMyPreferences::OnChanged() {
  238. //tell the host that our state has changed to enable/disable the apply button appropriately.
  239. m_callback->on_state_changed();
  240. }
  241. class preferences_page_myimpl : public preferences_page_impl<CMyPreferences> {
  242. // preferences_page_impl<> helper deals with instantiation of our dialog; inherits from preferences_page_v3.
  243. public:
  244. const char * get_name() {return input_kdm::g_get_name();}
  245. GUID get_guid() {return input_kdm::g_get_preferences_guid();}
  246. GUID get_parent_guid() {return guid_input;}
  247. };
  248. static input_singletrack_factory_t< input_kdm > g_input_factory_kdm;
  249. static preferences_page_factory_t <preferences_page_myimpl> g_config_dsdiff_factory;
  250. DECLARE_FILE_TYPE("KDM Files", "*.kdm");
  251. DECLARE_COMPONENT_VERSION("KDM Decoder", MYVERSION, "Decodes Ken Silverman's KDM music files.\n\nhttps://www.patreon.com/kode54");
  252. VALIDATE_COMPONENT_FILENAME("foo_input_kdm.dll");