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

Offline RhoSigma

  • Seasoned Forum Regular
  • Posts: 366
  • Use multiple Desktop windows with GuiTools.
External Module Player (XMP)
« 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: You need do download the xmp_demo.7z too and extract it into your (32-bit) QB64 folder. The xmp_demo folder contains the DLL, the lib native player and the actual music modules used in my player demo.

Save as file: XmpPlayer.bas
Code: QB64: [Select]
  1. '***********************************
  2. '*** Use the QB64 32-bit version ***
  3. '*** the DLL is for 32 bits only ***
  4. '***********************************
  5.  
  6. '--- declare the required (only) DLL routines, there are much more ---
  7. DECLARE DYNAMIC LIBRARY "xmp_demo\libxmp"
  8.     FUNCTION xmp_create_context%& ()
  9.     SUB xmp_free_context (BYVAL xmp_context%&)
  10.     FUNCTION xmp_load_module& (BYVAL xmp_context%&, path$)
  11.     SUB xmp_release_module (BYVAL xmp_context%&)
  12.     FUNCTION xmp_start_player& (BYVAL xmp_context%&, BYVAL rate&, BYVAL format&)
  13.     FUNCTION xmp_play_buffer& (BYVAL xmp_context%&, BYVAL buffer%&, BYVAL size&, BYVAL loops&)
  14.     SUB xmp_end_player (BYVAL xmp_context%&)
  15.  
  16. '************************************************
  17. '*** Un(comment) the xmp_load_module() line   ***
  18. '*** of the music module you wanna listen to. ***
  19. '*** There are five different module types to ***
  20. '*** to show the diversity of the XMP libary. ***
  21. '************************************************
  22.  
  23. '--- init player ---
  24. ctx%& = xmp_create_context%&
  25.  
  26. 'Module name  : Jordan Jazz
  27. 'Module type  : Protracker M.K.
  28. 'Module length: 32 patterns
  29. 'Patterns     : 17  / Instruments  : 31
  30. 'Samples      : 31  / Channels     : 4
  31. 'Duration     : 2 min 36 s
  32. xErr& = xmp_load_module&(ctx%&, "xmp_demo\mod.JordanJazz" + CHR$(0))
  33.  
  34. 'Module name  : Pelforth Blues
  35. 'Module type  : Protracker M.K.
  36. 'Module length: 53 patterns
  37. 'Patterns     : 46  / Instruments  : 31
  38. 'Samples      : 31  / Channels     : 4
  39. 'Duration     : 6 min 22 s
  40. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\mod.PelforthBlues" + CHR$(0))
  41.  
  42. 'Module name  : Callisto
  43. 'Module type  : OctaMED 4.00 MMD1
  44. 'Module length: 71 patterns
  45. 'Patterns     : 50  / Instruments  : 37
  46. 'Samples      : 23  / Channels     : 8
  47. 'Duration     : 3 min 49 s
  48. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\oct.Callisto" + CHR$(0))
  49.  
  50. 'Module name  : Abbys Remix
  51. 'Module type  : DigiBooster Pro 2.17 DBM0
  52. 'Module length: 50 patterns
  53. 'Patterns     : 205 / Instruments  : 65
  54. 'Samples      : 65  / Channels     : 12
  55. 'Duration     : 3 min 35 s
  56. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\dbm.AbbysRemix" + CHR$(0))
  57.  
  58. 'Module name  : Sweet Dreams
  59. 'Module type  : FastTracker v2.00 XM 1.04
  60. 'Module length: 50 patterns
  61. 'Patterns     : 54  / Instruments  : 24
  62. 'Samples      : 24  / Channels     : 24
  63. 'Duration     : 2 min 56 s
  64. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\fst.SweetDreams" + CHR$(0))
  65.  
  66. 'Module name  : Late Noon Scene
  67. 'Module type  : Scream Tracker 3.01 S3M
  68. 'Module length: 30 patterns
  69. 'Patterns     : 32  / Instruments  : 31
  70. 'Samples      : 31  / Channels     : 16
  71. 'Duration     : 3 min 17 s
  72. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\s3m.LateNoonScene" + CHR$(0))
  73.  
  74. xErr& = xmp_start_player&(ctx%&, _SNDRATE, 0)
  75.  
  76. '--- get storage for sound data ---
  77. DIM buf AS _MEM
  78. bsz& = _SNDRATE * 5 * 2 * 2 'space for 5 seconds 16-bit stereo sound
  79. buf = _MEMNEW(bsz&)
  80.  
  81. '--- init scroll text ---
  82. txt$ = "The 'External Module Player (XMP)' library (http://xmp.sourceforge.net/) " +_
  83.        "integrated into the QB64 parts system could greatly improve the sound " +_
  84.        "abilities of QB64 by easily adding dozens of well known and even some " +_
  85.        "obscure music tracker formats from various platforms ontop of the already " +_
  86.        "existing sound functions. Hence it could bring back a couple of those " +_
  87.        "formats, which got lost with the transition from SDL to OpenGL. --- --- "
  88.  
  89. '--- main loop ---
  90. PRINT "Press any key to stop replay, or wait until end ..."
  91. st! = TIMER
  92.     _LIMIT 7
  93.     IF _SNDRAWLEN < .5 THEN GOSUB RefillSoundBuffer
  94.     'do your stuff here (non-blocking, ie. no INPUT etc.)
  95.     LOCATE 10, 10
  96.     PRINT LEFT$(txt$, 60)
  97.     txt$ = MID$(txt$, 2) + LEFT$(txt$, 1)
  98.     '-----
  99.     LOCATE 14, 24
  100.     PRINT USING "Buffered sound: ##.##### seconds"; _SNDRAWLEN
  101.     '-----
  102.     ct! = TIMER
  103.     min$ = RIGHT$("00" + LTRIM$(STR$(INT((ct! - st!) / 60))), 2)
  104.     sec$ = RIGHT$("00" + LTRIM$(STR$(INT((ct! - st!) - (VAL(min$) * 60)))), 2)
  105.     LOCATE 16, 24
  106.     PRINT USING "  Elapsed time:  &:& (mm:ss)"; min$; sec$
  107.  
  108. '--- free storage ---
  109.  
  110. '--- free player ---
  111. xmp_end_player ctx%&
  112. xmp_release_module ctx%&
  113. xmp_free_context ctx%&
  114.  
  115. '--- wait until _SNDRAW is done ---
  116. LOCATE 1, 1: COLOR 28
  117. PRINT "Replay stopped or reached end, emptying sound buffer ...": COLOR 12
  118.     _LIMIT 7
  119.     LOCATE 10, 10
  120.     PRINT LEFT$(txt$, 60)
  121.     txt$ = MID$(txt$, 2) + LEFT$(txt$, 1)
  122.     '-----
  123.     remain# = _SNDRAWLEN
  124.     LOCATE 14, 24
  125.     PRINT USING "Buffered sound: ##.##### seconds"; remain#
  126.     '-----
  127.     ct! = TIMER
  128.     min$ = RIGHT$("00" + LTRIM$(STR$(INT((ct! - st!) / 60))), 2)
  129.     sec$ = RIGHT$("00" + LTRIM$(STR$(INT((ct! - st!) - (VAL(min$) * 60)))), 2)
  130.     LOCATE 16, 24
  131.     PRINT USING "  Elapsed time:  &:& (mm:ss)"; min$; sec$
  132. LOOP UNTIL remain# = 0
  133.  
  134. '--- guess what ---
  135.  
  136. '--- get next portion of sound and send to _SNDRAW ---
  137. RefillSoundBuffer:
  138. 'To avoid sound garbage at the end, we first clear the
  139. 'buffer, just in case the final portion doesn't fill the
  140. 'entire buffer anymore.
  141. _MEMFILL buf, buf.OFFSET, bsz&, 0 AS _BYTE
  142. '-----
  143. xErr& = xmp_play_buffer&(ctx%&, buf.OFFSET, bsz&, 1)
  144. FOR i& = 0 TO (bsz&) - 2 STEP 4
  145.     leftSample& = _MEMGET(buf, buf.OFFSET + i&, INTEGER)
  146.     rigtSample& = _MEMGET(buf, buf.OFFSET + i& + 2, INTEGER)
  147.     _SNDRAW leftSample& / 32768#, rigtSample& / 32768#
  148. NEXT i&
  149.  
  150.  
