Author Topic: External Module Player (XMP)  (Read 16566 times)

0 Members and 1 Guest are viewing this topic.

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: External Module Player (XMP)
« Reply #15 on: August 04, 2020, 11:50:45 am »
Hi RhoSigma,

Here is, why I asked about getting the total length of time. Sound devastation by stereo malfunction by the SNDRAW command is known. This program will work around this problem and finally! Stereo MOD audio is the same as from WINAMP. Thank you again. This opens up further possibilities for the QB64. I'm absolutely thrilled.

Code: QB64: [Select]
  1. XMPPLAY "love.mod"
  2.  
  3. SUB XMPPLAY (file AS STRING)
  4.     DECLARE DYNAMIC LIBRARY "xmp_demo\libxmp"
  5.         FUNCTION xmp_create_context%& ()
  6.         SUB xmp_free_context (BYVAL xmp_context%&)
  7.         FUNCTION xmp_load_module& (BYVAL xmp_context%&, path$)
  8.         SUB xmp_release_module (BYVAL xmp_context%&)
  9.         FUNCTION xmp_start_player& (BYVAL xmp_context%&, BYVAL rate&, BYVAL format&)
  10.         FUNCTION xmp_play_buffer& (BYVAL xmp_context%&, BYVAL buffer AS _OFFSET, BYVAL size AS _OFFSET, BYVAL loops&)
  11.         SUB xmp_end_player (BYVAL xmp_context%&)
  12.         SUB xmp_get_frame_info (BYVAL xmp_context%&, BYVAL info%&)
  13.     END DECLARE
  14.  
  15.     '--- the (rudimentary) frame info type ---
  16.     TYPE xmp_frame_info '      /* Current frame information */
  17.         posi AS LONG '         /* Current position */
  18.         pattern AS LONG '      /* Current pattern */
  19.         row AS LONG '          /* Current row in pattern */
  20.         num_rows AS LONG '     /* Number of rows in current pattern */
  21.         frame AS LONG '        /* Current frame */
  22.         speed AS LONG '        /* Current replay speed */
  23.         bpm AS LONG '          /* Current bpm */
  24.         time AS LONG '         /* Current module time in ms */
  25.         total_time AS LONG '   /* Estimated replay time in ms*/
  26.         frame_time AS LONG '   /* Frame replay time in us */
  27.         'we don't use the frame buffer, as we play to an
  28.         'external buffer using xmp_play_buffer&()
  29.         buffer AS _OFFSET '    /* Pointer to sound buffer */
  30.         buffer_size AS LONG '  /* Used buffer size */
  31.         total_size AS LONG '   /* Total buffer size */
  32.         '----------
  33.         volume AS LONG '       /* Current master volume */
  34.         loop_count AS LONG '   /* Loop counter */
  35.         virt_channels AS LONG '/* Number of virtual channels */
  36.         virt_used AS LONG '    /* Used virtual channels */
  37.         sequence AS LONG '     /* Current sequence */
  38.         'the following is not yet detailed, its just a safety
  39.         'margin for now to avoid memory protection faults
  40.         xmp_channel_info AS STRING * 8192 '/* Current channel information */
  41.     END TYPE
  42.     ctx%& = xmp_create_context%&
  43.     xerr& = xmp_load_module&(ctx%&, file$ + CHR$(0))
  44.     xerr2& = xmp_start_player&(ctx%&, _SNDRATE, 0)
  45.     DIM xfi AS xmp_frame_info
  46.     DIM m AS _MEM: m = _MEM(xfi)
  47.     xmp_get_frame_info ctx%&, m.OFFSET
  48.     _MEMFREE m
  49.     Time& = _SNDRATE * INT(xfi.total_time / 1000) * 4
  50.     DIM Buf AS _MEM
  51.     Buf = _MEMNEW(Time&)
  52.     buf& = xmp_play_buffer&(ctx%&, Buf.OFFSET, Buf.SIZE, 10)
  53.     SAVESOUND16S Buf, Time&, "swap.wav"
  54.     xmp_end_player ctx%&
  55.     xmp_release_module ctx%&
  56.     xmp_free_context ctx%&
  57.     PRINT "done"
  58.     _SNDPLAYFILE ("swap.wav")
  59. SUB SAVESOUND16S (arr AS _MEM, lenght AS LONG, file AS STRING) 'speed upgraded version (now much faster), first SINGLE values are recalculated as INTEGERs and THEN writed at once to file.
  60.     TYPE head16
  61.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  62.         size AS LONG '              4 bytes  (file size)
  63.         fomat AS STRING * 4 '       4 bytes  (WAVE)
  64.         sub1 AS STRING * 4 '        4 bytes  (fmt )
  65.         subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  66.         format AS INTEGER '         2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  67.         channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  68.         rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  69.         ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  70.         Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  71.         Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  72.         subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  73.         lenght AS LONG '            4 bytes  Data block size
  74.     END TYPE '                     44 bytes  total
  75.     DIM H16 AS head16
  76.     ch = FREEFILE
  77.     H16.chunk = "RIFF"
  78.     H16.size = 44 + lenght 'two channels, it create 16 bit, stereo wav file, one sample use 2 bytes to one channel
  79.     H16.fomat = "WAVE"
  80.     H16.sub1 = "fmt "
  81.     H16.subchunksize = 16
  82.     H16.format = 1
  83.     H16.channels = 2
  84.     H16.rate = 44100
  85.     H16.ByteRate = 44100 * 2 * 16 / 8
  86.     H16.Block = 4
  87.     H16.Bits = 16
  88.     H16.subchunk2 = "data"
  89.     H16.lenght = lenght
  90.     IF _FILEEXISTS(file$) THEN KILL file$
  91.     OPEN file$ FOR BINARY AS #ch
  92.     PUT #ch, , H16
  93.     sp$ = SPACE$(lenght)
  94.     _MEMGET arr, arr.OFFSET, sp$
  95.     PUT ch, , sp$
  96.     CLOSE ch
  97.  

