virtualdj_plugin_sdk/
lib.rs1pub mod ffi;
7
8use std::ffi::{CStr, CString};
9use std::fmt;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum PluginError {
14 Ok,
16 Fail,
18 NotImplemented,
20 NullPointer,
22}
23
24impl fmt::Display for PluginError {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match self {
27 PluginError::Ok => write!(f, "Success"),
28 PluginError::Fail => write!(f, "General failure"),
29 PluginError::NotImplemented => write!(f, "Not implemented"),
30 PluginError::NullPointer => write!(f, "Null pointer"),
31 }
32 }
33}
34
35impl std::error::Error for PluginError {}
36
37impl From<ffi::HRESULT> for PluginError {
38 fn from(hr: ffi::HRESULT) -> Self {
39 match hr {
40 ffi::S_OK => PluginError::Ok,
41 ffi::E_NOTIMPL => PluginError::NotImplemented,
42 ffi::E_FAIL => PluginError::Fail,
43 _ => PluginError::Fail,
44 }
45 }
46}
47
48pub type Result<T> = std::result::Result<T, PluginError>;
50
51pub struct PluginContext {
64 plugin: *mut ffi::VdjPlugin,
65 callbacks: *const ffi::VdjCallbacks,
66}
67
68impl PluginContext {
69 pub fn new(plugin: *mut ffi::VdjPlugin, callbacks: &ffi::VdjCallbacks) -> Self {
79 PluginContext {
80 plugin,
81 callbacks: callbacks as *const ffi::VdjCallbacks,
82 }
83 }
84
85 pub fn get_info_double(&self, command: &str) -> Result<f64> {
93 if self.plugin.is_null() || self.callbacks.is_null() {
94 return Err(PluginError::NullPointer);
95 }
96
97 let c_command = std::ffi::CString::new(command)
98 .map_err(|_| PluginError::Fail)?;
99
100 let mut result: f64 = 0.0;
101
102 let hr = unsafe {
103 ((*self.callbacks).get_info)(
104 self.plugin,
105 c_command.as_ptr() as *const u8,
106 &mut result as *mut f64,
107 )
108 };
109
110 if hr == ffi::S_OK {
111 Ok(result)
112 } else {
113 Err(PluginError::from(hr))
114 }
115 }
116
117 pub fn get_info_string(&self, command: &str) -> Result<String> {
125 if self.plugin.is_null() || self.callbacks.is_null() {
126 return Err(PluginError::NullPointer);
127 }
128
129 let c_command = std::ffi::CString::new(command)
130 .map_err(|_| PluginError::Fail)?;
131
132 let mut output: [u8; 1024] = [0; 1024];
133
134 let hr = unsafe {
135 ((*self.callbacks).get_string_info)(
136 self.plugin,
137 c_command.as_ptr() as *const u8,
138 output.as_mut_ptr(),
139 output.len() as i32,
140 )
141 };
142
143 if hr == ffi::S_OK {
144 let c_str = unsafe { CStr::from_ptr(output.as_ptr() as *const i8) };
145 Ok(c_str.to_string_lossy().into_owned())
146 } else {
147 Err(PluginError::from(hr))
148 }
149 }
150
151 pub fn send_command(&self, command: &str) -> Result<()> {
159 if self.plugin.is_null() || self.callbacks.is_null() {
160 return Err(PluginError::NullPointer);
161 }
162
163 let c_command = std::ffi::CString::new(command)
164 .map_err(|_| PluginError::Fail)?;
165
166 let hr = unsafe {
167 ((*self.callbacks).send_command)(
168 self.plugin,
169 c_command.as_ptr() as *const u8,
170 )
171 };
172
173 if hr == ffi::S_OK {
174 Ok(())
175 } else {
176 Err(PluginError::from(hr))
177 }
178 }
179}
180
181#[derive(Debug, Clone)]
183pub struct PluginInfo {
184 pub name: String,
185 pub author: String,
186 pub description: String,
187 pub version: String,
188 pub flags: u32,
189}
190
191impl PluginInfo {
192 fn from_ffi(ffi_info: &ffi::VdjPluginInfo) -> Result<Self> {
193 let name = unsafe { CStr::from_ptr(ffi_info.plugin_name as *const i8) }
194 .to_string_lossy()
195 .into_owned();
196 let author = unsafe { CStr::from_ptr(ffi_info.author as *const i8) }
197 .to_string_lossy()
198 .into_owned();
199 let description = unsafe { CStr::from_ptr(ffi_info.description as *const i8) }
200 .to_string_lossy()
201 .into_owned();
202 let version = unsafe { CStr::from_ptr(ffi_info.version as *const i8) }
203 .to_string_lossy()
204 .into_owned();
205
206 Ok(PluginInfo {
207 name,
208 author,
209 description,
210 version,
211 flags: ffi_info.flags,
212 })
213 }
214}
215
216pub trait PluginBase {
218 fn on_load(&mut self) -> Result<()> {
220 Ok(())
221 }
222
223 fn get_info(&self) -> PluginInfo {
225 PluginInfo {
226 name: "VirtualDJ Plugin".to_string(),
227 author: "Plugin Developer".to_string(),
228 description: "A VirtualDJ plugin".to_string(),
229 version: "1.0.0".to_string(),
230 flags: 0,
231 }
232 }
233
234 fn on_parameter(&mut self, id: i32) -> Result<()> {
236 Ok(())
237 }
238
239 fn on_get_parameter_string(&self, id: i32) -> Result<String> {
241 Err(PluginError::NotImplemented)
242 }
243}
244
245pub trait DspPlugin: PluginBase {
247 fn on_start(&mut self) -> Result<()> {
249 Ok(())
250 }
251
252 fn on_stop(&mut self) -> Result<()> {
254 Ok(())
255 }
256
257 fn on_process_samples(&mut self, buffer: &mut [f32]) -> Result<()>;
259
260 fn sample_rate(&self) -> i32 {
262 44100
263 }
264
265 fn song_bpm(&self) -> i32 {
267 120
268 }
269
270 fn song_pos_beats(&self) -> f64 {
272 0.0
273 }
274}
275
276pub trait BufferDspPlugin: PluginBase {
278 fn on_start(&mut self) -> Result<()> {
280 Ok(())
281 }
282
283 fn on_stop(&mut self) -> Result<()> {
285 Ok(())
286 }
287
288 fn on_get_song_buffer(&mut self, song_pos: i32, nb: i32) -> Option<&[i16]>;
290
291 fn sample_rate(&self) -> i32 {
293 44100
294 }
295
296 fn song_bpm(&self) -> i32 {
298 120
299 }
300
301 fn song_pos(&self) -> i32 {
303 0
304 }
305
306 fn song_pos_beats(&self) -> f64 {
308 0.0
309 }
310}
311
312pub trait PositionDspPlugin: PluginBase {
314 fn on_start(&mut self) -> Result<()> {
316 Ok(())
317 }
318
319 fn on_stop(&mut self) -> Result<()> {
321 Ok(())
322 }
323
324 fn on_transform_position(
326 &mut self,
327 song_pos: &mut f64,
328 video_pos: &mut f64,
329 volume: &mut f32,
330 src_volume: &mut f32,
331 ) -> Result<()>;
332
333 fn on_process_samples(&mut self, buffer: &mut [f32]) -> Result<()> {
335 Ok(())
336 }
337
338 fn sample_rate(&self) -> i32 {
340 44100
341 }
342
343 fn song_bpm(&self) -> i32 {
345 120
346 }
347
348 fn song_pos(&self) -> i32 {
350 0
351 }
352
353 fn song_pos_beats(&self) -> f64 {
355 0.0
356 }
357}
358
359pub trait VideoFxPlugin: PluginBase {
361 fn on_start(&mut self) -> Result<()> {
363 Ok(())
364 }
365
366 fn on_stop(&mut self) -> Result<()> {
368 Ok(())
369 }
370
371 fn on_draw(&mut self) -> Result<()>;
373
374 fn on_device_init(&mut self) -> Result<()> {
376 Ok(())
377 }
378
379 fn on_device_close(&mut self) -> Result<()> {
381 Ok(())
382 }
383
384 fn on_audio_samples(&mut self, buffer: &[f32]) -> Result<()> {
386 Ok(())
387 }
388
389 fn width(&self) -> i32 {
391 1920
392 }
393
394 fn height(&self) -> i32 {
396 1080
397 }
398
399 fn sample_rate(&self) -> i32 {
401 44100
402 }
403
404 fn song_bpm(&self) -> i32 {
406 120
407 }
408
409 fn song_pos_beats(&self) -> f64 {
411 0.0
412 }
413}
414
415pub trait VideoTransitionPlugin: PluginBase {
417 fn on_draw(&mut self, crossfader: f32) -> Result<()>;
419
420 fn on_device_init(&mut self) -> Result<()> {
422 Ok(())
423 }
424
425 fn on_device_close(&mut self) -> Result<()> {
427 Ok(())
428 }
429
430 fn width(&self) -> i32 {
432 1920
433 }
434
435 fn height(&self) -> i32 {
437 1080
438 }
439
440 fn sample_rate(&self) -> i32 {
442 44100
443 }
444
445 fn song_bpm(&self) -> i32 {
447 120
448 }
449
450 fn song_pos_beats(&self) -> f64 {
452 0.0
453 }
454}
455
456pub trait OnlineSourcePlugin: PluginBase {
458 fn is_logged(&self) -> Result<bool> {
460 Ok(false)
461 }
462
463 fn on_login(&mut self) -> Result<()> {
465 Ok(())
466 }
467
468 fn on_logout(&mut self) -> Result<()> {
470 Ok(())
471 }
472
473 fn on_search(&mut self, search: &str) -> Result<Vec<SearchResult>> {
475 Ok(Vec::new())
476 }
477
478 fn on_search_cancel(&mut self) -> Result<()> {
480 Ok(())
481 }
482}
483
484#[derive(Debug, Clone)]
486pub struct SearchResult {
487 pub unique_id: String,
488 pub title: String,
489 pub artist: String,
490 pub genre: Option<String>,
491 pub bpm: Option<f32>,
492}
493
494#[cfg(test)]
495mod tests {
496 use super::*;
497
498 #[test]
499 fn test_error_conversion() {
500 let err: PluginError = ffi::E_FAIL.into();
501 assert_eq!(err, PluginError::Fail);
502 }
503}