« Last Edit: August 02, 2020, 03:34:18 PM by RhoSigma »
Interested in my QB64 Stuff?
GuiTools Framework, Blankers, QB64/Notepad++ setup ...
Libraries (MD5/SHA2 hashing, DES56 encryption, LZW packer, File/Data buffers, Image processing, C++ stdlib wrappers and more)
see here: https://www.qb64.org/forum/index.php?topic=809.msg100182#msg100182

Offline Petr

  • Forum Resident
  • Posts: 1349
  • The best code is the DNA of the hops.
Re: External Module Player (XMP)
« Reply #1 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&
« Last Edit: August 02, 2020, 05:06:50 AM by Petr »

Offline RhoSigma

  • Seasoned Forum Regular
  • Posts: 366
  • Use multiple Desktop windows with GuiTools.
Re: External Module Player (XMP)
« Reply #2 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?
Interested in my QB64 Stuff?
GuiTools Framework, Blankers, QB64/Notepad++ setup ...
Libraries (MD5/SHA2 hashing, DES56 encryption, LZW packer, File/Data buffers, Image processing, C++ stdlib wrappers and more)
see here: https://www.qb64.org/forum/index.php?topic=809.msg100182#msg100182

Offline Petr

  • Forum Resident
  • Posts: 1349
  • The best code is the DNA of the hops.
