package audio import audio.Bass.BASS_DEVICE_ENABLED import audio.Bass.BASS_DEVICE_INIT import audio.Bass.BASS_POS_BYTE import audio.Bass.BASS_STREAM_DECODE import audio.Bass.BASS_SAMPLE_MONO import audio.BassEnc.BASS_ENCODE_PCM import codes.Result_Boolean_String import codes.Somecodes.Companion.ValidFile import codes.Somecodes.Companion.ValidString import com.sun.jna.Memory import com.sun.jna.Pointer import contentCache import org.tinylog.Logger @Suppress("unused") class AudioPlayer (var samplingrate: Int) { val bass: Bass = Bass.Instance val bassenc : BassEnc = BassEnc.Instance var initedDevice = -1 init { if (samplingrate<1) samplingrate = 44100 // Default sampling rate Logger.info {"Bass version ${Integer.toHexString(bass.BASS_GetVersion())}"} Logger.info { "BassEnc version ${Integer.toHexString(bassenc.BASS_Encode_GetVersion())}" } InitAudio(0) // Audio 0 is No Sound, use for reading and writing wav silently } /** * Initializes the audio system with the specified device ID. * Call it before using any audio functions. * @param id The device ID to initialize. * @return True if initialization was successful, false otherwise. */ fun InitAudio(id : Int) : Boolean { val bdi = Bass.BASS_DEVICEINFO() if (bass.BASS_GetDeviceInfo(id, bdi)){ Logger.info { "Audio ID=$id Name=${bdi.name} Driver=${bdi.driver}" } if (bdi.flags and BASS_DEVICE_ENABLED == 0) { Logger.error { "Audio ID=$id is not enabled, cannot initialize" } return false } if (bdi.flags and BASS_DEVICE_INIT > 0){ Logger.info { "Audio ID=$id is already initialized" } if (id > 0) { initedDevice = id Logger.info { "Real Audio Device reused ID=$initedDevice" } } return true } } if (bass.BASS_Init(id, 48000, 0)){ Logger.info { "Audio ID=$id inited succesfully" } if (id > 0) { initedDevice = id Logger.info { "Real Audio Device used ID=$initedDevice" } } return true } else { Logger.error { "Audio ID=$id initialization failed: ${bass.BASS_ErrorGetCode()}" } return false } } /** * Uninitializes the audio system if it was previously initialized. */ fun Close(){ if (initedDevice != -1) { bass.BASS_SetDevice(initedDevice) if (bass.BASS_Free()) { Logger.info {"Audio ID=$initedDevice uninitialized successfully"} } else { Logger.error { "Audio ID=$initedDevice uninitialization failed: ${bass.BASS_ErrorGetCode()}" } } initedDevice = -1 } } /** * Loads an audio file and retrieves its information. * Check for isValid() method in AudioFileInfo class to verify the loaded file. * @param fileName The name of the audio file to load. * @return An AudioFileInfo object containing the file information. */ fun LoadAudioFile(fileName: String) : AudioFileInfo { val result = AudioFileInfo() if (ValidFile(fileName)){ result.fileName = fileName bass.BASS_SetDevice(0) // Set to No Sound device for reading val handle = bass.BASS_StreamCreateFile(false, fileName, 0L, 0L, BASS_STREAM_DECODE or BASS_SAMPLE_MONO) if (handle!=0){ // successfully opened the file // read file size val size = bass.BASS_ChannelGetLength(handle, BASS_POS_BYTE) if (size > 0) { result.fileSize = size } // read file duration val duration = bass.BASS_ChannelBytes2Seconds(handle, size) if (duration > 0) { result.duration = duration } // read file bytes val mem = Memory(size) val bytesRead = bass.BASS_ChannelGetData(handle, mem, size.toInt()) if (bytesRead > 0) { result.bytes = mem.getByteArray(0, bytesRead) } // close the handle bass.BASS_StreamFree(handle) } } return result } /** * Writes the audio data from a byte array to a WAV file. * @param data The byte array containing the audio data. * @param target The target file name for the WAV file. * @param withChime If true, adds a chime sound at the beginning and end of the audio. * @return A Result_Boolean_String indicating success or failure and a message. */ fun WavWriter(data: ByteArray, target: String, withChime: Boolean = true) : Result_Boolean_String { bass.BASS_SetDevice(0) // Set to No Sound device for writing val streamhandle = bass.BASS_StreamCreate(samplingrate, 1, BASS_STREAM_DECODE, Pointer(-1), null) if (streamhandle!=0){ val encodehandle = bassenc.BASS_Encode_Start(streamhandle, target, BASS_ENCODE_PCM, null, null) if (encodehandle!=0){ fun pushData(data: ByteArray): Boolean { val mem = Memory(data.size.toLong()) mem.write(0, data, 0, data.size) val pushresult = bass.BASS_StreamPutData(streamhandle, mem, data.size) if (pushresult==-1){ Logger.error { "BASS_StreamPutData failed: ${bass.BASS_ErrorGetCode()}" } } return pushresult != -1 } var all_success = true if (withChime){ val chup = contentCache.getAudioFile("chimeup") if (chup!=null && chup.isValid()){ if (!pushData(chup.bytes)){ all_success = false Logger.error { "Failed to push Chime Up" } } } else Logger.error { "withChime=true, but Chime Up not available" } } if (!pushData(data)){ all_success = false Logger.error { "Failed to push Data ByteArray" } } if (withChime){ val chdn = contentCache.getAudioFile("chimedown") if (chdn!=null && chdn.isValid()){ if (!pushData(chdn.bytes)){ all_success = false Logger.error { "Failed to push Chime Down" } } } else Logger.error { "withChime=true, but Chime Down not available" } } val readsize: Long = 1024 * 1024 // read 1 MB at a time var totalread: Long = 0 do{ val p = Memory(readsize) val read = bass.BASS_ChannelGetData(streamhandle, p, 4096) if (read > 0) { totalread += read } } while (read > 0) bassenc.BASS_Encode_Stop(encodehandle) bass.BASS_StreamFree(streamhandle) return if (all_success){ Result_Boolean_String(true, "WAV file written successfully: $target") } else { Result_Boolean_String(false, "Failed to write some data to WAV file: $target") } } else { bass.BASS_StreamFree(streamhandle) return Result_Boolean_String(false, "Failed to start encoding: ${bass.BASS_ErrorGetCode()}") } } else { return Result_Boolean_String(false, "Failed to create stream: ${bass.BASS_ErrorGetCode()}") } } /** * Writes the audio data from the sources to a WAV file. * @param sources List of AudioFileInfo objects containing the audio data to write. * @param target The target file name for the WAV file. * @param withChime If true, adds a chime sound at the beginning and end of the audio. * @return A Result_Boolean_String indicating success or failure and a message. */ fun WavWriter(sources: List, target: String, withChime: Boolean = true) : Result_Boolean_String { if (sources.isEmpty()) { return Result_Boolean_String(false,"Invalid Source") } if (!ValidString(target)) { return Result_Boolean_String(false, " Invalid target file name") } bass.BASS_SetDevice(0) // Set to No Sound device for writing val streamhandle = bass.BASS_StreamCreate(samplingrate, 1, BASS_STREAM_DECODE, Pointer(-1), null) if (streamhandle==0){ return Result_Boolean_String(false, "Failed to create stream: ${bass.BASS_ErrorGetCode()}") } val encodehandle = bassenc.BASS_Encode_Start(streamhandle, target, BASS_ENCODE_PCM, null, null) if (encodehandle==0){ bass.BASS_StreamFree(streamhandle) return Result_Boolean_String(false, "Failed to start encoding: ${bass.BASS_ErrorGetCode()}") } fun pushData(data: ByteArray): Boolean { val mem = Memory(data.size.toLong()) mem.write(0, data, 0, data.size) val pushresult = bass.BASS_StreamPutData(streamhandle, mem, data.size) if (pushresult==-1){ Logger.error { "BASS_StreamPutData failed: ${bass.BASS_ErrorGetCode()}" } } return pushresult != -1 } var allsuccess = true if (withChime){ val chup = contentCache.getAudioFile("chimeup") if (chup!=null && chup.isValid()){ if (!pushData(chup.bytes)){ allsuccess = false Logger.error { "Failed to push Chime Up" } } } else Logger.error { "withChime=true, but Chime Up not available" } } sources.forEach { source -> if (source.isValid()) { // write the bytes to the stream if (!pushData(source.bytes)){ allsuccess = false Logger.error { "Source ${source.fileName} push failed" } } } else { allsuccess = false Logger.error { "Not pushing Source=${source.fileName} because invalid" } } } if (withChime){ val chdn = contentCache.getAudioFile("chimedown") if (chdn!=null && chdn.isValid()){ if (!pushData(chdn.bytes)){ allsuccess = false Logger.error { "Failed to push Chime Down" } } } else Logger.error { "withChime=true, but Chime Down not available"} } val readsize: Long = 1024 * 1024 // read 1 MB at a time var totalread: Long = 0 do{ val p = Memory(readsize) val read = bass.BASS_ChannelGetData(streamhandle, p, 4096) if (read > 0) { totalread += read } } while (read > 0) // close the encoding handle bassenc.BASS_Encode_Stop(encodehandle) bass.BASS_ChannelFree(streamhandle) return if (allsuccess){ Result_Boolean_String(true, "WAV file written successfully: $target") } else { Result_Boolean_String(false, "Failed to write some data to WAV file: $target") } } }