package Audio; import lombok.Getter; import org.tinylog.Logger; import java.io.File; public class AudioPlayer { private final Bass bass; private int deviceid = -1; @Getter private boolean inited = false; int playbackhandle = 0; float playbackvolume = 1.0f; public AudioPlayer(){ bass = Bass.Instance; Logger.info("Bass Version = {}", Integer.toHexString(bass.BASS_GetVersion())); } /** * Unload Bass */ public void Unload(){ if (inited){ Logger.info("Freeing Device {}", deviceid); bass.BASS_SetDevice(deviceid); bass.BASS_Free(); } deviceid = -1; inited = false; } /** * Detect Output Devices */ public void DetectOutputDevices(){ Logger.info("Detecting Output Devices..."); int ii = 1; while (true){ Bass.BASS_DEVICEINFO info = new Bass.BASS_DEVICEINFO(); if (bass.BASS_GetDeviceInfo(ii, info)){ Logger.info("Device {} = {}, flags = {}", ii, info.name, Integer.toHexString(info.flags)); ii++; } else { break; } } } /** * Get BASS_DEVICEINFO for a device * @param device device id * @return BASS_DEVICEINFO object or null if failed */ public Bass.BASS_DEVICEINFO GetDeviceInfo(int device){ Bass.BASS_DEVICEINFO info = new Bass.BASS_DEVICEINFO(); if (bass.BASS_GetDeviceInfo(device, info)){ return info; } return null; } /** * Find Device ID with Name * @param name device name * @return device id, or -1 if not found */ public int FindDeviceIDWithName(String name){ int result = -1; int ii = 1; while(true){ Bass.BASS_DEVICEINFO info = new Bass.BASS_DEVICEINFO(); if (bass.BASS_GetDeviceInfo(ii, info)){ if (info.name.contains(name)){ result = ii; break; } ii++; } else { // gak ada lagi break; } } return result; } /** * Open Output Device * @param device device id, starts from 1 * @param freq output frequency * @return true if success */ public boolean OpenDevice(int device, int freq){ int flag = Bass.BASS_DEVICE_REINIT | Bass.BASS_DEVICE_16BITS | Bass.BASS_DEVICE_MONO | Bass.BASS_DEVICE_FREQ; boolean success = bass.BASS_Init(device, freq, flag); if (success){ Logger.info("Device {} opened successfully", device); deviceid = device; inited = true; } else { Logger.error("Failed to open device {}, Error Code {}", device, bass.BASS_ErrorGetCode()); } return success; } /** * Set Master Output Volume * Master Output Volume related to the system volume (Alsamixer on Linux, Volume Mixer on Windows) * @param value volume value, 0-100 */ public void setMasterVolume(int value){ if (value<0) value = 0; if (value>100) value = 100; if (inited){ bass.BASS_SetDevice(deviceid); if (!bass.BASS_SetVolume(value/100.0f)){ Logger.error("Failed to set Master Volume to {}, Error Code {}", value, bass.BASS_ErrorGetCode()); } else Logger.info("Master Volume set to {}", value); } else Logger.info("AudioPlayer not initialized"); } /** * Get Master Output Volume * Master Output Volume related to the system volume (Alsamixer on Linux, Volume Mixer on Windows) * @return 0 - 100, or -1 if failed */ @SuppressWarnings("unused") public int getMasterVolume(){ if (inited){ bass.BASS_SetDevice(deviceid); float vol = bass.BASS_GetVolume(); if (vol>=0 && vol<=1){ return (int)(vol*100); } } else Logger.info("AudioPlayer not initialized"); return -1; } /** * Get Playback Volume * is the volume of the currently playing audio file * @return 0 - 100 */ public int getPlaybackvolume(){ if (playbackvolume<0) return 0; else if (playbackvolume>1.0f) return 100; else return (int)(playbackvolume*100); } /** * Set Playback Volume * is the volume of the currently playing audio file * @param value 0 - 100 */ public void setPlaybackvolume(int value){ if (value<0) value = 0; if (value>100) value = 100; playbackvolume = value/100.0f; if (playbackhandle!=0){ bass.BASS_ChannelSetAttribute(playbackhandle, Bass.BASS_ATTRIB_VOL, playbackvolume); } } private float lastplaybackvolume = 1.0f; /** * Set Playback Volume to 0 */ public void Mute(){ lastplaybackvolume = playbackvolume; setPlaybackvolume(0); } /** * Set Playback Volume to last volume before Mute */ public void Unmute(){ setPlaybackvolume((int)lastplaybackvolume*100); } /** * Open Audio File * @param ff audio file name to open * @return AudioFileProperties object or null if failed */ public AudioFileProperties OpenAudioFile(File ff){ if (inited){ int handle = bass.BASS_StreamCreateFile(false, ff.getAbsolutePath(), 0, 0,0); if (handle!=0){ Logger.info("Audio file {} opened successfully", ff.getName()); AudioFileProperties prop = new AudioFileProperties(handle, ff.getName()); prop.Length = bass.BASS_ChannelGetLength(handle, Bass.BASS_POS_BYTE); prop.duration = bass.BASS_ChannelBytes2Seconds(handle, prop.Length); return prop; } else Logger.error("Failed to open audio file {}, Error Code {}", ff.getAbsolutePath(), bass.BASS_ErrorGetCode()); } else Logger.info("AudioPlayer not initialized"); return null; } /** * Close Audio File * @param prop AudioFileProperties object to close */ public void CloseAudioFile(AudioFileProperties prop) { playbackhandle = 0; if (inited) { if (prop != null) { if (!bass.BASS_StreamFree(prop.handle)) { { Logger.error("Failed to close audio file {}, Error Code {}", prop.filename, bass.BASS_ErrorGetCode()); } } else Logger.info("Audio file {} closed successfully", prop.filename); } else Logger.info("AudioPlayer not initialized"); } } /** * Play Audio File * @param prop AudioFileProperties object to play * @param looping set true to loop the audio file * @param event PlaybackEvent object to handle playback event */ public void PlayAudioFile(AudioFileProperties prop, boolean looping, PlaybackEvent event){ if (inited){ if (prop!=null){ if (bass.BASS_ChannelStart(prop.handle)){ playbackhandle = prop.handle; bass.BASS_ChannelSetAttribute(prop.handle, Bass.BASS_ATTRIB_VOL, playbackvolume); if (looping) bass.BASS_ChannelFlags(prop.handle, Bass.BASS_SAMPLE_LOOP, Bass.BASS_SAMPLE_LOOP); if (event!=null) event.onPlaybackStart(prop); int devfailsync = bass.BASS_ChannelSetSync(prop.handle, Bass.BASS_SYNC_DEV_FAIL,0, (handle, channel, data, user)->{ if (event!=null) event.onPlaybackFailure(prop, "Device Failure"); }, null); if (devfailsync==0) Logger.error("Failed to set Device Failure Sync, Error Code {}", bass.BASS_ErrorGetCode()); int endsync = bass.BASS_ChannelSetSync(prop.handle, Bass.BASS_SYNC_END, 0, (handle, channel, data, user)->{ if (looping) { if (event!=null) event.onPlaybackLooped(prop); } else if (event!=null) event.onPlaybackFinished(prop); }, null); if (endsync==0) Logger.error("Failed to set End Sync, Error Code {}", bass.BASS_ErrorGetCode()); } else { if (event!=null) event.onPlaybackFailure(prop, String.format("Failed to play audio file %s, Error Code %d", prop.filename, bass.BASS_ErrorGetCode())); } } else { if (event!=null) event.onPlaybackFailure(null, "AudioFileProperties is null"); } } else { if (event!=null) event.onPlaybackFailure(prop, "AudioPlayer not initialized"); } } }