@matjash/pixi-native-win32-x64 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,10 +7,13 @@ use std::thread;
7
7
  use std::time::{Duration, Instant};
8
8
 
9
9
  use maudio::audio::sample_rate::SampleRate;
10
- use maudio::data_source::sources::buffer::{AudioBuffer, AudioBufferBuilder};
10
+ use maudio::data_source::data_source_builder::DataSourceBuilder;
11
+ use maudio::data_source::pcm_source::PcmSource;
11
12
  use maudio::data_source::sources::pcm_ring_buffer::{PcmRbRecv, PcmRbSend, PcmRingBuffer};
13
+ use maudio::data_source::{DataSource, SourceContext};
12
14
  use maudio::engine::{engine_builder::EngineBuilder, Engine};
13
15
  use maudio::sound::{notifier::EndNotifier, sound_builder::SoundBuilder, Sound};
16
+ use maudio::{ErrorKinds, MaResult, MaudioError};
14
17
  use napi::bindgen_prelude::*;
15
18
  use napi::threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode};
16
19
  use napi_derive::napi;
@@ -112,9 +115,64 @@ impl StreamingControl {
112
115
  }
113
116
  }
114
117
 
118
+ struct SharedPcmSource {
119
+ samples: Arc<Vec<f32>>,
120
+ }
121
+
122
+ impl SharedPcmSource {
123
+ fn new(samples: Arc<Vec<f32>>) -> Self {
124
+ Self { samples }
125
+ }
126
+ }
127
+
128
+ impl PcmSource<f32> for SharedPcmSource {
129
+ fn fill_pcm_frames(
130
+ &mut self,
131
+ output: &mut [f32],
132
+ context: &mut SourceContext,
133
+ ) -> MaResult<usize> {
134
+ let channels = context.data_format.channels as usize;
135
+ let frame_count = self.samples.len() / channels;
136
+ let cursor = usize::try_from(context.cursor).unwrap_or(usize::MAX);
137
+ if cursor >= frame_count {
138
+ return Ok(0);
139
+ }
140
+
141
+ let output_frames = output.len() / channels;
142
+ let frames_to_copy = output_frames.min(frame_count - cursor);
143
+ let sample_start = cursor * channels;
144
+ let samples_to_copy = frames_to_copy * channels;
145
+ output[..samples_to_copy]
146
+ .copy_from_slice(&self.samples[sample_start..sample_start + samples_to_copy]);
147
+ context.cursor += frames_to_copy as u64;
148
+ Ok(frames_to_copy)
149
+ }
150
+
151
+ fn seek_to_pcm_frame(&mut self, frame_index: u64, context: &mut SourceContext) -> MaResult<()> {
152
+ let frame_count = self.samples.len() / context.data_format.channels as usize;
153
+ if frame_index > frame_count as u64 {
154
+ return Err(MaudioError::new_ma_error(ErrorKinds::InvalidOperation(
155
+ "Audio seek is outside the cached PCM source",
156
+ )));
157
+ }
158
+ context.cursor = frame_index;
159
+ Ok(())
160
+ }
161
+
162
+ fn cursor_in_pcm_frames(&self, context: &SourceContext) -> MaResult<u64> {
163
+ Ok(context.cursor)
164
+ }
165
+
166
+ fn length_in_pcm_frames(&self, context: &SourceContext) -> MaResult<u64> {
167
+ Ok((self.samples.len() / context.data_format.channels as usize) as u64)
168
+ }
169
+ }
170
+
171
+ type StaticDataSource = DataSource<f32, SharedPcmSource>;
172
+
115
173
  enum VoiceSource {
116
174
  Loading,
117
- Static(Box<AudioBuffer<f32>>),
175
+ Static(Box<StaticDataSource>),
118
176
  Streaming {
119
177
  receiver: Box<PcmRbRecv<f32>>,
120
178
  control: Arc<StreamingControl>,
@@ -643,8 +701,8 @@ impl EngineRuntime {
643
701
  let Some(mut voice) = self.voices.remove(&id) else {
644
702
  continue;
645
703
  };
646
- let result = AudioBufferBuilder::build_f32(CHANNELS as u32, &samples)
647
- .map_err(|error| format!("Cannot create miniaudio buffer: {error}"))
704
+ let result = build_static_data_source(samples)
705
+ .map_err(|error| format!("Cannot create miniaudio data source: {error}"))
648
706
  .and_then(|buffer| {
649
707
  voice.source = VoiceSource::Static(Box::new(buffer));
650
708
  self.attach_sound(&mut voice)
@@ -830,11 +888,12 @@ impl EngineRuntime {
830
888
  .snapshot
831
889
  .queued_frames
832
890
  .store(queued, Ordering::Release);
833
- if control.ready.load(Ordering::Acquire) && !voice.play_announced {
834
- if sound.play_sound().is_ok() {
835
- voice.play_announced = true;
836
- state.queue_event(voice.owner_id, "play", Some(*id), None, None);
837
- }
891
+ if control.ready.load(Ordering::Acquire)
892
+ && !voice.play_announced
893
+ && sound.play_sound().is_ok()
894
+ {
895
+ voice.play_announced = true;
896
+ state.queue_event(voice.owner_id, "play", Some(*id), None, None);
838
897
  }
839
898
  let starved = voice.play_announced
840
899
  && voice.playing
@@ -913,30 +972,25 @@ fn start_static_decode(
913
972
  preload: bool,
914
973
  request_id: Option<u32>,
915
974
  ) {
975
+ let key = cache_key(&options);
976
+ if let Some(samples) = state
977
+ .cache
978
+ .lock()
979
+ .ok()
980
+ .and_then(|cache| cache.get(&key).cloned())
981
+ {
982
+ complete_static_decode(&state, &options, preload, request_id, samples);
983
+ return;
984
+ }
985
+
916
986
  thread::spawn(move || {
917
- let key = cache_key(&options);
918
- let decoded = state
919
- .cache
920
- .lock()
921
- .ok()
922
- .and_then(|cache| cache.get(&key).cloned())
923
- .map_or_else(
924
- || decode_static(&options, state.sample_rate).map(Arc::new),
925
- Ok,
926
- );
987
+ let decoded = decode_static(&options, state.sample_rate).map(Arc::new);
927
988
  match decoded {
928
989
  Ok(samples) => {
929
990
  if let Ok(mut cache) = state.cache.lock() {
930
991
  cache.insert(key, Arc::clone(&samples));
931
992
  }
932
- if preload {
933
- state.queue_event(options.owner_id, "load", request_id, None, None);
934
- } else {
935
- state.push_command(EngineCommand::AttachStatic {
936
- id: options.id,
937
- samples,
938
- });
939
- }
993
+ complete_static_decode(&state, &options, preload, request_id, samples);
940
994
  }
941
995
  Err(message) => {
942
996
  if !preload {
@@ -954,6 +1008,28 @@ fn start_static_decode(
954
1008
  });
955
1009
  }
956
1010
 
1011
+ fn complete_static_decode(
1012
+ state: &SharedState,
1013
+ options: &NativeVoiceOptions,
1014
+ preload: bool,
1015
+ request_id: Option<u32>,
1016
+ samples: Arc<Vec<f32>>,
1017
+ ) {
1018
+ if preload {
1019
+ state.queue_event(options.owner_id, "load", request_id, None, None);
1020
+ } else {
1021
+ state.push_command(EngineCommand::AttachStatic {
1022
+ id: options.id,
1023
+ samples,
1024
+ });
1025
+ }
1026
+ }
1027
+
1028
+ fn build_static_data_source(samples: Arc<Vec<f32>>) -> MaResult<StaticDataSource> {
1029
+ DataSourceBuilder::new(CHANNELS as u32, SampleRate::Sr48000)
1030
+ .build_f32(SharedPcmSource::new(samples))
1031
+ }
1032
+
957
1033
  fn start_streaming_decode(
958
1034
  state: Arc<SharedState>,
959
1035
  options: NativeVoiceOptions,
@@ -1233,6 +1309,29 @@ fn redact_credentials(message: &str) -> String {
1233
1309
  mod tests {
1234
1310
  use super::*;
1235
1311
 
1312
+ #[test]
1313
+ fn static_voices_share_pcm_with_independent_cursors() {
1314
+ let samples = Arc::new(vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6]);
1315
+ let mut first = build_static_data_source(Arc::clone(&samples)).unwrap();
1316
+ let second = build_static_data_source(Arc::clone(&samples)).unwrap();
1317
+
1318
+ assert_eq!(Arc::strong_count(&samples), 3);
1319
+ assert_eq!(first.cursor_in_pcm_frames().unwrap(), 0);
1320
+ assert_eq!(second.cursor_in_pcm_frames().unwrap(), 0);
1321
+
1322
+ assert_eq!(first.read_pcm_frames(1).unwrap().frames(), 1);
1323
+ assert_eq!(first.cursor_in_pcm_frames().unwrap(), 1);
1324
+ assert_eq!(second.cursor_in_pcm_frames().unwrap(), 0);
1325
+
1326
+ first.seek_to_pcm_frame(0).unwrap();
1327
+ assert_eq!(first.cursor_in_pcm_frames().unwrap(), 0);
1328
+ assert!(first.seek_to_pcm_frame(4).is_err());
1329
+
1330
+ drop(first);
1331
+ drop(second);
1332
+ assert_eq!(Arc::strong_count(&samples), 1);
1333
+ }
1334
+
1236
1335
  #[test]
1237
1336
  fn atempo_decomposes_extreme_rates() {
1238
1337
  assert_eq!(build_atempo_filter(4.0).unwrap(), "atempo=2,atempo=2");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matjash/pixi-native-win32-x64",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "commonjs",
5
5
  "description": "Windows x64 native binaries for @matjash/pixi-native",
6
6
  "license": "MIT",