Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import os
- import ctypes
- from collections import namedtuple
- # Make sure you have BASSMOD installed:
- # http://www.un4seen.com/
- _B = ctypes.cdll.LoadLibrary('./libbassmod.so')
- #_B = ctypes.windll.LoadLibrary('BASSMOD.DLL')
- BASS_ERRORS = {0: "BASS_OK: No error",
- 1: "BASS_ERROR_MEM: Memory error",
- 2: "BASS_ERROR_FILEOPEN: Cannot open file",
- 3: "BASS_ERROR_DRIVER: Cannot find a free/valid driver",
- 5: "BASS_ERROR_HANDLE: Invalid handle",
- 6: "BASS_ERROR_FORMAT: Unsupported format",
- 7: "BASS_ERROR_POSITION: Invalid playback position",
- 8: "BASS_ERROR_INIT: Init has not been successfully called",
- 14: "BASS_ERROR_ALREADY: Already initialized/loaded",
- 19: "BASS_ERROR_ILLTYPE: An illegal type was specified",
- 20: "BASS_ERROR_ILLPARAM: An illegal parameter was specified",
- 23: "BASS_ERROR_DEVICE: Illegal device number",
- 24: "BASS_ERROR_NOPLAY: Not playing",
- 28: "BASS_ERROR_NOMUSIC: No MOD music has been loaded",
- 30: "BASS_ERROR_NOSYNC: Synchronizers have been disabled",
- 37: "BASS_ERROR_NOTAVAIL: Requested data is not available",
- 38: "BASS_ERROR_DECODE: The channel is a 'decoding channel'",
- 41: "BASS_ERROR_FILEFORM: Unsupported file format",
- -1: "BASS_ERROR_UNKNOWN: Some other mystery error",
- }
- # Active States
- STOPPED = 0
- PLAYING = 1
- PAUSED = 3
- # Device Flags
- DEVICE_8BITS = 1 # use 8 bit resolution, else 16 bit
- DEVICE_MONO = 2 # use mono, else stereo
- DEVICE_NOSYNC= 16 # disable synchronizers
- # Music Flags
- MUSIC_RAMP = 1 # normal ramping
- MUSIC_RAMPS = 2 # sensitive ramping
- MUSIC_LOOP = 4 # loop music
- MUSIC_FT2MOD = 16 # play .MOD as FastTracker 2 does
- MUSIC_PT1MOD = 32 # play .MOD as ProTracker 1 does
- MUSIC_POSRESET = 256 # stop all notes when moving position
- MUSIC_SURROUND = 512 # surround sound^M
- MUSIC_SURROUND2 = 1024 # surround sound (mode 2)^M
- MUSIC_STOPBACK = 2048 # stop the music on a backwards jump effect^M
- MUSIC_CALCLEN = 8192 # calculate playback length^M
- MUSIC_NONINTER = 16384 # non-interpolated mixing^M
- MUSIC_NOSAMPLE = 0x400000 # don't load the samples^M
- # Special Unicode Flag
- UNICODE_FLAG = 0x80000000
- # Sync Flags
- SYNC_MUSICPOS = 0
- SYNC_POS = 0
- SYNC_MUSICINST = 1
- SYNC_END = 2
- SYNC_MUSICFX = 3
- SYNC_ONETIME = 0x80000000 # sync only once, else continuously
- _B.BASSMOD_GetCPU.restype = ctypes.c_float
- SYNCPROC = ctypes.CFUNCTYPE(None, ctypes.c_long, ctypes.c_long, ctypes.c_long)
- __version__ = _B.BASSMOD_GetVersion()
- class BassError(Exception): pass
- class Bass(object):
- def __init__(self, device=-1, freq=44100, flags=0, init=True):
- self.config = namedtuple('Config', 'device freq flags')(device, freq, flags)
- self.init = False
- self._position_scaler = 1
- self._amplify = 50
- self._pan_seperation = 50
- if init:
- self.start()
- # For use with 'with Bass(...) as X:'
- def __enter__(self):
- if not self.init:
- bass_error()
- return self
- def __exit__(self, type, value, traceback):
- self.free()
- bass_error()
- def __del__(self):
- self.free()
- def start(self):
- self.init = bool(_B.BASSMOD_Init(self.config.device, self.config.freq, self.config.flags))
- @property
- def error_code(self):
- "Returns the BASSMOD error code. Lookup using BassError() function"
- return _B.BASSMOD_ErrorGetCode()
- def free(self):
- "Frees all resources used by the digial output, including the MOD music."
- _B.BASSMOD_Free()
- @property
- def cpu(self):
- "Retrieves the current CPU useage of BASSMOD in floating point percent"
- return _B.BASSMOD_GetCPU()
- @property
- def volume(self):
- "Retrueves the current volume level"
- vol = _B.BASSMOD_GetVolume()
- if vol == -1: bass_error()
- return vol
- @volume.setter
- def volume(self, vol):
- "Sets the digital output master volume"
- if not (0 <= vol <= 100):
- raise ValueError("Invalid volume level %r. Must be 0..100" % vol)
- if not _B.BASSMOD_SetVolume(int(vol)):
- bass_error()
- def music_decode(self, length):
- "Gets decoded sample data from the MOD music"
- buff = ctypes.create_string_buffer('\x00', length)
- buff_p = ctypes.pointer(buff)
- size = _B.BASSMOD_MusicDecode(buff_p, length)
- if size == -1: bass_error()
- return buff.raw[:size]
- def music_free(self):
- "Frees the MOD music's resources"
- _B.BASSMOD_MusicFree()
- @property
- def order_length(self):
- size = _B.BASSMOD_MusicGetLength(0)
- if size == -1:
- bass_error()
- return size
- @property
- def playback_length(self):
- size = _B.BASSMOD_MusicGetLength(1)
- if size == -1:
- bass_error()
- return size
- @property
- def name(self):
- "Retrieves the MOD music's name"
- name = ctypes.c_char_p(_B.BASSMOD_MusicGetName()).value
- if name is None: bass_error()
- return name
- @property
- def position(self):
- "Retrieves the playback position of the MOD music. Returns a Position()"
- pos = _B.BASSMOD_MusicGetPosition()
- if pos == -1: bass_error()
- return Position(pos & 0xFFFF, (pos >> 16) * self._position_scaler)
- @position.setter
- def position(self, pos):
- if not _B.BASSMOD_MusicSetPosition(int(pos)):
- bass_error()
- # This is kind of annoying. Maybe we should have a bunch of channel objects... yuck.
- def channel_volume(self, channel):
- "Retrieves the volume level of a channel in the MOD music."
- vol = _B.BASSMOD_MusicGetVolume(channel & 0xFFFF)
- if vol == -1: bass_error()
- return vol
- def instrument_volume(self, instrument):
- "Retrieves the volume level of an instrument in the MOD music."
- vol = _B.BASSMOD_MusicGetVolume(channel | 0x10000)
- if vol == -1: bass_error()
- return vol
- def set_channel_volume(self, channel, vol):
- "Sets the volume level of a channel in the MOD music."
- if not (0 <= vol <= 100):
- raise ValueError("Invalid volume %r. Valid range is 0..100" % vol)
- if not _B.BASSMOD_MusicSetVolume(channel & 0xFFFF, vol):
- bass_error()
- def instrument_volume(self, instrument):
- "Retrieves the volume level of an instrument in the MOD music."
- if not (0 <= vol <= 100):
- raise ValueError("Invalid volume %r. Valid range is 0..100" % vol)
- if not _B.BASSMOD_MusicSetVolume(channel | 0x10000, vol):
- bass_error()
- @property
- def position_scaler(self):
- return self._position_scaler
- @position_scaler.setter
- def position_scaler(self, scale):
- if not (0 <= scale <= 256):
- raise ValueError("Invalid position scaler %r. Valid range is 0..256" % scale)
- if not _B.BASSMOD_MusicSetPositionScaler(int(scale)):
- bass_error()
- self._position_scaler = int(scale)
- @property
- def status(self):
- return _B.BASSMOD_MusicIsActive()
- @property
- def stop(self):
- return self.status == STOPPED
- @stop.setter
- def stop(self, val):
- if val and (not _B.BASSMOD_MusicStop()):
- bass_error()
- @property
- def play(self):
- return self.status == PLAYING
- @play.setter
- def play(self, val):
- if val and (not _B.BASSMOD_MusicPlay()):
- bass_error()
- def play_at_position(self, order, row, flags=-1, reset=False):
- "Plays the MOD music, using the specified start position and flags."
- pos = (row << 16) + order
- if not _B.BASSMOD_MusicPlayEx(pos, flags, ctypes.c_bool(reset)):
- bass_error()
- def play_at_time(self, seconds, flags=-1, reset=False):
- "Plays the MOD music, using the specified start position in integer seconds and flags."
- pos = int(seconds) | 0xFFFF0000
- if not _B.BASSMOD_MusicPlayEx(pos, flags, ctypes.c_bool(reset)):
- bass_error()
- def play_flags(self, flags):
- "Set flags on MOD music from current position"
- if not _B.BASSMOD_MusicPlayEx(0xFFFFFFFF, flags, False):
- bass_error()
- @property
- def pause(self):
- return self.status == PAUSED
- @pause.setter
- def pause(self, val):
- if val and (not _B.BASSMOD_MusicPause()):
- bass_error()
- def load(self, music, offset=0, length=0, flags=0):
- "Loads MOD music."
- if isinstance(music, str):
- mem = False
- music_p = music
- else:
- mem = True
- music_p = ctypes.c_char_p(music)
- result = _B.BASSMOD_MusicLoad(mem, music_p, offset, length, flags)
- if not result: bass_error()
- return True
- @property
- def amplify(self):
- return self._amplfy
- @amplify.setter
- def amplify(self, amp):
- "Sets the MOD music's amplification level."
- if not (0 <= amp <= 100):
- raise ValueError("Invalid amplification level %r. Must be 0..100" % amp)
- if not _B.BASSMOD_MusicSetAmplify(amp):
- bass_error()
- self._amplify = amp
- @property
- def pan_seperation(self):
- return self._pan_seperation
- @pan_seperation.setter
- def pan_seperation(self, pan):
- "Sets the MOD music's pan seperation level."
- if not (0 <= pan <= 100):
- raise ValueError("Invalid pan seperation level %r. Must be 0..100" % pan)
- if not _B.BASSMOD_MusicSetPanSep(pan):
- bass_error()
- self._pan_seperation = pan
- class _Sync(object):
- def __init__(self, func, sync_type, param, user=0, one_time=False):
- self.callback = SYNCPROC(func)
- sync = sync_type | (one_time * SYNC_ONETIME)
- self.handle = _B.BASSMOD_MusicSetSync(sync, param, self.callback, user)
- if self.handle == 0:
- bass_error()
- def __del__(self):
- if self.handle:
- result = _B.BASSMOD_MusicRemoveSync(self.handle)
- #if not _B.BASSMOD_MusicRemoveSync(self.handle):
- # bass_error(message="Error while removing sync")
- class SyncPos(_Sync):
- def __init__(self, func, position, user, one_time=False):
- if not isinstance(position, Position):
- raise ValueError("Sync position must be a Position()")
- super(SyncPos, self).__init__(func, SYNC_POS, int(position), user, one_time)
- class SyncEnd(_Sync):
- def __init__(self, func, user, one_time=False):
- super(SyncEnd, self).__init__(func, SYNC_END, 0, user, one_time)
- class SyncMusicInst(_Sync):
- def __init__(self, func, instrument, note, user, one_time=False):
- if instrument < 1:
- raise ValueError("Instrument number must be > 0")
- elif not (-1 <= note <= 119):
- raise ValueError("Note must be 0..119 or -1")
- inst_note = (note << 16) + instrument
- super(SyncMusicInst, self).__init__(func, SYNC_MUSICINST, inst_note, user, one_time)
- class SyncMusicFX(_Sync):
- def __init__(self, func, value_or_position, user, one_time=False):
- """If value_or_position == True, value of sync is passed to callback. Otherwise a
- position tuple (row, order) is sent"""
- param = 1 if value_or_position else 0
- super(SyncMusicFX, self).__init__(func, SYNC_MUSICFX, param, user, one_time)
- class Position(object):
- def __init__(self, order, row):
- self.order = order & 0xFFFF
- self.row = row & 0xFFFF
- def __int__(self):
- return (self.row << 16) + self.order
- def active():
- "Checks if the MOD music is active (playing)."
- return _B.BASSMOD_MusicIsActive()
- def device_description(devnum, raise_on_invalid=False):
- "Retrieves the text description of a device. Always NONE in Linux"
- desc = _B.BASSMOD_GetDeviceDescription(int(devnum))
- if raise_on_invalid and (not desc):
- bass_error()
- return desc
- def bass_error(message=None, force_exception=False):
- err_id = _B.BASSMOD_ErrorGetCode()
- if err_id or force_exception:
- msg = "" if message is None else str(message)
- raise BassError(msg + BASS_ERRORS[err_id])
- if __name__ == '__main__':
- import sys
- import time
- START_TIME = time.time()
- def player(song):
- # Demo player similar to 'contest.c'
- global START_TIME
- def loop_sync(handle, data, user):
- global START_TIME
- START_TIME = time.time()
- with Bass() as bass:
- flags = MUSIC_LOOP | MUSIC_RAMPS | MUSIC_SURROUND | MUSIC_CALCLEN
- bass.load(song, flags=flags)
- sync = SyncEnd(loop_sync, 0)
- sys.stdout.write("Playing %s [%d orders]" % (bass.name, bass.order_length))
- try:
- t = bass.playback_length / 176400
- sys.stdout.write(" %d:%02d\n" % (t/60, t%60))
- except BassError:
- sys.stdout.write("\n")
- bass.play = True
- START_TIME = time.time()
- try:
- while bass.play:
- pos = bass.position
- t = time.time() - START_TIME
- order, row = pos.order, pos.row
- cpu = bass.cpu
- sys.stdout.write("pos: %03d,%03d - time: %d:%02d - cpu: %.2f%% \r" % (order, row, t/60, t%60, cpu))
- sys.stdout.flush()
- time.sleep(0.005)
- except KeyboardInterrupt:
- # For the purposes of the demonstration, you must break out of the player
- # with a keybroard interrupt.
- pass
- sys.stdout.write((" " * 60) + "\n")
- if len(sys.argv) < 2:
- sys.stderr.write("USAGE: %s <file_name>\n" % os.path.basename(sys.argv[0]))
- sys.exit(1)
- player(sys.argv[1])
Advertisement
Add Comment
Please, Sign In to add comment