317 lines
12 KiB
Kotlin
317 lines
12 KiB
Kotlin
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 = 44100) {
|
|
val bass: Bass = Bass.Instance
|
|
val bassenc : BassEnc = BassEnc.Instance
|
|
val bassencmp3: BassEncMP3 = BassEncMP3.Instance
|
|
val bassencopus: BassEncOpus = BassEncOpus.Instance
|
|
val bassencogg : BassEncOGG = BassEncOGG.Instance
|
|
val bassmix : BassMix = BassMix.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())}" }
|
|
Logger.info { "BassEncMP3 version ${Integer.toHexString(bassencmp3.BASS_Encode_MP3_GetVersion())}" }
|
|
Logger.info { "BassEncOpus version ${Integer.toHexString(bassencopus.BASS_Encode_OPUS_GetVersion())}" }
|
|
Logger.info {" BassEncOGG version ${Integer.toHexString(bassencogg.BASS_Encode_OGG_GetVersion())}"}
|
|
Logger.info {" BassMix version ${Integer.toHexString(bassmix.BASS_Mixer_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<AudioFileInfo>, 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")
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
} |