QB64.org Forum

Active Forums => Programs => Topic started by: RhoSigma on August 01, 2020, 04:30:40 pm

Title: External Module Player (XMP)
Post by: RhoSigma on August 01, 2020, 04:30:40 pm
Almost 4 month ago I suggested this as a possible enhancement for QB64 v1.5 here (https://www.qb64.org/forum/index.php?topic=2446). As there were absolutely no reactions to it, I assume that the number of potential users for this stuff is so low, that it's not worth the efforts to put it into QB64.

However, as proof of concept and for the few people who might be interested in, here comes a little demo to showcase the usage of this player library in form of a DLL.

NOTE: This is outdated, please follow the green link above to the most recent update of this demo program.

EDIT: Outdated codebox removed, see note above...
Title: Re: External Module Player (XMP)
Post by: Petr on August 02, 2020, 04:50:51 am
Works well for me! Perfect work, RhoSigma. Thank you very much for sharing it. I wrote just one - to your source, because so, at is is it plays just first five second music for me:

row 109: LOOP WHILE NOT xErr&
Title: Re: External Module Player (XMP)
Post by: RhoSigma on August 02, 2020, 05:37:58 am
Well, if the _KEYHIT = 0 gives you the immediate player stop, then it seems any key is sticky on your keyboard, continuously giving you random keyhits. Did you try INKEY$ = "" instead? Would it give you the same behavior?, then definitly check Your keyboard, maybe a loose cable?
Title: Re: External Module Player (XMP)
Post by: Petr on August 02, 2020, 05:53:03 am
You are right. Something is wrong on my system. What is 17 Keyhit code?

  [ This attachment cannot be displayed inline in 'Print Page' view ]  
Title: Re: External Module Player (XMP)
Post by: Petr on August 02, 2020, 06:09:46 am
Really perfect work. It play also XM files, not just MOD files! I'm really happy about it!
Title: Re: External Module Player (XMP)
Post by: SMcNeill on August 02, 2020, 06:10:06 am
Ctrl-Q should be 17, I think.
Title: Re: External Module Player (XMP)
Post by: FellippeHeitor on August 02, 2020, 06:27:50 am
Interestingly I was providing support to @MLambert last night regarding weird InForm behavior and just learned that shift returns -16 when released, ctrl returns -17 and alt returns -18.

KEYHIT never returns a positive 16, 17 or 18 in those cases.

Another something to notice is that these low return codes were specific to Lambert's laptop, as I don't get them on my machine. Might be a computer setting we couldn't find or a hardware-specific behavior.

These are not old-time scan codes as the wiki can attest. Curiously these are the same codes that web browsers report to JavaScript when these keys are pressed/released.

Also notice that QB64 will keep getting modifier key events from Windows even when the program window is not focused.
Title: Re: External Module Player (XMP)
Post by: Petr on August 02, 2020, 06:42:16 am
KEYHIT always return me the same outputs. Each time the program is started, the sixth value is -17. And indeed, -17 is the code to release Ctrl. This error does not occur in IDE 1.4 64 bit, but always occurs in IDE 1.5 b5e896d (32 bit). The keyboard I use is A4TECH model KD-800L
Title: Re: External Module Player (XMP)
Post by: RhoSigma on August 02, 2020, 07:43:09 am
I work with V1.4 stable, on a HP Pavilion G series Laptop.
I do get the -16,-17,-18 codes too when releasing shift/ctrl/alt keys, but it's not immediatly returned without a keypress/release as for Petr.

However, if we're going to discuss this further, then we should open a new topic, cause this one is about music module playing, not keyboard issues.
Title: Re: External Module Player (XMP)
Post by: RhoSigma on August 02, 2020, 08:02:42 am
Really perfect work. It play also XM files, not just MOD files! I'm really happy about it!

BTW - You find a complete list of supported formats on the official XMP page here http://xmp.sourceforge.net/, the formats are collected in sub-lists for each platform.

And here you find 1000s (really) of different music modules usually packed in *.lzh or *.lha archives, which can be unpacked with 7-zip: http://aminet.net/tree?path=mods
Title: Re: External Module Player (XMP)
Post by: Dav on August 02, 2020, 08:36:49 am
NIce, RhoSigma! Looks like it supports a lot of formats.  I'm gonna look for my old trackers files!

- Dav
Title: Re: External Module Player (XMP)
Post by: RhoSigma on August 02, 2020, 08:19:28 pm
Today I did work to make the player operating in near realtime. The version from my inital post does buffer 5 seconds of sound, which would give every intervention like fast forwarding or rewinding a delay of 5 seconds. Also in realtime it would be possible to add some cool effects stuff. I've added oscillators for both, left and right channels in this new version to showcase.

To get into realtime, the tracker modul must be played frame by frame, and most trackers used a frame rate of 50fps (the vertical blank frequency in PAL-TV standard) which was a stable metronom timing base back in those days. However, such a frame is only worth 0.02 seconds of sound data and _SNDRAW needs to be fed quiet well to keep playing stable.

So the following player version works on my hardware, but I'd be interested in results from other people. If you get random disruptions in sound then first try to raise the _LIMIT value in the main loop. If it doesn't help, then add frames to the sound buffer size in line 82 (ie. raise the 0.02 value to 0.04, 0.06, 0.08 etc.) until it works.

NOTE: This is outdated, please follow the green link above to the most recent update of this demo program.

EDIT: Outdated codebox removed, see note above...
Title: Re: External Module Player (XMP)
Post by: Dav on August 02, 2020, 08:45:48 pm
Plays very well for me. Using code as is, I'm not getting random disruptions.  Testing on an older Thinkpad R61, Win7 32bit.  Runs smooth.

- Dav
Title: Re: External Module Player (XMP)
Post by: Petr on August 03, 2020, 03:53:04 pm
Play good on my system. Is there any way, how find total track time in begin?

I've made some attempts with this (especially here I tried to have the library convert the contents of the MOD file into my memory at once) but it seems that the library releases the content gradually, as it should be played, ie a track 3 minutes long memory writes in three minutes. Or is there a way save an entire song at once to memory?
Title: Re: External Module Player (XMP)
Post by: RhoSigma on August 03, 2020, 09:14:21 pm
@Dav,@Petr
Thanks for your feedback, good to hear the realtime concept works as expected.

Petr, in this  version I've added retrieval of information using the xmp_frame_info() function. The current replay time as well as the total time are retrieved via the new UDT which is filled by the mentioned function. (see the PrintTime: routine at the end of program)

To convert more data or the entire song it's enough to raise the buffer size (line 111), just replace the 0.02 with eg. 180 so get 3 mins into memory, even if the song is shorter it will not hurt, the remaining time is then simply filled with silence. But note that if you do so with this version, then the oscillators and timers don't update in realtime anymore, but in the time you've set.

NOTE: This is outdated, please follow the green link above to the most recent update of this demo program.

EDIT: Outdated codebox removed, see note above...
Title: Re: External Module Player (XMP)
Post by: Petr 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.  

Title: Re: External Module Player (XMP)
Post by: RhoSigma 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 :)
Title: Re: External Module Player (XMP)
Post by: Petr 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.  
Title: Re: External Module Player (XMP)
Post by: RhoSigma 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.
Title: Re: External Module Player (XMP)
Post by: Petr 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!