9

The problem is:

I try to play Fast Tracker module in infinite loop, but doing so just replay music from start, instead of following repeat position.

Example: (here's the source for module https://api.modarchive.org/downloads.php?moduleid=153915#zeta_force_level_2.xm)

import pygame

pygame.mixer.init()
pygame.mixer.music.load('/path/to/zeta_force_level_2.xm')
pygame.mixer.music.play(-1)

What I'm trying to achieve: Play module music in loop, each time looping on repeat position, not on start of track. Use of pygame isn't necessary: I use it because I didn't found anything suitable for playing tracker music

Thanks in advance.

Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62
MaxLunar
  • 653
  • 6
  • 24

1 Answers1

7

Update: I wrote a simple demo in cython that successfully plays your linked .xm file. It basically is a translation of this c demo code. My code for it can be found on this github page. For getting it to work in Ubuntu, I had to install the libxmp-dev package. Note that everything is hardcoded in at the moment, so it would need to be refactored to be more directly usable in your project.


This is by no means a conclusive answer. I ran into many potential pitfalls along the way that make me doubt if pygame is the right tool for the job here, but I will present what I have found out so far as well as some suggestions.

It looks like the .xm Fast Tracker MODule format is different from your typical wav/ogg/mp3 file in that rather than just playing an array of sample data, you can combine different MIDI instruments and samples together to create you music, like the (sweet) chiptune linked in the question.

It turns out that SDL/pygame, can play such files, but in a rather limited way. Looking at pygame's music module, there is a set_pos function. However, trying to use that gave me a pygame.error: set_pos unsupported for this codec. Interestingly however, I was able to work around this by using pygame.mixer.music.play with the optional start keyword. While start on most file formats is simply the offset in seconds before starting the file (only on the first playthrough of the song), it has a different meaning for MOD files like the .xm file in the question. Apparently, it corresponds to a pattern number in the MOD file. As a result, there are a very limited number of potential starting points that can be used in pygame, based on where each pattern starts in the file.

If you have a specific pattern number you want to start from in mind, then the following code would be sufficient to loop. Note that I use pygame's event system to see when the sound is finished to "loop" the sound file with the appropriate "pattern offset":

import pygame

pygame.init()
pygame.mixer.music.load('zeta_force_level_2.xm')
pattern = 10
loop_event = pygame.USEREVENT + 1
pygame.mixer.music.set_endevent(loop_event)
pygame.mixer.music.play(start=pattern)

while True:
    for event in pygame.event.get():
        if event.type == loop_event:
            pygame.mixer.music.play(start=pattern)

At this point, you might be wondering what exactly are these patterns? If you have ffmpeg installed on your system, you can run ffprobe on your file and get the following output:

Input #0, libmodplug, from 'zeta_force_level_2.xm':
  Metadata:
    name            : zeta force level 2
    instrument      : by zabutom --
                    : bye bye computer..
                    : see you in a week
    sample          : zeta force level 2
    extra info      : 20 patterns, 10 channels, 3/14 instruments, 1/14 sample
  Duration: 00:01:01.00, bitrate: 3 kb/s
    Stream #0:0: Audio: pcm_s16le, 44100 Hz, 2 channels, s16, 1411 kb/s

It looks like there are 20 patterns in this file from which you can choose as your starting location for the loop. To get more information about your particular file, you can open (and edit!) your file in a tool like MilkyTracker and get an output like this:

enter image description here There are some tutorials for MilkyTracker online on youtube, but it looks like a pretty complicated piece of software.

There also appears to be a library called libxmp and its corresponding python binding. This should handle the conversion required to "render" MOD file data into a simple PCM array that can be played in a library like pyaudio or any python binding to OpenAL. Either way, it looks like you have your work cut out for you!

CodeSurgeon
  • 2,435
  • 2
  • 15
  • 36
  • libxmp-python looks interesting, but I need to test if it replays correctly. I'll accept answer if so, anyway thank you for replying. – MaxLunar Dec 25 '17 at 09:40
  • @MaxLunar I'll keep the bounty open for the rest of the week (4 more days), and that will gradually give larger visibility to your question. I suggest waiting with the accept till the end to see if other answers appear, considering how unaccepted questions are also much more likely to get answers. – Andras Deak -- Слава Україні Dec 25 '17 at 22:18
  • @MaxLunar Just trying out the library I had linked to and was wondering what OS platform and python version are you using? It looks like the libxmp python binding might have been written with python2 in mind... – CodeSurgeon Dec 27 '17 at 04:01
  • @CodeSurgeon I'm using python 3.5 and actually I wanna target both Linux and Windows. I've tested the solution with pygame on Win, but seems that I screwed up something or it doesnt work here (no sound at all). – MaxLunar Dec 28 '17 at 02:07
  • @CodeSurgeon looks like it also depends on system when trying to play tracker music. Linux plays sound aswell, but Windows are not. Also i'm trying to achieve portability, so wherever I start my script, it should play sound. Is this possible to solve? Thanks. – MaxLunar Dec 29 '17 at 05:04
  • @MaxLunar Were you able to get the c library version of libxmp installed in windows? For Ubuntu, I just needed to install a `libxmp-dev` package (I am not too good at the whole building stuff from source thing). I have written a cython script that can load the data with `libxmp` and then play the audio with `OpenAL`. I will go ahead and update my answer with the code, but it is very unpolished and needs to be refactored into a nice function to be really useful. – CodeSurgeon Dec 29 '17 at 05:24
  • @CodeSurgeon I just gonna accept the answer, because bounty is about to expire. You can update answer for sure, anyone who stuck in same way would check it out. Another way is pastebin, if it is too rough to be posted right now. – MaxLunar Dec 29 '17 at 11:54
  • @CodeSurgeon what's about Windows? What is required to build, so I can prepare before being stuck in pip errors. Provide some README in repo, or tell me how i can contact you, instead of conversation in comments (preferably Telegram) – MaxLunar Dec 29 '17 at 16:18
  • @MaxLunar I have just registered on Telegram if you need to reach me there. My username there is `@anara32`. Feel free to message me any questions you have! – CodeSurgeon Dec 30 '17 at 04:08