* love.mod (Filesize: 445.13 KB, Downloads: 334)
« Last Edit: August 04, 2020, 12:15:00 pm by Petr »

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: External Module Player (XMP)
« Reply #16 on: August 04, 2020, 01:10:23 pm »
Well Petr, that's a nice example how to use XMP as a Mod2Wav converter, glad you like it and it's useful for you :)
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: External Module Player (XMP)
« Reply #17 on: August 11, 2020, 05:16:57 pm »
Here is an improved version. I've found that for some music files, the length of a song can't be calculated correctly. That's why I solved it by taking all the content as a song, except zeros (silence) during the analysis from the end of the song.

Again, 32bit IDE is needed.

Code: QB64: [Select]
  1. XMPPLAY "blitzkrieg.mod"
  2.  
  3. SUB XMPPLAY (file AS STRING)
  4.     DECLARE DYNAMIC LIBRARY "xmp_demo\libxmp"
  5.         FUNCTION xmp_create_context%& ()
  6.         SUB xmp_free_context (BYVAL xmp_context%&)
  7.         FUNCTION xmp_load_module& (BYVAL xmp_context%&, path$)
  8.         SUB xmp_release_module (BYVAL xmp_context%&)
  9.         FUNCTION xmp_start_player& (BYVAL xmp_context%&, BYVAL rate&, BYVAL format&)
  10.         FUNCTION xmp_play_buffer& (BYVAL xmp_context%&, BYVAL buffer AS _OFFSET, BYVAL size AS _OFFSET, BYVAL loops&)
  11.         SUB xmp_end_player (BYVAL xmp_context%&)
  12.         SUB xmp_get_frame_info (BYVAL xmp_context%&, BYVAL info%&)
  13.     END DECLARE
  14.  
  15.     '--- the (rudimentary) frame info type ---
  16.     TYPE xmp_frame_info '      /* Current frame information */
  17.         posi AS LONG '         /* Current position */
  18.         pattern AS LONG '      /* Current pattern */
  19.         row AS LONG '          /* Current row in pattern */
  20.         num_rows AS LONG '     /* Number of rows in current pattern */
  21.         frame AS LONG '        /* Current frame */
  22.         speed AS LONG '        /* Current replay speed */
  23.         bpm AS LONG '          /* Current bpm */
  24.         time AS LONG '         /* Current module time in ms */
  25.         total_time AS LONG '   /* Estimated replay time in ms*/
  26.         frame_time AS LONG '   /* Frame replay time in us */
  27.         'we don't use the frame buffer, as we play to an
  28.         'external buffer using xmp_play_buffer&()
  29.         buffer AS _OFFSET '    /* Pointer to sound buffer */
  30.         buffer_size AS LONG '  /* Used buffer size */
  31.         total_size AS LONG '   /* Total buffer size */
  32.         '----------
  33.         volume AS LONG '       /* Current master volume */
  34.         loop_count AS LONG '   /* Loop counter */
  35.         virt_channels AS LONG '/* Number of virtual channels */
  36.         virt_used AS LONG '    /* Used virtual channels */
  37.         sequence AS LONG '     /* Current sequence */
  38.         'the following is not yet detailed, its just a safety
  39.         'margin for now to avoid memory protection faults
  40.         xmp_channel_info AS STRING * 8192 '/* Current channel information */
  41.     END TYPE
  42.     ctx%& = xmp_create_context%&
  43.     xerr& = xmp_load_module&(ctx%&, file$ + CHR$(0))
  44.     xerr2& = xmp_start_player&(ctx%&, _SNDRATE, 0)
  45.     DIM xfi AS xmp_frame_info
  46.     DIM m AS _MEM: m = _MEM(xfi)
  47.     xmp_get_frame_info ctx%&, m.OFFSET
  48.     _MEMFREE m
  49.  
  50.     Tim& = xfi.total_time / 1000
  51.     Time& = _SNDRATE * Tim& * 4
  52.    
  53.     DIM Buf AS _MEM
  54.     Buf = _MEMNEW(Time&)
  55.     buf& = xmp_play_buffer&(ctx%&, Buf.OFFSET, Buf.SIZE, 0)
  56.  
  57.     Time& = LastRec(Buf)
  58.     PRINT "lastrec:"; LastRec(Buf)
  59.     SAVESOUND16S Buf, Time&, "swap.wav"
  60.     xmp_end_player ctx%&
  61.     xmp_release_module ctx%&
  62.     xmp_free_context ctx%&
  63.     PRINT "done"
  64.     _SNDPLAYFILE ("swap.wav")
  65.  
  66. SUB SAVESOUND16S (arr AS _MEM, lenght AS LONG, file AS STRING) 'speed upgraded version (now much faster), first SINGLE values are recalculated as INTEGERs and THEN writed at once to file.
  67.     TYPE head16
  68.         chunk AS STRING * 4 '       4 bytes  (RIFF)
  69.         size AS LONG '              4 bytes  (file size)
  70.         fomat AS STRING * 4 '       4 bytes  (WAVE)
  71.         sub1 AS STRING * 4 '        4 bytes  (fmt )
  72.         subchunksize AS LONG '      4 bytes  (lo / hi), $00000010 for PCM audio
  73.         format AS INTEGER '         2 bytes  (0001 = standard PCM, 0101 = IBM mu-law, 0102 = IBM a-law, 0103 = IBM AVC ADPCM)
  74.         channels AS INTEGER '       2 bytes  (1 = mono, 2 = stereo)
  75.         rate AS LONG '              4 bytes  (sample rate, standard is 44100)
  76.         ByteRate AS LONG '          4 bytes  (= sample rate * number of channels * (bits per channel /8))
  77.         Block AS INTEGER '          2 bytes  (block align = number of channels * bits per sample /8)
  78.         Bits AS INTEGER '           2 bytes  (bits per sample. 8 = 8, 16 = 16)
  79.         subchunk2 AS STRING * 4 '   4 bytes  ("data")  contains begin audio samples
  80.         lenght AS LONG '            4 bytes  Data block size
  81.     END TYPE '                     44 bytes  total
  82.     DIM H16 AS head16
  83.     ch = FREEFILE
  84.     H16.chunk = "RIFF"
  85.     H16.size = 44 + lenght 'two channels, it create 16 bit, stereo wav file, one sample use 2 bytes to one channel
  86.     H16.fomat = "WAVE"
  87.     H16.sub1 = "fmt "
  88.     H16.subchunksize = 16
  89.     H16.format = 1
  90.     H16.channels = 2
  91.     H16.rate = 44100
  92.     H16.ByteRate = 44100 * 2 * 16 / 8
  93.     H16.Block = 4
  94.     H16.Bits = 16
  95.     H16.subchunk2 = "data"
  96.     H16.lenght = lenght
  97.     IF _FILEEXISTS(file$) THEN KILL file$
  98.     OPEN file$ FOR BINARY AS #ch
  99.     PUT #ch, , H16
  100.     sp$ = SPACE$(lenght)
  101.     _MEMGET arr, arr.OFFSET, sp$
  102.     PUT ch, , sp$
  103.     CLOSE ch
  104.  
  105. FUNCTION LastRec&& (a AS _MEM)
  106.     DIM b AS _OFFSET, c AS _OFFSET
  107.     b = a.SIZE - 2
  108.     DO UNTIL b = 0 OR v <> 0
  109.         v = _MEMGET(a, a.OFFSET + b, INTEGER)
  110.         b = b - 2
  111.     LOOP
  112.     DIM LR AS _INTEGER64
  113.     DIM LRO AS _MEM
  114.     LRO = _MEM(LR)
  115.     _MEMPUT LRO, LRO.OFFSET, b
  116.     LastRec = LR
  117.     _MEMFREE LRO
  118.  
