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 ( ' \x 00' , 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

" % ( t/ 60 , t% 60 ) )

except BassError:

sys . stdout . write ( "

" )

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 ) + "

" )

if len ( sys . argv ) < 2 :

sys . stderr . write ( "USAGE: %s <file_name>

" % os . path . basename ( sys . argv [ 0 ] ) )

sys . exit ( 1 )