From a32a1d330077dbdb1477ff5d092380fd334cb4bb Mon Sep 17 00:00:00 2001 From: rdkartono Date: Sat, 26 Jul 2025 10:14:46 +0700 Subject: [PATCH] first commit 26/07/2025 --- src/Main.kt | 6 +- src/audio/AudioPlayer.kt | 61 +++++++++++++++++- src/audio/Bass.java | 3 +- src/audio/BassEnc.java | 129 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 192 insertions(+), 7 deletions(-) create mode 100644 src/audio/BassEnc.java diff --git a/src/Main.kt b/src/Main.kt index dcb7466..d25d777 100644 --- a/src/Main.kt +++ b/src/Main.kt @@ -1,11 +1,11 @@ import audio.AudioPlayer -import codes.Somecodes import org.tinylog.Logger -val audioPlayer = AudioPlayer() + fun main() { Logger.info("Application started" as Any) + val audioPlayer = AudioPlayer(44100) // 44100 Hz sampling rate audioPlayer.InitAudio(1) -} \ No newline at end of file +} diff --git a/src/audio/AudioPlayer.kt b/src/audio/AudioPlayer.kt index 26bfa1f..feacfb6 100644 --- a/src/audio/AudioPlayer.kt +++ b/src/audio/AudioPlayer.kt @@ -5,16 +5,27 @@ 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.Bass.STREAMPROC_PUSH +import audio.BassEnc.BASS_ENCODE_PCM import codes.Somecodes.Companion.ValidFile +import codes.Somecodes.Companion.ValidString import com.sun.jna.Memory import org.tinylog.Logger +import java.util.function.BiConsumer @Suppress("unused") -class AudioPlayer { - var bass: Bass = Bass.Instance +class AudioPlayer (var samplingrate: Int) { + val bass: Bass = Bass.Instance + val bassenc : BassEnc = BassEnc.Instance var initedDevice = -1 + + + + init { - Logger.info("Audio version ${Integer.toHexString(bass.BASS_GetVersion())}" as Any) + 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 } @@ -110,5 +121,49 @@ class AudioPlayer { return result } + /** + * 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 callback A BiConsumer that accepts a Boolean indicating success or failure and a String message. + */ + fun WavWriter(sources: List, target: String, callback: BiConsumer) { + if (sources.isEmpty() || !ValidFile(target)) { + callback.accept(false, " Invalid sources") + return + } + if (!ValidString(target)) { + callback.accept(false, " Invalid target file name") + return + } + + bass.BASS_SetDevice(0) // Set to No Sound device for writing + val streamhandle = bass.BASS_StreamCreate(samplingrate, 1, BASS_STREAM_DECODE, STREAMPROC_PUSH, null) + if (streamhandle==0){ + callback.accept(false, "Failed to create stream: ${bass.BASS_ErrorGetCode()}") + return + } + val encodehandle = bassenc.BASS_Encode_Start(streamhandle, target, BASS_ENCODE_PCM, null, null) + if (encodehandle==0){ + bass.BASS_StreamFree(streamhandle) + callback.accept(false, "Failed to start encoding: ${bass.BASS_ErrorGetCode()}") + return + } + val playresult = bass.BASS_ChannelPlay(streamhandle,false) + if (!playresult) { + bassenc.BASS_Encode_Stop(encodehandle) + bass.BASS_StreamFree(streamhandle) + callback.accept(false, "BASS_ChannelPlay failed: ${bass.BASS_ErrorGetCode()}") + return + } + + // TODO write each source to the stream + + // close the encoding handle + bassenc.BASS_Encode_Stop(encodehandle) + bass.BASS_ChannelFree(streamhandle) + callback.accept(true, "WAV file written successfully: $target") + } + } \ No newline at end of file diff --git a/src/audio/Bass.java b/src/audio/Bass.java index 2662c1a..d025b1e 100644 --- a/src/audio/Bass.java +++ b/src/audio/Bass.java @@ -416,6 +416,8 @@ public interface Bass extends Library { int BASS_SYNC_MIXTIME = 0x40000000; // flag: sync at mixtime, else at playtime int BASS_SYNC_ONETIME = 0x80000000; // flag: sync only once, else continuously + + interface SYNCPROC extends Callback { void SYNCPROC(int handle, int channel, int data, Pointer user); @@ -787,5 +789,4 @@ public interface Bass extends Library { int BASS_StreamCreate(int freq, int chans, int flags, int proc, Pointer user); - } diff --git a/src/audio/BassEnc.java b/src/audio/BassEnc.java new file mode 100644 index 0000000..de43b0f --- /dev/null +++ b/src/audio/BassEnc.java @@ -0,0 +1,129 @@ +package audio; + +import com.sun.jna.*; + +@SuppressWarnings("unused") +public interface BassEnc extends Library { + + BassEnc Instance = (BassEnc) Native.load("bassenc", BassEnc.class); + + // Additional error codes returned by BASS_ErrorGetCode + int BASS_ERROR_CAST_DENIED = 2100; // access denied (invalid password) + int BASS_ERROR_SERVER_CERT = 2101; // missing/invalid certificate + + // Additional BASS_SetConfig options + int BASS_CONFIG_ENCODE_PRIORITY = 0x10300; + int BASS_CONFIG_ENCODE_QUEUE = 0x10301; + int BASS_CONFIG_ENCODE_CAST_TIMEOUT = 0x10310; + + // Additional BASS_SetConfigPtr options + int BASS_CONFIG_ENCODE_CAST_PROXY = 0x10311; + int BASS_CONFIG_ENCODE_CAST_BIND = 0x10312; + int BASS_CONFIG_ENCODE_SERVER_CERT = 0x10320; + int BASS_CONFIG_ENCODE_SERVER_KEY = 0x10321; + + // BASS_Encode_Start flags + int BASS_ENCODE_NOHEAD = 1; // don't send a WAV header to the encoder + int BASS_ENCODE_FP_8BIT = 2; // convert floating-point sample data to 8-bit integer + int BASS_ENCODE_FP_16BIT = 4; // convert floating-point sample data to 16-bit integer + int BASS_ENCODE_FP_24BIT = 6; // convert floating-point sample data to 24-bit integer + int BASS_ENCODE_FP_32BIT = 8; // convert floating-point sample data to 32-bit integer + int BASS_ENCODE_FP_AUTO = 14; // convert floating-point sample data back to channel's format + int BASS_ENCODE_BIGEND = 16; // big-endian sample data + int BASS_ENCODE_PAUSE = 32; // start encording paused + int BASS_ENCODE_PCM = 64; // write PCM sample data (no encoder) + int BASS_ENCODE_RF64 = 128; // send an RF64 header + int BASS_ENCODE_QUEUE = 0x200; // queue data to feed encoder asynchronously + int BASS_ENCODE_WFEXT = 0x400; // WAVEFORMATEXTENSIBLE "fmt" chunk + int BASS_ENCODE_CAST_NOLIMIT = 0x1000; // don't limit casting data rate + int BASS_ENCODE_LIMIT = 0x2000; // limit data rate to real-time + int BASS_ENCODE_AIFF = 0x4000; // send an AIFF header rather than WAV + int BASS_ENCODE_DITHER = 0x8000; // apply dither when converting floating-point sample data to integer + int BASS_ENCODE_AUTOFREE = 0x40000; // free the encoder when the channel is freed + + // BASS_Encode_GetCount counts + int BASS_ENCODE_COUNT_IN = 0; // sent to encoder + int BASS_ENCODE_COUNT_OUT = 1; // received from encoder + int BASS_ENCODE_COUNT_CAST = 2; // sent to cast server + int BASS_ENCODE_COUNT_QUEUE = 3; // queued + int BASS_ENCODE_COUNT_QUEUE_LIMIT = 4; // queue limit + int BASS_ENCODE_COUNT_QUEUE_FAIL = 5; // failed to queue + int BASS_ENCODE_COUNT_IN_FP = 6; // sent to encoder before floating-point conversion + + // BASS_Encode_CastInit content MIME types + String BASS_ENCODE_TYPE_MP3 = "audio/mpeg"; + String BASS_ENCODE_TYPE_OGG = "audio/ogg"; + String BASS_ENCODE_TYPE_AAC = "audio/aacp"; + + // BASS_Encode_CastInit flags + int BASS_ENCODE_CAST_PUBLIC = 1; // add to public directory + int BASS_ENCODE_CAST_PUT = 2; // use PUT method + int BASS_ENCODE_CAST_SSL = 4; // use SSL/TLS encryption + + // BASS_Encode_CastGetStats types + int BASS_ENCODE_STATS_SHOUT = 0; // Shoutcast stats + int BASS_ENCODE_STATS_ICE = 1; // Icecast mount-point stats + int BASS_ENCODE_STATS_ICESERV = 2; // Icecast server stats + + // BASS_Encode_ServerInit flags + int BASS_ENCODE_SERVER_NOHTTP = 1; // no HTTP headers + int BASS_ENCODE_SERVER_META = 2; // Shoutcast metadata + int BASS_ENCODE_SERVER_SSL = 4; // support SSL/TLS encryption + int BASS_ENCODE_SERVER_SSLONLY = 8; // require SSL/TLS encryption + + // Encoder notifications + int BASS_ENCODE_NOTIFY_ENCODER = 1; // encoder died + int BASS_ENCODE_NOTIFY_CAST = 2; // cast server connection died + int BASS_ENCODE_NOTIFY_SERVER = 3; // server died + int BASS_ENCODE_NOTIFY_CAST_TIMEOUT = 0x10000; // cast timeout + int BASS_ENCODE_NOTIFY_QUEUE_FULL = 0x10001; // queue is out of space + int BASS_ENCODE_NOTIFY_FREE = 0x10002; // encoder has been freed + + interface ENCODEPROC extends Callback { + /** + * Encoding Callback function. + * @param encoderhandle Encoder handle + * @param channelhandle Channel handle + * @param encodedData Buffer containing the encoded data + * @param length number of bytes + * @param user the user pointer passed to BASS_Encode_Start + */ + void ENCODEPROC(int encoderhandle, int channelhandle, Memory encodedData, int length, Pointer user); + } + + interface ENCODEPROCEX extends Callback { + /** + * Encoding Callback function + * @param handle Encoder handle + * @param channel Channel handle + * @param buffer Buffer containing the encoded data + * @param length number of bytes + * @param offset file offset of the data + * @param user the user pointer passed to BASS_Encode_Start + */ + void ENCODEPROCEX(int handle, int channel, Memory buffer, int length, long offset, Object user); + } + + interface ENCODERPROC extends Callback { + /** + * Encoder Callback function. + * @param encoderHandle Encoder handle + * @param channelHandle Channel handle + * @param encodedData Buffer containing the PCM Data (input) and receiving the encoded data (output) + * @param length Number of bytes in (-1 = closing) + * @param maxOut Maximum number of bytes out + * @param user the user pointer passed to BASS_Encode_Start + * @return the amount of encoded data (-1 = stop) + */ + int ENCODERPROC(int encoderHandle, int channelHandle, Memory encodedData, int length, int maxOut, Pointer user); + } + + + int BASS_Encode_GetVersion(); + int BASS_Encode_Start(int handle, String cmdline, int flags, ENCODEPROC proc, Pointer user); + boolean BASS_Encode_Stop(int handle); + boolean BASS_Encode_Write(int handle, Pointer buffer, int length); + int BASS_Encode_IsActive(int handle); + boolean BASS_Encode_SetPaused(int handle, boolean paused); + int BASS_Encode_GetChannel(int handle); +}