flyingfox

Python BASSMOD Wrapper

Oct 17th, 2011
775
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.23 KB | None | 0 0
  1. import os
  2. import ctypes
  3. from collections import namedtuple
  4.  
  5. # Make sure you have BASSMOD installed:
  6. # http://www.un4seen.com/
  7. _B = ctypes.cdll.LoadLibrary('./libbassmod.so')
  8. #_B = ctypes.windll.LoadLibrary('BASSMOD.DLL')
  9.  
  10. BASS_ERRORS = {0: "BASS_OK: No error",
  11.                1: "BASS_ERROR_MEM: Memory error",
  12.                2: "BASS_ERROR_FILEOPEN: Cannot open file",
  13.                3: "BASS_ERROR_DRIVER: Cannot find a free/valid driver",
  14.                5: "BASS_ERROR_HANDLE: Invalid handle",
  15.                6: "BASS_ERROR_FORMAT: Unsupported format",
  16.                7: "BASS_ERROR_POSITION: Invalid playback position",
  17.                8: "BASS_ERROR_INIT: Init has not been successfully called",
  18.                14: "BASS_ERROR_ALREADY: Already initialized/loaded",
  19.                19: "BASS_ERROR_ILLTYPE: An illegal type was specified",
  20.                20: "BASS_ERROR_ILLPARAM: An illegal parameter was specified",
  21.                23: "BASS_ERROR_DEVICE: Illegal device number",
  22.                24: "BASS_ERROR_NOPLAY: Not playing",
  23.                28: "BASS_ERROR_NOMUSIC: No MOD music has been loaded",
  24.                30: "BASS_ERROR_NOSYNC: Synchronizers have been disabled",
  25.                37: "BASS_ERROR_NOTAVAIL: Requested data is not available",
  26.                38: "BASS_ERROR_DECODE: The channel is a 'decoding channel'",
  27.                41: "BASS_ERROR_FILEFORM: Unsupported file format",
  28.                -1: "BASS_ERROR_UNKNOWN: Some other mystery error",
  29.               }
  30.  
  31. # Active States
  32. STOPPED = 0
  33. PLAYING = 1
  34. PAUSED  = 3
  35.  
  36. # Device Flags
  37. DEVICE_8BITS = 1  # use 8 bit resolution, else 16 bit
  38. DEVICE_MONO  = 2  # use mono, else stereo
  39. DEVICE_NOSYNC= 16 # disable synchronizers
  40.  
  41. # Music Flags
  42. MUSIC_RAMP      = 1          # normal ramping
  43. MUSIC_RAMPS     = 2          # sensitive ramping
  44. MUSIC_LOOP      = 4          # loop music
  45. MUSIC_FT2MOD    = 16         # play .MOD as FastTracker 2 does
  46. MUSIC_PT1MOD    = 32         # play .MOD as ProTracker 1 does
  47. MUSIC_POSRESET  = 256        # stop all notes when moving position
  48. MUSIC_SURROUND  = 512        # surround sound^M
  49. MUSIC_SURROUND2 = 1024       # surround sound (mode 2)^M
  50. MUSIC_STOPBACK  = 2048       # stop the music on a backwards jump effect^M
  51. MUSIC_CALCLEN   = 8192       # calculate playback length^M
  52. MUSIC_NONINTER  = 16384      # non-interpolated mixing^M
  53. MUSIC_NOSAMPLE  = 0x400000   # don't load the samples^M
  54.  
  55. # Special Unicode Flag
  56. UNICODE_FLAG    = 0x80000000
  57.  
  58. # Sync Flags
  59. SYNC_MUSICPOS  = 0
  60. SYNC_POS       = 0
  61. SYNC_MUSICINST = 1
  62. SYNC_END       = 2
  63. SYNC_MUSICFX   = 3
  64. SYNC_ONETIME   = 0x80000000 # sync only once, else continuously
  65.  
  66. _B.BASSMOD_GetCPU.restype = ctypes.c_float
  67.  
  68. SYNCPROC = ctypes.CFUNCTYPE(None, ctypes.c_long, ctypes.c_long, ctypes.c_long)
  69.  
  70. __version__ = _B.BASSMOD_GetVersion()
  71.  
  72. class BassError(Exception): pass
  73.  
  74. class Bass(object):
  75.     def __init__(self, device=-1, freq=44100, flags=0, init=True):
  76.         self.config = namedtuple('Config', 'device freq flags')(device, freq, flags)
  77.         self.init = False
  78.  
  79.         self._position_scaler = 1
  80.         self._amplify = 50
  81.         self._pan_seperation = 50
  82.  
  83.         if init:
  84.             self.start()
  85.  
  86.     # For use with 'with Bass(...) as X:'
  87.     def __enter__(self):
  88.         if not self.init:
  89.             bass_error()
  90.         return self
  91.     def __exit__(self, type, value, traceback):
  92.         self.free()
  93.         bass_error()
  94.  
  95.     def __del__(self):
  96.         self.free()
  97.  
  98.     def start(self):
  99.         self.init = bool(_B.BASSMOD_Init(self.config.device, self.config.freq, self.config.flags))
  100.  
  101.     @property
  102.     def error_code(self):
  103.         "Returns the BASSMOD error code.  Lookup using BassError() function"
  104.         return _B.BASSMOD_ErrorGetCode()
  105.  
  106.     def free(self):
  107.         "Frees all resources used by the digial output, including the MOD music."
  108.         _B.BASSMOD_Free()
  109.  
  110.     @property
  111.     def cpu(self):
  112.         "Retrieves the current CPU useage of BASSMOD in floating point percent"
  113.         return _B.BASSMOD_GetCPU()
  114.  
  115.     @property
  116.     def volume(self):
  117.         "Retrueves the current volume level"
  118.         vol = _B.BASSMOD_GetVolume()
  119.         if vol == -1: bass_error()
  120.         return vol
  121.     @volume.setter
  122.     def volume(self, vol):
  123.         "Sets the digital output master volume"
  124.         if not (0 <= vol <= 100):
  125.             raise ValueError("Invalid volume level %r.  Must be 0..100" % vol)
  126.         if not _B.BASSMOD_SetVolume(int(vol)):
  127.             bass_error()
  128.  
  129.     def music_decode(self, length):
  130.         "Gets decoded sample data from the MOD music"
  131.         buff = ctypes.create_string_buffer('\x00', length)
  132.         buff_p = ctypes.pointer(buff)
  133.         size = _B.BASSMOD_MusicDecode(buff_p, length)
  134.  
  135.         if size == -1: bass_error()
  136.         return buff.raw[:size]
  137.  
  138.     def music_free(self):
  139.         "Frees the MOD music's resources"
  140.         _B.BASSMOD_MusicFree()
  141.  
  142.     @property
  143.     def order_length(self):
  144.         size = _B.BASSMOD_MusicGetLength(0)
  145.         if size == -1:
  146.             bass_error()
  147.         return size
  148.     @property
  149.     def playback_length(self):
  150.         size = _B.BASSMOD_MusicGetLength(1)
  151.         if size == -1:
  152.             bass_error()
  153.         return size
  154.  
  155.     @property
  156.     def name(self):
  157.         "Retrieves the MOD music's name"
  158.         name = ctypes.c_char_p(_B.BASSMOD_MusicGetName()).value
  159.         if name is None: bass_error()
  160.         return name
  161.  
  162.     @property
  163.     def position(self):
  164.         "Retrieves the playback position of the MOD music.  Returns a Position()"
  165.         pos = _B.BASSMOD_MusicGetPosition()
  166.         if pos == -1: bass_error()
  167.         return Position(pos & 0xFFFF, (pos >> 16) * self._position_scaler)
  168.     @position.setter
  169.     def position(self, pos):
  170.         if not _B.BASSMOD_MusicSetPosition(int(pos)):
  171.             bass_error()
  172.  
  173.     # This is kind of annoying.  Maybe we should have a bunch of channel objects... yuck.
  174.     def channel_volume(self, channel):
  175.         "Retrieves the volume level of a channel in the MOD music."
  176.         vol = _B.BASSMOD_MusicGetVolume(channel & 0xFFFF)
  177.         if vol == -1: bass_error()
  178.         return vol
  179.     def instrument_volume(self, instrument):
  180.         "Retrieves the volume level of an instrument in the MOD music."
  181.         vol = _B.BASSMOD_MusicGetVolume(channel | 0x10000)
  182.         if vol == -1: bass_error()
  183.         return vol
  184.     def set_channel_volume(self, channel, vol):
  185.         "Sets the volume level of a channel in the MOD music."
  186.         if not (0 <= vol <= 100):
  187.             raise ValueError("Invalid volume %r.  Valid range is 0..100" % vol)
  188.         if not _B.BASSMOD_MusicSetVolume(channel & 0xFFFF, vol):
  189.             bass_error()
  190.     def instrument_volume(self, instrument):
  191.         "Retrieves the volume level of an instrument in the MOD music."
  192.         if not (0 <= vol <= 100):
  193.             raise ValueError("Invalid volume %r.  Valid range is 0..100" % vol)
  194.         if not _B.BASSMOD_MusicSetVolume(channel | 0x10000, vol):
  195.             bass_error()
  196.  
  197.     @property
  198.     def position_scaler(self):
  199.         return self._position_scaler
  200.     @position_scaler.setter
  201.     def position_scaler(self, scale):
  202.         if not (0 <= scale <= 256):
  203.             raise ValueError("Invalid position scaler %r.  Valid range is 0..256" % scale)
  204.         if not _B.BASSMOD_MusicSetPositionScaler(int(scale)):
  205.             bass_error()
  206.         self._position_scaler = int(scale)
  207.  
  208.     @property
  209.     def status(self):
  210.         return _B.BASSMOD_MusicIsActive()
  211.  
  212.     @property
  213.     def stop(self):
  214.         return self.status == STOPPED
  215.     @stop.setter
  216.     def stop(self, val):
  217.         if val and (not _B.BASSMOD_MusicStop()):
  218.             bass_error()
  219.  
  220.     @property
  221.     def play(self):
  222.         return self.status == PLAYING
  223.     @play.setter
  224.     def play(self, val):
  225.         if val and (not _B.BASSMOD_MusicPlay()):
  226.             bass_error()
  227.     def play_at_position(self, order, row, flags=-1, reset=False):
  228.         "Plays the MOD music, using the specified start position and flags."
  229.         pos = (row << 16) + order
  230.         if not _B.BASSMOD_MusicPlayEx(pos, flags, ctypes.c_bool(reset)):
  231.             bass_error()
  232.     def play_at_time(self, seconds, flags=-1, reset=False):
  233.         "Plays the MOD music, using the specified start position in integer seconds and flags."
  234.         pos = int(seconds) | 0xFFFF0000
  235.         if not _B.BASSMOD_MusicPlayEx(pos, flags, ctypes.c_bool(reset)):
  236.             bass_error()
  237.     def play_flags(self, flags):
  238.         "Set flags on MOD music from current position"
  239.         if not _B.BASSMOD_MusicPlayEx(0xFFFFFFFF, flags, False):
  240.             bass_error()
  241.  
  242.     @property
  243.     def pause(self):
  244.         return self.status == PAUSED
  245.     @pause.setter
  246.     def pause(self, val):
  247.         if val and (not _B.BASSMOD_MusicPause()):
  248.             bass_error()
  249.  
  250.     def load(self, music, offset=0, length=0, flags=0):
  251.         "Loads MOD music."
  252.         if isinstance(music, str):
  253.             mem = False
  254.             music_p = music
  255.         else:
  256.             mem = True
  257.             music_p = ctypes.c_char_p(music)
  258.  
  259.         result = _B.BASSMOD_MusicLoad(mem, music_p, offset, length, flags)
  260.         if not result: bass_error()
  261.         return True
  262.  
  263.     @property
  264.     def amplify(self):
  265.         return self._amplfy
  266.     @amplify.setter
  267.     def amplify(self, amp):
  268.         "Sets the MOD music's amplification level."
  269.         if not (0 <= amp <= 100):
  270.             raise ValueError("Invalid amplification level %r.  Must be 0..100" % amp)
  271.         if not _B.BASSMOD_MusicSetAmplify(amp):
  272.             bass_error()
  273.         self._amplify = amp
  274.  
  275.     @property
  276.     def pan_seperation(self):
  277.         return self._pan_seperation
  278.     @pan_seperation.setter
  279.     def pan_seperation(self, pan):
  280.         "Sets the MOD music's pan seperation level."
  281.         if not (0 <= pan <= 100):
  282.             raise ValueError("Invalid pan seperation level %r.  Must be 0..100" % pan)
  283.         if not _B.BASSMOD_MusicSetPanSep(pan):
  284.             bass_error()
  285.         self._pan_seperation = pan
  286.            
  287. class _Sync(object):
  288.     def __init__(self, func, sync_type, param, user=0, one_time=False):
  289.         self.callback = SYNCPROC(func)
  290.         sync = sync_type | (one_time * SYNC_ONETIME)
  291.         self.handle = _B.BASSMOD_MusicSetSync(sync, param, self.callback, user)
  292.         if self.handle == 0:
  293.             bass_error()
  294.  
  295.     def __del__(self):
  296.         if self.handle:
  297.             result = _B.BASSMOD_MusicRemoveSync(self.handle)
  298.             #if not _B.BASSMOD_MusicRemoveSync(self.handle):
  299.             #    bass_error(message="Error while removing sync")
  300.  
  301. class SyncPos(_Sync):
  302.     def __init__(self, func, position, user, one_time=False):
  303.         if not isinstance(position, Position):
  304.             raise ValueError("Sync position must be a Position()")
  305.         super(SyncPos, self).__init__(func, SYNC_POS, int(position), user, one_time)
  306.  
  307. class SyncEnd(_Sync):
  308.     def __init__(self, func, user, one_time=False):
  309.         super(SyncEnd, self).__init__(func, SYNC_END, 0, user, one_time)
  310.  
  311. class SyncMusicInst(_Sync):
  312.     def __init__(self, func, instrument, note, user, one_time=False):
  313.         if instrument < 1:
  314.             raise ValueError("Instrument number must be > 0")
  315.         elif not (-1 <= note <= 119):
  316.             raise ValueError("Note must be 0..119 or -1")
  317.         inst_note = (note << 16) + instrument
  318.         super(SyncMusicInst, self).__init__(func, SYNC_MUSICINST, inst_note, user, one_time)
  319.  
  320. class SyncMusicFX(_Sync):
  321.     def __init__(self, func, value_or_position, user, one_time=False):
  322.         """If value_or_position == True, value of sync is passed to callback.  Otherwise a
  323.        position tuple (row, order) is sent"""
  324.         param = 1 if value_or_position else 0
  325.         super(SyncMusicFX, self).__init__(func, SYNC_MUSICFX, param, user, one_time)
  326.  
  327. class Position(object):
  328.     def __init__(self, order, row):
  329.         self.order = order & 0xFFFF
  330.         self.row = row & 0xFFFF
  331.     def __int__(self):
  332.         return (self.row << 16) + self.order
  333.  
  334. def active():
  335.     "Checks if the MOD music is active (playing)."
  336.     return _B.BASSMOD_MusicIsActive()
  337.  
  338. def device_description(devnum, raise_on_invalid=False):
  339.     "Retrieves the text description of a device.  Always NONE in Linux"
  340.     desc = _B.BASSMOD_GetDeviceDescription(int(devnum))
  341.     if raise_on_invalid and (not desc):
  342.         bass_error()
  343.     return desc
  344.  
  345. def bass_error(message=None, force_exception=False):
  346.     err_id = _B.BASSMOD_ErrorGetCode()
  347.     if err_id or force_exception:
  348.         msg = "" if message is None else str(message)
  349.         raise BassError(msg + BASS_ERRORS[err_id])
  350.  
  351. if __name__ == '__main__':
  352.     import sys
  353.     import time
  354.  
  355.     START_TIME = time.time()
  356.     def player(song):
  357.         # Demo player similar to 'contest.c'
  358.         global START_TIME
  359.  
  360.         def loop_sync(handle, data, user):
  361.             global START_TIME
  362.             START_TIME = time.time()
  363.  
  364.         with Bass() as bass:
  365.             flags = MUSIC_LOOP | MUSIC_RAMPS | MUSIC_SURROUND | MUSIC_CALCLEN
  366.             bass.load(song, flags=flags)
  367.  
  368.             sync = SyncEnd(loop_sync, 0)
  369.             sys.stdout.write("Playing %s [%d orders]" % (bass.name, bass.order_length))
  370.             try:
  371.                 t = bass.playback_length / 176400
  372.                 sys.stdout.write(" %d:%02d\n" % (t/60, t%60))
  373.             except BassError:
  374.                 sys.stdout.write("\n")
  375.  
  376.             bass.play = True
  377.             START_TIME = time.time()
  378.             try:
  379.                 while bass.play:
  380.                     pos = bass.position
  381.                     t = time.time() - START_TIME
  382.                     order, row = pos.order, pos.row
  383.                     cpu = bass.cpu
  384.                     sys.stdout.write("pos: %03d,%03d - time: %d:%02d - cpu: %.2f%%   \r" % (order, row, t/60, t%60, cpu))
  385.                     sys.stdout.flush()
  386.                     time.sleep(0.005)
  387.             except KeyboardInterrupt:
  388.                 # For the purposes of the demonstration, you must break out of the player
  389.                 # with a keybroard interrupt.  
  390.                 pass
  391.             sys.stdout.write((" " * 60) + "\n")
  392.  
  393.     if len(sys.argv) < 2:
  394.         sys.stderr.write("USAGE: %s <file_name>\n" % os.path.basename(sys.argv[0]))
  395.         sys.exit(1)
  396.     player(sys.argv[1])
Advertisement
Add Comment
Please, Sign In to add comment