1use crate::{call_proc, library::setup_library};
15use log::{error, info};
16use std::{
17 ffi::{c_void, CString},
18 sync::Once,
19 time::Duration,
20};
21use tokio::time::sleep;
22use win_wrap::common::{free_library, get_proc_address, load_library, FARPROC, HMODULE};
23use crate::library::get_rigela_library_path;
24
25macro_rules! bass {
26 ($module:expr,init,$device:expr,$freq:expr,$flags:expr,$win:expr,$clsid:expr) => {
27 call_proc!(
28 $module,
29 BASS_Init,
30 extern "system" fn(i32, i32, i32, i32, i32) -> bool,
31 $device,
32 $freq,
33 $flags,
34 $win,
35 $clsid
36 )
37 };
38 ($module:expr,free) => {
39 call_proc!($module, BASS_Free, extern "system" fn() -> bool,)
40 };
41 ($module:expr,stream_create,$freq:expr,$chans:expr,$flags:expr,$proc:expr,$user:expr) => {
42 call_proc!(
43 $module,
44 BASS_StreamCreate,
45 extern "system" fn(
46 i32,
47 i32,
48 i32,
49 *const extern "system" fn(i32, i32, i32, i32) -> i32,
50 i32,
51 ) -> i32,
52 $freq,
53 $chans,
54 $flags,
55 $proc as *const extern "system" fn(i32, i32, i32, i32) -> i32,
56 $user
57 )
58 };
59 ($module:expr,stream_free,$handle:expr) => {
60 call_proc!(
61 $module,
62 BASS_StreamFree,
63 extern "system" fn(i32) -> bool,
64 $handle
65 )
66 };
67 ($module:expr,channel_play,$handle:expr,$restart:expr) => {
68 call_proc!(
69 $module,
70 BASS_ChannelPlay,
71 extern "system" fn(i32, bool) -> bool,
72 $handle,
73 $restart
74 )
75 };
76 ($module:expr,channel_pause,$handle:expr) => {
77 call_proc!(
78 $module,
79 BASS_ChannelPause,
80 extern "system" fn(i32) -> bool,
81 $handle
82 )
83 };
84 ($module:expr,channel_stop,$handle:expr) => {
85 call_proc!(
86 $module,
87 BASS_ChannelStop,
88 extern "system" fn(i32) -> bool,
89 $handle
90 )
91 };
92 ($module:expr,channel_start,$handle:expr) => {
93 call_proc!(
94 $module,
95 BASS_ChannelStart,
96 extern "system" fn(i32) -> bool,
97 $handle
98 )
99 };
100 ($module:expr,stream_put_data,$handle:expr,$data:expr) => {
101 call_proc!(
102 $module,
103 BASS_StreamPutData,
104 extern "system" fn(i32, *const u8, i32) -> i32,
105 $handle,
106 $data.as_ptr(),
107 $data.len() as i32
108 )
109 };
110 ($module:expr,stream_put_file_data,$handle:expr,$data:expr) => {
111 call_proc!(
112 $module,
113 BASS_StreamPutFileData,
114 extern "system" fn(i32, *const u8, i32) -> i32,
115 $handle,
116 $data.as_ptr(),
117 $data.len() as i32
118 )
119 };
120 ($module:expr,channel_is_active,$handle:expr) => {
121 call_proc!(
122 $module,
123 BASS_ChannelIsActive,
124 extern "system" fn(i32) -> i32,
125 $handle
126 )
127 };
128 ($module:expr,channel_set_sync,$handle:expr,$type:expr,$param:expr,$proc:expr,$user:expr) => {
129 call_proc!(
130 $module,
131 BASS_ChannelSetSync,
132 extern "system" fn(i32, u32, i64, fn(i32, i32, i32, i32), i32) -> i32,
133 $handle,
134 $type,
135 $param,
136 $proc,
137 $user
138 )
139 };
140 ($module:expr,channel_remove_sync,$handle:expr,$h_sync:expr) => {
141 call_proc!(
142 $module,
143 BASS_ChannelRemoveSync,
144 extern "system" fn(i32, i32) -> bool,
145 $handle,
146 $h_sync
147 )
148 };
149 ($module:expr,stream_create_file,$mem:expr,$data:expr,$offset:expr,$length:expr,$flags:expr) => {
150 call_proc!(
151 $module,
152 BASS_StreamCreateFile,
153 extern "system" fn(bool, *const c_void, u64, u64, i32) -> i32,
154 $mem,
155 $data as *const c_void,
156 $offset,
157 $length,
158 $flags
159 )
160 };
161 ($module:expr,channel_slide_attribute,$handle:expr,$attrib:expr,$value:expr,$time:expr) => {
162 call_proc!(
163 $module,
164 BASS_ChannelSlideAttribute,
165 extern "system" fn(i32, i32, f32, i32) -> bool,
166 $handle,
167 $attrib,
168 $value,
169 $time
170 )
171 };
172}
173
174#[cfg(target_arch = "x86_64")]
175const LIB_NAME: &str = "bass-64.dll";
176#[cfg(target_arch = "x86")]
177const LIB_NAME: &str = "bass-32.dll";
178
179const STREAMPROC_PUSH: usize = usize::MAX;
181
182const BASS_ACTIVE_STOPPED: i32 = 0;
184#[allow(unused)]
186const BASS_ACTIVE_PLAYING: i32 = 1;
187const BASS_ACTIVE_STALLED: i32 = 2;
190const BASS_ACTIVE_PAUSED: i32 = 3;
192#[allow(unused)]
194const BASS_ACTIVE_PAUSED_DEVICE: i32 = 4;
195
196#[allow(unused)]
198const BASS_SYNC_POS: u32 = 0;
199#[allow(unused)]
200const BASS_SYNC_END: u32 = 2;
201#[allow(unused)]
202const BASS_SYNC_META: u32 = 4;
203#[allow(unused)]
204const BASS_SYNC_SLIDE: u32 = 5;
205#[allow(unused)]
206const BASS_SYNC_STALL: u32 = 6;
207#[allow(unused)]
208const BASS_SYNC_DOWNLOAD: u32 = 7;
209#[allow(unused)]
210const BASS_SYNC_FREE: u32 = 8;
211#[allow(unused)]
213const BASS_SYNC_SETPOS: u32 = 11;
214#[allow(unused)]
216const BASS_SYNC_MUSICPOS: u32 = 10;
217#[allow(unused)]
219const BASS_SYNC_MUSICINST: u32 = 1;
220#[allow(unused)]
222const BASS_SYNC_MUSICFX: u32 = 3;
223#[allow(unused)]
224const BASS_SYNC_OGG_CHANGE: u32 = 12;
225#[allow(unused)]
226const BASS_SYNC_DEV_FAIL: u32 = 14;
227#[allow(unused)]
228const BASS_SYNC_DEV_FORMAT: u32 = 15;
229#[allow(unused)]
231const BASS_SYNC_THREAD: u32 = 0x20000000;
232#[allow(unused)]
235const BASS_SYNC_MIXTIME: u32 = 0x40000000;
236#[allow(unused)]
237const BASS_SYNC_ONETIME: u32 = 0x80000000; const BASS_ATTRIB_FREQ: i32 = 1;
241#[allow(unused)]
242const BASS_ATTRIB_VOL: i32 = 2;
243#[allow(unused)]
244const BASS_ATTRIB_PAN: i32 = 3;
245#[allow(unused)]
247const BASS_ATTRIB_EAXMIX: i32 = 4;
248#[allow(unused)]
250const BASS_ATTRIB_NOBUFFER: i32 = 5;
251#[allow(unused)]
252const BASS_ATTRIB_VBR: i32 = 6;
253#[allow(unused)]
254const BASS_ATTRIB_CPU: i32 = 7;
255#[allow(unused)]
256const BASS_ATTRIB_SRC: i32 = 8;
257#[allow(unused)]
258const BASS_ATTRIB_NET_RESUME: i32 = 9;
259#[allow(unused)]
261const BASS_ATTRIB_SCANINFO: i32 = 10;
262#[allow(unused)]
264const BASS_ATTRIB_NORAMP: i32 = 11;
265#[allow(unused)]
266const BASS_ATTRIB_BITRATE: i32 = 12;
267#[allow(unused)]
268const BASS_ATTRIB_BUFFER: i32 = 13;
269#[allow(unused)]
270const BASS_ATTRIB_GRANULE: i32 = 14;
271#[allow(unused)]
272const BASS_ATTRIB_USER: i32 = 15;
273#[allow(unused)]
274const BASS_ATTRIB_TAIL: i32 = 16;
275#[allow(unused)]
276const BASS_ATTRIB_PUSH_LIMIT: i32 = 17;
277#[allow(unused)]
279const BASS_ATTRIB_DOWNLOADPROC: i32 = 18;
280#[allow(unused)]
282const BASS_ATTRIB_VOLDSP: i32 = 19;
283#[allow(unused)]
285const BASS_ATTRIB_VOLDSP_PRIORITY: i32 = 20;
286#[allow(unused)]
287const BASS_ATTRIB_MUSIC_AMPLIFY: i32 = 0x100;
288#[allow(unused)]
290const BASS_ATTRIB_MUSIC_PANSEP: i32 = 0x101;
291#[allow(unused)]
292const BASS_ATTRIB_MUSIC_PSCALER: i32 = 0x102;
293#[allow(unused)]
294const BASS_ATTRIB_MUSIC_BPM: i32 = 0x103;
295#[allow(unused)]
296const BASS_ATTRIB_MUSIC_SPEED: i32 = 0x104;
297#[allow(unused)]
298const BASS_ATTRIB_MUSIC_VOL_GLOBAL: i32 = 0x105;
299#[allow(unused)]
300const BASS_ATTRIB_MUSIC_ACTIVE: i32 = 0x106;
301#[allow(unused)]
302const BASS_ATTRIB_MUSIC_VOL_CHAN: i32 = 0x200;
303#[allow(unused)]
305const BASS_ATTRIB_MUSIC_VOL_INST: i32 = 0x300; #[derive(Debug)]
308pub struct BassChannelOutputStream {
309 h_bass: i32,
310 h_module: HMODULE,
311}
312
313impl BassChannelOutputStream {
314 fn create(slot: impl FnOnce(HMODULE) -> Option<i32>) -> Self {
315 let bass_path = get_rigela_library_path().join(LIB_NAME);
316 #[cfg(target_arch = "x86_64")]
317 setup_library(&bass_path, include_bytes!("../lib/bass-64.dll"));
318 #[cfg(target_arch = "x86")]
319 setup_library(&bass_path, include_bytes!("../lib/bass-32.dll"));
320 let h_module = match load_library(bass_path.to_str().unwrap()) {
321 Ok(h) => h,
322 Err(e) => {
323 error!("Can't open the library ({}). {}", bass_path.display(), e);
324 return Self::null();
325 }
326 };
327 static _LOADED: Once = Once::new();
328 _LOADED.call_once(|| {
329 info!(
330 "{} loaded, library handle is {:?}.",
331 bass_path.display(),
332 h_module.0
333 )
334 });
335 bass!(h_module, init, -1, 44100, 0, 0, 0);
336 let h_bass = slot(h_module).unwrap();
337 Self { h_bass, h_module }
338 }
339
340 pub fn new(sample_rate: u32, num_channels: u32) -> Self {
347 Self::create(|h_module| {
348 bass!(
349 h_module,
350 stream_create,
351 sample_rate as i32,
352 num_channels as i32,
353 0,
354 STREAMPROC_PUSH,
355 0
356 )
357 })
358 }
359
360 pub fn from_memory_file(data: &[u8]) -> Self {
365 Self::create(|h_module| bass!(h_module, stream_create_file, true, data.as_ptr(), 0, 0, 0))
366 }
367
368 pub fn from_disk_file(path: &str) -> Self {
373 let path = CString::new(path).unwrap();
374 Self::create(|h_module| bass!(h_module, stream_create_file, false, path.as_ptr(), 0, 0, 0))
375 }
376
377 fn null() -> Self {
381 Self {
382 h_bass: 0,
383 h_module: HMODULE::default(),
384 }
385 }
386
387 pub fn is_valid(&self) -> bool {
391 (!self.h_module.is_invalid()) && self.h_bass != 0
392 }
393 pub fn dispose(&self) {
397 bass!(self.h_module, stream_free, self.h_bass);
398 bass!(self.h_module, free);
399 }
400
401 pub fn play(&self, restart: bool) {
406 bass!(self.h_module, channel_play, self.h_bass, restart);
407 }
408
409 pub fn pause(&self) {
413 bass!(self.h_module, channel_pause, self.h_bass);
414 }
415
416 pub fn stop(&self) {
420 bass!(self.h_module, channel_stop, self.h_bass);
421 }
422
423 pub fn start(&self) {
427 bass!(self.h_module, channel_start, self.h_bass);
428 }
429
430 pub fn put_data(&self, data: &[u8]) -> i32 {
435 bass!(self.h_module, stream_put_data, self.h_bass, data).unwrap_or(0)
436 }
437
438 pub fn put_file_data(&self, data: &[u8]) -> i32 {
444 bass!(self.h_module, stream_put_file_data, self.h_bass, data).unwrap_or(0)
445 }
446
447 pub fn set_freq(&self, value: f32) {
453 bass!(
454 self.h_module,
455 channel_slide_attribute,
456 self.h_bass,
457 BASS_ATTRIB_FREQ,
458 value,
459 100
460 );
461 }
462
463 pub fn is_active(&self) -> i32 {
467 bass!(self.h_module, channel_is_active, self.h_bass).unwrap_or(BASS_ACTIVE_STOPPED)
468 }
469
470 pub fn is_stopped(&self) -> bool {
474 self.is_active() == BASS_ACTIVE_STOPPED
475 }
476
477 pub async fn wait_until_stopped_or_stalled(&self) {
481 self.wait(|active| active == BASS_ACTIVE_STOPPED || active == BASS_ACTIVE_STALLED)
482 .await;
483 }
484
485 pub async fn wait_until_paused(&self) {
489 self.wait(|active| active == BASS_ACTIVE_PAUSED).await;
490 }
491
492 async fn wait(&self, condition: impl Fn(i32) -> bool) {
493 loop {
494 let Some(active) = bass!(self.h_module, channel_is_active, self.h_bass) else {
495 break;
496 };
497 if condition(active) {
498 break;
499 }
500 sleep(Duration::from_millis(100)).await;
501 }
502 }
503}
504
505unsafe impl Send for BassChannelOutputStream {}
506unsafe impl Sync for BassChannelOutputStream {}
507
508impl Drop for BassChannelOutputStream {
509 fn drop(&mut self) {
510 if !self.h_module.is_invalid() {
511 free_library(self.h_module);
512 }
513 }
514}
515
516#[cfg(test)]
517mod test_bass {
518 use crate::bass::BassChannelOutputStream;
519
520 #[tokio::test]
521 async fn main() {
522 let out = BassChannelOutputStream::new(16000, 1);
523 let mut data = vec![];
524 for i in 0..8000 {
525 data.push(((i as f64).sin() * 127f64 + 128f64) as u8)
526 }
527 out.start();
528 out.put_data(&data);
529 out.wait_until_stopped_or_stalled().await;
530 let out = BassChannelOutputStream::from_disk_file(
531 r"C:\Users\Administrator\.rigela\resources\tip.wav",
532 );
533 for _ in 0..5 {
534 out.start();
535 out.wait_until_stopped_or_stalled().await;
536 }
537 }
538}