Re: External Module Player (XMP)
« Reply #3 on: August 02, 2020, 05:53:03 AM »
You are right. Something is wrong on my system. What is 17 Keyhit code?

 

Offline Petr

  • Forum Resident
  • Posts: 1349
  • The best code is the DNA of the hops.
Re: External Module Player (XMP)
« Reply #4 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!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 2582
    • Steve’s QB64 Archive Forum
Re: External Module Player (XMP)
« Reply #5 on: August 02, 2020, 06:10:06 AM »
Ctrl-Q should be 17, I think.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline FellippeHeitor

  • QB64 Developer
  • Forum Resident
  • Posts: 2209
  • LET IT = BE
    • QB64.org
Re: External Module Player (XMP)
« Reply #6 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.
« Last Edit: August 02, 2020, 06:42:29 AM by FellippeHeitor »

Offline Petr

  • Forum Resident
  • Posts: 1349
  • The best code is the DNA of the hops.
Re: External Module Player (XMP)
« Reply #7 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

Offline RhoSigma

  • Seasoned Forum Regular
  • Posts: 366
  • Use multiple Desktop windows with GuiTools.
Re: External Module Player (XMP)
« Reply #8 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.
Interested in my QB64 Stuff?
GuiTools Framework, Blankers, QB64/Notepad++ setup ...
Libraries (MD5/SHA2 hashing, DES56 encryption, LZW packer, File/Data buffers, Image processing, C++ stdlib wrappers and more)
see here: https://www.qb64.org/forum/index.php?topic=809.msg100182#msg100182

Offline RhoSigma

  • Seasoned Forum Regular
  • Posts: 366
  • Use multiple Desktop windows with GuiTools.
Re: External Module Player (XMP)
« Reply #9 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
« Last Edit: August 02, 2020, 09:30:32 AM by RhoSigma »
Interested in my QB64 Stuff?
GuiTools Framework, Blankers, QB64/Notepad++ setup ...
Libraries (MD5/SHA2 hashing, DES56 encryption, LZW packer, File/Data buffers, Image processing, C++ stdlib wrappers and more)
see here: https://www.qb64.org/forum/index.php?topic=809.msg100182#msg100182

Offline Dav

  • Forum Regular
  • Posts: 232
Re: External Module Player (XMP)
« Reply #10 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

Offline RhoSigma

  • Seasoned Forum Regular
  • Posts: 366
  • Use multiple Desktop windows with GuiTools.
Re: External Module Player (XMP)
« Reply #11 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: You also need the xmp_demo.7z archive from the inital post too.