* blitzkrieg.mod (Filesize: 126.62 KB, Downloads: 342)
« Last Edit: August 11, 2020, 05:27:06 pm by Petr »

Marked as best answer by RhoSigma on December 29, 2021, 03:26:16 pm

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: External Module Player (XMP)
« Reply #18 on: December 29, 2021, 08:25:58 pm »
Hi all,

after almost 5 years, libxmp got lots of fixes plus a couple new features and was finally updated to version 4.5.0 by the mid of this year. This was a good occasion for me to complete my wrapper library for it and to improve my demo player to showcase some of the libxmp functions and give a hint how to correctly use the wrapper library.

When porting the informations over into libxmp.bi/.bm and testing it, I found several badly described things and tried to correct/improve those with my own findings from examining the libxmp sources.

The XmpPlayer.bas file will espacially show the use of the various wrapper or helper functions in libxmp.bm, which are used to retrieve informations from the internal libxmp structures.

The player itself is almost self explanatory at runtime and programmed as an endless program loop, so simply hit the X button to quit it at any time. It is able to show various informations about the current module and also features a wave oscillator and frequency spectrum (FFT) display, the latter thanks to _vince's fabulous Fast Fourier algorithms.

Note that this program requires the 32-bit version of QB64, as libxmp is currently available as 32-bit version only, this will hopefully change in the future. So here it is, just move the extracted xmpDemo folder with its entire contents into your QB64 working folder (where qb64.exe is in), then open the QB64 IDE, untick the Save EXE in source folder option (if active (in Run Menu)), load XmpPlayer.bas from the xmpDemo folder, hit F5 and enjoy the music.
* xmpDemo.7z (Filesize: 2.53 MB, Downloads: 283)
« Last Edit: December 30, 2021, 08:59:39 am by RhoSigma »
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: External Module Player (XMP)
« Reply #19 on: December 30, 2021, 12:18:17 pm »
I struggled with it a bit (I unpacked it into another folder) and it works perfectly. Special thanks for the solution of the spectrum analyzer, which we fought here for a long time. I'm playing music now, I'll study the code. Thank you so much for sharing!