rigela_utils/
bass.rs

1/*
2 * Copyright (c) 2024. The RigelA open source project team and
3 * its contributors reserve all rights.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and limitations under the License.
12 */
13
14use 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
179//noinspection SpellCheckingInspection
180const STREAMPROC_PUSH: usize = usize::MAX;
181
182/// The channel is not active, or a handle is not a valid channel.
183const BASS_ACTIVE_STOPPED: i32 = 0;
184/// The channel is playing (or recording).
185#[allow(unused)]
186const BASS_ACTIVE_PLAYING: i32 = 1;
187/// Playback of the stream has been stalled due to a lack of sample data.
188/// Playback will automatically resume once there is sufficient data to do so.
189const BASS_ACTIVE_STALLED: i32 = 2;
190/// The channel is paused.
191const BASS_ACTIVE_PAUSED: i32 = 3;
192/// The channel's device is paused.
193#[allow(unused)]
194const BASS_ACTIVE_PAUSED_DEVICE: i32 = 4;
195
196// BASS_ChannelSetSync types
197#[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//noinspection SpellCheckingInspection
212#[allow(unused)]
213const BASS_SYNC_SETPOS: u32 = 11;
214//noinspection SpellCheckingInspection
215#[allow(unused)]
216const BASS_SYNC_MUSICPOS: u32 = 10;
217//noinspection SpellCheckingInspection
218#[allow(unused)]
219const BASS_SYNC_MUSICINST: u32 = 1;
220//noinspection SpellCheckingInspection
221#[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/// flag: call sync in another thread
230#[allow(unused)]
231const BASS_SYNC_THREAD: u32 = 0x20000000;
232//noinspection SpellCheckingInspection
233/// flag: sync at mixtime, else at playtime
234#[allow(unused)]
235const BASS_SYNC_MIXTIME: u32 = 0x40000000;
236#[allow(unused)]
237const BASS_SYNC_ONETIME: u32 = 0x80000000; // flag: sync only once, else continuously
238
239// Channel attributes
240const BASS_ATTRIB_FREQ: i32 = 1;
241#[allow(unused)]
242const BASS_ATTRIB_VOL: i32 = 2;
243#[allow(unused)]
244const BASS_ATTRIB_PAN: i32 = 3;
245//noinspection SpellCheckingInspection
246#[allow(unused)]
247const BASS_ATTRIB_EAXMIX: i32 = 4;
248//noinspection SpellCheckingInspection
249#[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//noinspection SpellCheckingInspection
260#[allow(unused)]
261const BASS_ATTRIB_SCANINFO: i32 = 10;
262//noinspection SpellCheckingInspection
263#[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//noinspection SpellCheckingInspection
278#[allow(unused)]
279const BASS_ATTRIB_DOWNLOADPROC: i32 = 18;
280//noinspection SpellCheckingInspection
281#[allow(unused)]
282const BASS_ATTRIB_VOLDSP: i32 = 19;
283//noinspection SpellCheckingInspection
284#[allow(unused)]
285const BASS_ATTRIB_VOLDSP_PRIORITY: i32 = 20;
286#[allow(unused)]
287const BASS_ATTRIB_MUSIC_AMPLIFY: i32 = 0x100;
288//noinspection SpellCheckingInspection
289#[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// + channel #
304#[allow(unused)]
305const BASS_ATTRIB_MUSIC_VOL_INST: i32 = 0x300; // + instrument #
306
307#[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    //noinspection RsUnresolvedReference
341    /**
342    创建一个通道输出流。
343    `sample_rate` 采样率。
344    `num_channels` 声道数量。
345    */
346    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    /**
361    从内存文件创建实例。
362    `data` 文件数据。
363    */
364    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    /**
369    从磁盘文件创建实例。
370    `path` 文件路径。
371    */
372    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    /**
378    创建一个空实例。
379    */
380    fn null() -> Self {
381        Self {
382            h_bass: 0,
383            h_module: HMODULE::default(),
384        }
385    }
386
387    /**
388    判断输出流是否可用。
389    */
390    pub fn is_valid(&self) -> bool {
391        (!self.h_module.is_invalid()) && self.h_bass != 0
392    }
393    /**
394    清理释放。
395    */
396    pub fn dispose(&self) {
397        bass!(self.h_module, stream_free, self.h_bass);
398        bass!(self.h_module, free);
399    }
400
401    /**
402    播放操作,此方法和start方法的功能一样,但具有从头开始播放的选项。
403    `restart` 重新开始。
404    */
405    pub fn play(&self, restart: bool) {
406        bass!(self.h_module, channel_play, self.h_bass, restart);
407    }
408
409    /**
410    暂停操作。
411    */
412    pub fn pause(&self) {
413        bass!(self.h_module, channel_pause, self.h_bass);
414    }
415
416    /**
417    停止操作。
418    */
419    pub fn stop(&self) {
420        bass!(self.h_module, channel_stop, self.h_bass);
421    }
422
423    /**
424    开始或继续播放操作。
425    */
426    pub fn start(&self) {
427        bass!(self.h_module, channel_start, self.h_bass);
428    }
429
430    /**
431    写入数据。
432    `data` 音频数据。
433    */
434    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    //noinspection StructuralWrap
439    /**
440    写入文件数据。
441    `data` 音频文件数据。
442    */
443    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    //noinspection StructuralWrap
448    /**
449    设置播放频率。
450    `value` 要播放的频率。
451    */
452    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    /**
464    检查样本、流或MOD音乐是否处于活动状态(正在播放)或暂停状态。还可以检查是否正在录制。
465    */
466    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    /**
471    判断播放状态是否已经停止。
472    */
473    pub fn is_stopped(&self) -> bool {
474        self.is_active() == BASS_ACTIVE_STOPPED
475    }
476
477    /**
478    等待直到停止状态或没有数据可以播放。
479    */
480    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    /**
486    等待直到暂停。
487    */
488    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}