Code: QB64: [Select]
  1. '***********************************
  2. '*** Use the QB64 32-bit version ***
  3. '*** the DLL is for 32 bits only ***
  4. '***********************************
  5.  
  6. '--- declare the required (only) DLL routines, there are much more ---
  7. DECLARE DYNAMIC LIBRARY "xmp_demo\libxmp"
  8.     FUNCTION xmp_create_context%& ()
  9.     SUB xmp_free_context (BYVAL xmp_context%&)
  10.     FUNCTION xmp_load_module& (BYVAL xmp_context%&, path$)
  11.     SUB xmp_release_module (BYVAL xmp_context%&)
  12.     FUNCTION xmp_start_player& (BYVAL xmp_context%&, BYVAL rate&, BYVAL format&)
  13.     FUNCTION xmp_play_buffer& (BYVAL xmp_context%&, BYVAL buffer%&, BYVAL size&, BYVAL loops&)
  14.     SUB xmp_end_player (BYVAL xmp_context%&)
  15.  
  16. '************************************************
  17. '*** Un(comment) the xmp_load_module() line   ***
  18. '*** of the music module you wanna listen to. ***
  19. '*** There are five different module types to ***
  20. '*** to show the diversity of the XMP libary. ***
  21. '************************************************
  22.  
  23. '--- init player ---
  24. ctx%& = xmp_create_context%&
  25.  
  26. 'Module name  : Jordan Jazz
  27. 'Module type  : Protracker M.K.
  28. 'Module length: 32 patterns
  29. 'Patterns     : 17  / Instruments  : 31
  30. 'Samples      : 31  / Channels     : 4
  31. 'Duration     : 2 min 36 s
  32. xErr& = xmp_load_module&(ctx%&, "xmp_demo\mod.JordanJazz" + CHR$(0))
  33.  
  34. 'Module name  : Pelforth Blues
  35. 'Module type  : Protracker M.K.
  36. 'Module length: 53 patterns
  37. 'Patterns     : 46  / Instruments  : 31
  38. 'Samples      : 31  / Channels     : 4
  39. 'Duration     : 6 min 22 s
  40. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\mod.PelforthBlues" + CHR$(0))
  41.  
  42. 'Module name  : Callisto
  43. 'Module type  : OctaMED 4.00 MMD1
  44. 'Module length: 71 patterns
  45. 'Patterns     : 50  / Instruments  : 37
  46. 'Samples      : 23  / Channels     : 8
  47. 'Duration     : 3 min 49 s
  48. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\oct.Callisto" + CHR$(0))
  49.  
  50. 'Module name  : Abbys Remix
  51. 'Module type  : DigiBooster Pro 2.17 DBM0
  52. 'Module length: 50 patterns
  53. 'Patterns     : 205 / Instruments  : 65
  54. 'Samples      : 65  / Channels     : 12
  55. 'Duration     : 3 min 35 s
  56. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\dbm.AbbysRemix" + CHR$(0))
  57.  
  58. 'Module name  : Sweet Dreams
  59. 'Module type  : FastTracker v2.00 XM 1.04
  60. 'Module length: 50 patterns
  61. 'Patterns     : 54  / Instruments  : 24
  62. 'Samples      : 24  / Channels     : 24
  63. 'Duration     : 2 min 56 s
  64. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\fst.SweetDreams" + CHR$(0))
  65.  
  66. 'Module name  : Late Noon Scene
  67. 'Module type  : Scream Tracker 3.01 S3M
  68. 'Module length: 30 patterns
  69. 'Patterns     : 32  / Instruments  : 31
  70. 'Samples      : 31  / Channels     : 16
  71. 'Duration     : 3 min 17 s
  72. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\s3m.LateNoonScene" + CHR$(0))
  73.  
  74. xErr& = xmp_start_player&(ctx%&, _SNDRATE, 0)
  75.  
  76. '--- get storage for sound data ---
  77. 'Tracker modules are usually rendered at 50fps. Too be able to use
  78. 'things like fast forward or rewind later we need the player to
  79. 'perform nearly in realtime.
  80. DIM buf AS _MEM
  81. bsz& = _SNDRATE * 0.02 * 2 * 2 'one frame (50fps) 16-bit stereo sound
  82. buf = _MEMNEW(bsz&)
  83.  
  84. '--- init screen ---
  85. SCREEN _NEWIMAGE(640, 400, 256)
  86.  
  87. '--- main loop ---
  88. PRINT "Press any key to stop replay, or wait until end ..."
  89. st! = TIMER: stim# = TIMER(0.001)
  90.     _LIMIT 60 'walk somewhat faster than sound frame rate
  91.     '-----
  92.     IF _SNDRAWLEN < .2 THEN
  93.         GOSUB RefillSoundBuffer
  94.         GOSUB DrawOscillators
  95.     END IF
  96.     'do your stuff here (non-blocking, ie. no INPUT etc.)
  97.     LOCATE 20, 24
  98.     PRINT USING "Buffered sound: ##.##### seconds"; _SNDRAWLEN
  99.     '-----
  100.     GOSUB PrintTime
  101.     '-----
  102.     'Let's keep the display refresh rate the same as the sound
  103.     'frame rate to properly visualize the oscillators. However,
  104.     'with this code we hold the frame rate steady, even if we
  105.     'need to rise the _LIMIT value above to get a better sound
  106.     'buffer refill performance. This is important, as too many
  107.     '_DISPLAY calls per second will massively affect the program's
  108.     'overall performance in a negative way.
  109.     etim# = TIMER(0.001) - stim#
  110.     IF etim# < 0 THEN etim# = etim# + 86400 'midnight fix
  111.     IF etim# >= 0.02 THEN _DISPLAY: stim# = TIMER(0.001)
  112.  
  113. '--- free player ---
  114. xmp_end_player ctx%&
  115. xmp_release_module ctx%&
  116. xmp_free_context ctx%&
  117.  
  118. '--- wait until _SNDRAW is done ---
  119. 'This block is nearly useless now, as we practically
  120. 'perform in realtime now and the sound buffer has only
  121. 'a fraction of a second remaining data after replay stop.
  122. LOCATE 1, 1: COLOR 12
  123. PRINT "Replay stopped or reached end, emptying sound buffer ...": COLOR 12
  124.     _LIMIT 25
  125.     GOSUB DrawOscillators
  126.     COLOR 12 'cause it's changed by the oscillators
  127.     '-----
  128.     remain# = _SNDRAWLEN
  129.     LOCATE 20, 24
  130.     PRINT USING "Buffered sound: ##.##### seconds"; remain#
  131.     '-----
  132.     GOSUB PrintTime
  133.     '-----
  134.     _DISPLAY
  135. LOOP UNTIL remain# = 0
  136.  
  137. '--- free storage ---
  138.  
  139. '--- guess what ---
  140.  
  141. '--- get next portion of sound and send to _SNDRAW ---
  142. RefillSoundBuffer:
  143. 'To avoid sound garbage at the end, we first clear the
  144. 'buffer, just in case the final portion doesn't fill the
  145. 'entire buffer anymore.
  146. _MEMFILL buf, buf.OFFSET, bsz&, 0 AS _BYTE
  147. '-----
  148. xErr& = xmp_play_buffer&(ctx%&, buf.OFFSET, bsz&, 1)
  149. FOR i& = 0 TO (bsz&) - 2 STEP 4
  150.     leftSample& = _MEMGET(buf, buf.OFFSET + i&, INTEGER)
  151.     rigtSample& = _MEMGET(buf, buf.OFFSET + i& + 2, INTEGER)
  152.     _SNDRAW leftSample& / 32768#, rigtSample& / 32768#
  153. NEXT i&
  154.  
  155. '--- animate wave form oscillators ---
  156. DrawOscillators:
  157. 'As the oscillators width is probably <> number of samples, we need to
  158. 'scale the x-position, same is with the amplitude (y-position).
  159. ns& = bsz& / 4 'number of samples in the buffer
  160. ow% = 597 'oscillator width
  161. LOCATE 3, 35: COLOR 7: PRINT "Left Channel"
  162. LINE (20, 48)-(620, 144), 0, BF: x% = 0: y% = 96
  163. LINE (20, 48)-(620, 144), 7, B
  164. FOR i& = 0 TO (bsz&) - 2 STEP 4
  165.     leftSample& = _MEMGET(buf, buf.OFFSET + i&, INTEGER)
  166.     xp% = (ow% / ns& * (i& / 4)) + 22
  167.     yp% = (leftSample& / 32768# * 46) + y%
  168.     IF i& = 0 THEN PSET (xp%, yp%), 10: ELSE LINE -(xp%, yp%), 10
  169. NEXT i&
  170. LOCATE 11, 35: COLOR 7: PRINT "Right Channel"
  171. LINE (20, 176)-(620, 272), 0, BF: x% = 0: y% = 224
  172. LINE (20, 176)-(620, 272), 7, B
  173. FOR i& = 0 TO (bsz&) - 2 STEP 4
  174.     rigtSample& = _MEMGET(buf, buf.OFFSET + i& + 2, INTEGER)
  175.     xp% = (ow% / ns& * (i& / 4)) + 22
  176.     yp% = (rigtSample& / 32768# * 46) + y%
  177.     IF i& = 0 THEN PSET (xp%, yp%), 10: ELSE LINE -(xp%, yp%), 10
  178. NEXT i&
  179.  
  180. '--- print elapsed time ---
  181. PrintTime:
  182. ct! = TIMER
  183. min$ = RIGHT$("00" + LTRIM$(STR$(INT((ct! - st!) / 60))), 2)
  184. sec$ = RIGHT$("00" + LTRIM$(STR$(INT((ct! - st!) - (VAL(min$) * 60)))), 2)
  185. LOCATE 22, 24
  186. PRINT USING "  Elapsed time:  &:& (mm:ss)"; min$; sec$
  187.  
  188.  
Interested in my QB64 Stuff?
GuiTools Framework, Blankers, QB64/Notepad++ setup ...
Libraries (MD5/SHA2 hashing, DES56 encryption, LZW packer, File/Data buffers, Image processing, C++ stdlib wrappers and more)
see here: https://www.qb64.org/forum/index.php?topic=809.msg100182#msg100182

Offline Dav

  • Forum Regular
  • Posts: 232
Re: External Module Player (XMP)
« Reply #12 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

Offline Petr

  • Forum Resident
  • Posts: 1349
  • The best code is the DNA of the hops.
Re: External Module Player (XMP)
« Reply #13 on: Yesterday at 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?
« Last Edit: Yesterday at 04:57:28 PM by Petr »

Offline RhoSigma

  • Seasoned Forum Regular
  • Posts: 366
  • Use multiple Desktop windows with GuiTools.
Re: External Module Player (XMP)
« Reply #14 on: Yesterday at 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: You still need the xmp_demo.7z archive from the initial post for this :)

Code: QB64: [Select]
  1. '***********************************
  2. '*** Use the QB64 32-bit version ***
  3. '*** the DLL is for 32 bits only ***
  4. '***********************************
  5.  
  6. '--- declare the required (only) DLL routines, there are much more ---
  7. DECLARE DYNAMIC LIBRARY "xmp_demo\libxmp"
  8.     FUNCTION xmp_create_context%& ()
  9.     SUB xmp_free_context (BYVAL xmp_context%&)
  10.     FUNCTION xmp_load_module& (BYVAL xmp_context%&, path$)
  11.     SUB xmp_release_module (BYVAL xmp_context%&)
  12.     FUNCTION xmp_start_player& (BYVAL xmp_context%&, BYVAL rate&, BYVAL format&)
  13.     FUNCTION xmp_play_buffer& (BYVAL xmp_context%&, BYVAL buffer%&, BYVAL size&, BYVAL loops&)
  14.     SUB xmp_end_player (BYVAL xmp_context%&)
  15.     SUB xmp_get_frame_info (BYVAL xmp_context%&, BYVAL info%&)
  16.  
  17. '--- the (rudimentary) frame info type ---
  18. TYPE xmp_frame_info '      /* Current frame information */
  19.     posi AS LONG '         /* Current position */
  20.     pattern AS LONG '      /* Current pattern */
  21.     row AS LONG '          /* Current row in pattern */
  22.     num_rows AS LONG '     /* Number of rows in current pattern */
  23.     frame AS LONG '        /* Current frame */
  24.     speed AS LONG '        /* Current replay speed */
  25.     bpm AS LONG '          /* Current bpm */
  26.     time AS LONG '         /* Current module time in ms */
  27.     total_time AS LONG '   /* Estimated replay time in ms*/
  28.     frame_time AS LONG '   /* Frame replay time in us */
  29.     'we don't use the frame buffer, as we play to an
  30.     'external buffer using xmp_play_buffer&()
  31.     buffer AS _OFFSET '    /* Pointer to sound buffer */
  32.     buffer_size AS LONG '  /* Used buffer size */
  33.     total_size AS LONG '   /* Total buffer size */
  34.     '----------
  35.     volume AS LONG '       /* Current master volume */
  36.     loop_count AS LONG '   /* Loop counter */
  37.     virt_channels AS LONG '/* Number of virtual channels */
  38.     virt_used AS LONG '    /* Used virtual channels */
  39.     sequence AS LONG '     /* Current sequence */
  40.     'the following is not yet detailed, its just a safety
  41.     'margin for now to avoid memory protection faults
  42.     xmp_channel_info AS STRING * 8192 '/* Current channel information */
  43.  
  44. '************************************************
  45. '*** Un(comment) the xmp_load_module() line   ***
  46. '*** of the music module you wanna listen to. ***
  47. '*** There are five different module types to ***
  48. '*** to show the diversity of the XMP libary. ***
  49. '************************************************
  50.  
  51. '--- init player ---
  52. ctx%& = xmp_create_context%&
  53.  
  54. 'Module name  : Jordan Jazz
  55. 'Module type  : Protracker M.K.
  56. 'Module length: 32 patterns
  57. 'Patterns     : 17  / Instruments  : 31
  58. 'Samples      : 31  / Channels     : 4
  59. 'Duration     : 2 min 36 s
  60. xErr& = xmp_load_module&(ctx%&, "xmp_demo\mod.JordanJazz" + CHR$(0))
  61.  
  62. 'Module name  : Pelforth Blues
  63. 'Module type  : Protracker M.K.
  64. 'Module length: 53 patterns
  65. 'Patterns     : 46  / Instruments  : 31
  66. 'Samples      : 31  / Channels     : 4
  67. 'Duration     : 6 min 22 s
  68. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\mod.PelforthBlues" + CHR$(0))
  69.  
  70. 'Module name  : Callisto
  71. 'Module type  : OctaMED 4.00 MMD1
  72. 'Module length: 71 patterns
  73. 'Patterns     : 50  / Instruments  : 37
  74. 'Samples      : 23  / Channels     : 8
  75. 'Duration     : 3 min 49 s
  76. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\oct.Callisto" + CHR$(0))
  77.  
  78. 'Module name  : Abbys Remix
  79. 'Module type  : DigiBooster Pro 2.17 DBM0
  80. 'Module length: 50 patterns
  81. 'Patterns     : 205 / Instruments  : 65
  82. 'Samples      : 65  / Channels     : 12
  83. 'Duration     : 3 min 35 s
  84. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\dbm.AbbysRemix" + CHR$(0))
  85.  
  86. 'Module name  : Sweet Dreams
  87. 'Module type  : FastTracker v2.00 XM 1.04
  88. 'Module length: 50 patterns
  89. 'Patterns     : 54  / Instruments  : 24
  90. 'Samples      : 24  / Channels     : 24
  91. 'Duration     : 2 min 56 s
  92. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\fst.SweetDreams" + CHR$(0))
  93.  
  94. 'Module name  : Late Noon Scene
  95. 'Module type  : Scream Tracker 3.01 S3M
  96. 'Module length: 30 patterns
  97. 'Patterns     : 32  / Instruments  : 31
  98. 'Samples      : 31  / Channels     : 16
  99. 'Duration     : 3 min 17 s
  100. 'xErr& = xmp_load_module&(ctx%&, "xmp_demo\s3m.LateNoonScene" + CHR$(0))
  101.  
  102. xErr& = xmp_start_player&(ctx%&, _SNDRATE, 0)
  103.  
  104. '--- get storage for sound data ---
  105. 'Tracker modules are usually rendered at 50fps. Too be able to use
  106. 'things like fast forward or rewind later we need the player to
  107. 'perform nearly in realtime.
  108. DIM buf AS _MEM
  109. bsz& = _SNDRATE * 0.02 * 2 * 2 'one frame (50fps) 16-bit stereo sound
  110. buf = _MEMNEW(bsz&)
  111.  
  112. '--- init screen ---
  113. SCREEN _NEWIMAGE(640, 400, 256)
  114.  
  115. '--- main loop ---
  116. PRINT "Press any key to stop replay, or wait until end ..."
  117. stim# = TIMER(0.001)
  118.     _LIMIT 60 'walk somewhat faster than sound frame rate
  119.     '-----
  120.     IF _SNDRAWLEN < .2 THEN
  121.         GOSUB RefillSoundBuffer
  122.         GOSUB DrawOscillators
  123.     END IF
  124.     'do your stuff here (non-blocking, ie. no INPUT etc.)
  125.     LOCATE 20, 24
  126.     PRINT USING "Buffered sound: ##.##### seconds"; _SNDRAWLEN
  127.     '-----
  128.     GOSUB PrintTime
  129.     '-----
  130.     'Let's keep the display refresh rate the same as the sound
  131.     'frame rate to properly visualize the oscillators. However,
  132.     'with this code we hold the frame rate steady, even if we
  133.     'need to rise the _LIMIT value above to get a better sound
  134.     'buffer refill performance. This is important, as too many
  135.     '_DISPLAY calls per second will massively affect the program's
  136.     'overall performance in a negative way.
  137.     etim# = TIMER(0.001) - stim#
  138.     IF etim# < 0 THEN etim# = etim# + 86400 'midnight fix
  139.     IF etim# >= 0.02 THEN _DISPLAY: stim# = TIMER(0.001)
  140.  
  141. '--- free player ---
  142. xmp_end_player ctx%&
  143. xmp_release_module ctx%&
  144. xmp_free_context ctx%&
  145.  
  146. '--- wait until _SNDRAW is done ---
  147. 'This block is nearly useless now, as we practically
  148. 'perform in realtime now and the sound buffer has only
  149. 'a fraction of a second remaining data after replay stop.
  150. LOCATE 1, 1: COLOR 12
  151. PRINT "Replay stopped or reached end, emptying sound buffer ...": COLOR 12
  152.     _LIMIT 25
  153.     GOSUB DrawOscillators
  154.     COLOR 12 'cause it's changed by the oscillators
  155.     '-----
  156.     remain# = _SNDRAWLEN
  157.     LOCATE 20, 24
  158.     PRINT USING "Buffered sound: ##.##### seconds"; remain#
  159.     '-----
  160.     GOSUB PrintTime
  161.     '-----
  162.     _DISPLAY
  163. LOOP UNTIL remain# = 0
  164.  
  165. '--- free storage ---
  166.  
  167. '--- guess what ---
  168.  
  169. '--- get next portion of sound and send to _SNDRAW ---
  170. RefillSoundBuffer:
  171. 'To avoid sound garbage at the end, we first clear the
  172. 'buffer, just in case the final portion doesn't fill the
  173. 'entire buffer anymore.
  174. _MEMFILL buf, buf.OFFSET, bsz&, 0 AS _BYTE
  175. '-----
  176. xErr& = xmp_play_buffer&(ctx%&, buf.OFFSET, bsz&, 1)
  177. FOR i& = 0 TO (bsz&) - 2 STEP 4
  178.     leftSample& = _MEMGET(buf, buf.OFFSET + i&, INTEGER)
  179.     rigtSample& = _MEMGET(buf, buf.OFFSET + i& + 2, INTEGER)
  180.     _SNDRAW leftSample& / 32768#, rigtSample& / 32768#
  181. NEXT i&
  182.  
  183. '--- animate wave form oscillators ---
  184. DrawOscillators:
  185. 'As the oscillators width is probably <> number of samples, we need to
  186. 'scale the x-position, same is with the amplitude (y-position).
  187. ns& = bsz& / 4 'number of samples in the buffer
  188. ow% = 597 'oscillator width
  189. LOCATE 3, 35: COLOR 7: PRINT "Left Channel"
  190. LINE (20, 48)-(620, 144), 0, BF: x% = 0: y% = 96
  191. LINE (20, 48)-(620, 144), 7, B
  192. FOR i& = 0 TO (bsz&) - 2 STEP 4
  193.     leftSample& = _MEMGET(buf, buf.OFFSET + i&, INTEGER)
  194.     xp% = (ow% / ns& * (i& / 4)) + 22
  195.     yp% = (leftSample& / 32768# * 46) + y%
  196.     IF i& = 0 THEN PSET (xp%, yp%), 10: ELSE LINE -(xp%, yp%), 10
  197. NEXT i&
  198. LOCATE 11, 35: COLOR 7: PRINT "Right Channel"
  199. LINE (20, 176)-(620, 272), 0, BF: x% = 0: y% = 224
  200. LINE (20, 176)-(620, 272), 7, B
  201. FOR i& = 0 TO (bsz&) - 2 STEP 4
  202.     rigtSample& = _MEMGET(buf, buf.OFFSET + i& + 2, INTEGER)
  203.     xp% = (ow% / ns& * (i& / 4)) + 22
  204.     yp% = (rigtSample& / 32768# * 46) + y%
  205.     IF i& = 0 THEN PSET (xp%, yp%), 10: ELSE LINE -(xp%, yp%), 10
  206. NEXT i&
  207.  
  208. '--- print elapsed time ---
  209. PrintTime:
  210. DIM xfi AS xmp_frame_info
  211. DIM m AS _MEM: m = _MEM(xfi)
  212. xmp_get_frame_info ctx%&, m.OFFSET
  213. min$ = RIGHT$("00" + LTRIM$(STR$(INT(CINT(xfi.time / 1000) / 60))), 2)
  214. sec$ = RIGHT$("00" + LTRIM$(STR$(INT(CINT(xfi.time / 1000) - (VAL(min$) * 60)))), 2)
  215. LOCATE 22, 24
  216. PRINT USING "  Elapsed time:  &:& (mm:ss)"; min$; sec$
  217. min$ = RIGHT$("00" + LTRIM$(STR$(INT(CINT(xfi.total_time / 1000) / 60))), 2)
  218. sec$ = RIGHT$("00" + LTRIM$(STR$(INT(CINT(xfi.total_time / 1000) - (VAL(min$) * 60)))), 2)
  219. LOCATE 23, 24
  220. PRINT USING "    Total time:  &:& (mm:ss)"; min$; sec$
  221.  
  222.  
Interested in my QB64 Stuff?
GuiTools Framework, Blankers, QB64/Notepad++ setup ...
Libraries (MD5/SHA2 hashing, DES56 encryption, LZW packer, File/Data buffers, Image processing, C++ stdlib wrappers and more)
see here: https://www.qb64.org/forum/index.php?topic=809.msg100182#msg100182