ebur128_stream 0.1.0

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.
@@ -0,0 +1,123 @@
1
+ mod analyzer;
2
+ mod error;
3
+ mod normalize;
4
+ mod report;
5
+ mod samples;
6
+ mod snapshot;
7
+
8
+ use crate::{
9
+ error::Error,
10
+ report::Report,
11
+ samples::{InterleavedSamples, PlanarSamples},
12
+ snapshot::Snapshot,
13
+ };
14
+ use ebur128_stream_rs as engine;
15
+ use magnus::{RArray, Ruby, Symbol, TryConvert, Value};
16
+ use std::ops::Deref;
17
+
18
+ struct Channel(engine::Channel);
19
+
20
+ impl From<Channel> for engine::Channel {
21
+ fn from(value: Channel) -> Self {
22
+ value.0
23
+ }
24
+ }
25
+
26
+ impl TryConvert for Channel {
27
+ fn try_convert(val: Value) -> Result<Self, magnus::Error> {
28
+ let channel = Symbol::try_convert(val)?;
29
+ Ok(match channel.name()?.as_ref() {
30
+ "left" => Self(engine::Channel::Left),
31
+ "right" => Self(engine::Channel::Right),
32
+ "center" => Self(engine::Channel::Center),
33
+ "left_surround" => Self(engine::Channel::LeftSurround),
34
+ "right_surround" => Self(engine::Channel::RightSurround),
35
+ "lfe" => Self(engine::Channel::Lfe),
36
+ "other" => Self(engine::Channel::Other),
37
+ _ => {
38
+ let ruby = Ruby::get_with(val);
39
+ return Err(magnus::Error::new(
40
+ ruby.exception_arg_error(),
41
+ format!("unknown channel: {val}"),
42
+ ));
43
+ }
44
+ })
45
+ }
46
+ }
47
+
48
+ pub(crate) struct Channels {
49
+ inner: Vec<engine::Channel>,
50
+ }
51
+
52
+ impl Deref for Channels {
53
+ type Target = Vec<engine::Channel>;
54
+
55
+ fn deref(&self) -> &Self::Target {
56
+ &self.inner
57
+ }
58
+ }
59
+
60
+ impl<'a> From<&'a [engine::Channel]> for Channels {
61
+ fn from(value: &'a [engine::Channel]) -> Self {
62
+ Self {
63
+ inner: value.to_vec(),
64
+ }
65
+ }
66
+ }
67
+
68
+ impl TryConvert for Channels {
69
+ fn try_convert(val: Value) -> Result<Self, magnus::Error> {
70
+ Ok(Self {
71
+ inner: RArray::try_convert(val)?
72
+ .into_iter()
73
+ .map(|value| Ok(Channel::try_convert(value)?.into()))
74
+ .collect::<Result<Vec<engine::Channel>, magnus::Error>>()?,
75
+ })
76
+ }
77
+ }
78
+
79
+ impl Channels {
80
+ fn try_into_rarray(&self, ruby: &Ruby) -> Result<RArray, magnus::Error> {
81
+ let syms = self
82
+ .inner
83
+ .iter()
84
+ .map(|channel| {
85
+ use engine::Channel::*;
86
+
87
+ let str = match channel {
88
+ Left => "left",
89
+ Right => "right",
90
+ Center => "center",
91
+ LeftSurround => "left_surround",
92
+ RightSurround => "right_surround",
93
+ Lfe => "lfe",
94
+ Other => "other",
95
+ _ => {
96
+ return Err(magnus::Error::new(
97
+ ruby.exception_runtime_error(),
98
+ "couldn't convert to Symbol: {channel}",
99
+ ));
100
+ }
101
+ };
102
+ Ok(ruby.to_symbol(str))
103
+ })
104
+ .collect::<Result<Vec<Symbol>, magnus::Error>>()?;
105
+ Ok(ruby.ary_new_from_values(&syms))
106
+ }
107
+
108
+ fn into_boxed_slice(self) -> Box<[engine::Channel]> {
109
+ self.inner.into_boxed_slice()
110
+ }
111
+ }
112
+
113
+ #[magnus::init]
114
+ fn init(ruby: &Ruby) -> Result<(), Error> {
115
+ let ebur128_stream = ruby.define_module("EBUR128Stream")?;
116
+
117
+ analyzer::init(ruby, &ebur128_stream)?;
118
+ snapshot::init(ruby, &ebur128_stream)?;
119
+ report::init(ruby, &ebur128_stream)?;
120
+ normalize::init(ruby, &ebur128_stream)?;
121
+
122
+ Ok(())
123
+ }
@@ -0,0 +1,121 @@
1
+ use crate::{Channels, error::Error, samples::WritableInterleavedSamples};
2
+ use ebur128_stream_rs as engine;
3
+ use magnus::{
4
+ RModule, Ruby, Value, function, method,
5
+ prelude::*,
6
+ scan_args::{get_kwargs, scan_args},
7
+ };
8
+
9
+ // Members are the same to engine::normalize::Normalizer
10
+ #[magnus::wrap(class = "EBUR128Stream::Normalizer")]
11
+ struct Normalizer {
12
+ sample_rate: u32,
13
+ channels: Box<[engine::Channel]>,
14
+ target_lufs: Option<f64>,
15
+ true_peak_ceiling_dbtp: Option<f64>,
16
+ }
17
+
18
+ impl Normalizer {
19
+ fn new(args: &[Value]) -> Result<Self, Error> {
20
+ let args = scan_args::<(), (), (), (), _, ()>(args)?;
21
+ let kws = get_kwargs::<_, (u32, Channels), (Option<f64>, Option<f64>), ()>(
22
+ args.keywords,
23
+ &["sample_rate", "channels"],
24
+ &["target_lufs", "true_peak_ceiling_dbtp"],
25
+ )?;
26
+ let (sample_rate, channels) = kws.required;
27
+ let (target_lufs, true_peak_ceiling_dbtp) = kws.optional;
28
+
29
+ Ok(Self {
30
+ sample_rate,
31
+ channels: channels.into_boxed_slice(),
32
+ target_lufs,
33
+ true_peak_ceiling_dbtp,
34
+ })
35
+ }
36
+
37
+ fn normalize_in_place(
38
+ &self,
39
+ mut samples: WritableInterleavedSamples,
40
+ ) -> Result<NormalizeReport, Error> {
41
+ let mut normalizer = engine::normalize::Normalizer::new(self.sample_rate, &self.channels);
42
+ if let Some(target_lufs) = self.target_lufs {
43
+ normalizer = normalizer.target_lufs(target_lufs);
44
+ }
45
+ if let Some(true_peak_ceiling_dbtp) = self.true_peak_ceiling_dbtp {
46
+ normalizer = normalizer.true_peak_ceiling_dbtp(true_peak_ceiling_dbtp);
47
+ }
48
+
49
+ let report = normalizer
50
+ .normalize_in_place(samples.as_mut_slice())
51
+ .map_err(Error::runtime)?;
52
+ samples.write_back_in_place()?;
53
+
54
+ Ok(NormalizeReport { report })
55
+ }
56
+ }
57
+
58
+ #[magnus::wrap(class = "EBUR128Stream::NormalizeReport")]
59
+ struct NormalizeReport {
60
+ report: engine::normalize::NormalizeReport,
61
+ }
62
+
63
+ impl NormalizeReport {
64
+ fn measured_integrated_lufs(&self) -> Option<f64> {
65
+ self.report.measured_integrated_lufs
66
+ }
67
+
68
+ fn measured_true_peak_dbtp(&self) -> Option<f64> {
69
+ self.report.measured_true_peak_dbtp
70
+ }
71
+
72
+ fn target_lufs(&self) -> f64 {
73
+ self.report.target_lufs
74
+ }
75
+
76
+ fn true_peak_ceiling_dbtp(&self) -> Option<f64> {
77
+ self.report.true_peak_ceiling_dbtp
78
+ }
79
+
80
+ fn applied_gain_db(&self) -> f64 {
81
+ self.report.applied_gain_db
82
+ }
83
+
84
+ fn limited_by_true_peak(&self) -> bool {
85
+ self.report.limited_by_true_peak
86
+ }
87
+ }
88
+
89
+ pub(crate) fn init(ruby: &Ruby, module: &RModule) -> Result<(), Error> {
90
+ let normalizer = module.define_class("Normalizer", ruby.class_object())?;
91
+ normalizer.define_singleton_method("new", function!(Normalizer::new, -1))?;
92
+ normalizer.define_method(
93
+ "normalize_in_place",
94
+ method!(Normalizer::normalize_in_place, 1),
95
+ )?;
96
+
97
+ let normalize_report = module.define_class("NormalizeReport", ruby.class_object())?;
98
+ normalize_report.define_method(
99
+ "measured_integrated_lufs",
100
+ method!(NormalizeReport::measured_integrated_lufs, 0),
101
+ )?;
102
+ normalize_report.define_method(
103
+ "measured_true_peak_dbtp",
104
+ method!(NormalizeReport::measured_true_peak_dbtp, 0),
105
+ )?;
106
+ normalize_report.define_method("target_lufs", method!(NormalizeReport::target_lufs, 0))?;
107
+ normalize_report.define_method(
108
+ "true_peak_ceiling_dbtp",
109
+ method!(NormalizeReport::true_peak_ceiling_dbtp, 0),
110
+ )?;
111
+ normalize_report.define_method(
112
+ "applied_gain_db",
113
+ method!(NormalizeReport::applied_gain_db, 0),
114
+ )?;
115
+ normalize_report.define_method(
116
+ "limited_by_true_peak",
117
+ method!(NormalizeReport::limited_by_true_peak, 0),
118
+ )?;
119
+
120
+ Ok(())
121
+ }
@@ -0,0 +1,52 @@
1
+ use crate::error::Error;
2
+ use ebur128_stream_rs as engine;
3
+ use magnus::{RModule, Ruby, method, prelude::*};
4
+
5
+ #[magnus::wrap(class = "EBUR128Stream::Report")]
6
+ pub(crate) struct Report {
7
+ pub(crate) report: engine::Report,
8
+ }
9
+
10
+ impl Report {
11
+ fn integrated_lufs(&self) -> Option<f64> {
12
+ self.report.integrated_lufs()
13
+ }
14
+
15
+ fn loudness_range_lu(&self) -> Option<f64> {
16
+ self.report.loudness_range_lu()
17
+ }
18
+
19
+ fn true_peak_dbtp(&self) -> Option<f64> {
20
+ self.report.true_peak_dbtp()
21
+ }
22
+
23
+ fn momentary_max_lufs(&self) -> Option<f64> {
24
+ self.report.momentary_max_lufs()
25
+ }
26
+
27
+ fn short_term_max_lufs(&self) -> Option<f64> {
28
+ self.report.short_term_max_lufs()
29
+ }
30
+
31
+ fn programme_duration_seconds(&self) -> f64 {
32
+ self.report.programme_duration_seconds()
33
+ }
34
+ }
35
+
36
+ pub(crate) fn init(ruby: &Ruby, module: &RModule) -> Result<(), Error> {
37
+ let report = module.define_class("Report", ruby.class_object())?;
38
+ report.define_method("integrated_lufs", method!(Report::integrated_lufs, 0))?;
39
+ report.define_method("loudness_range_lu", method!(Report::loudness_range_lu, 0))?;
40
+ report.define_method("true_peak_dbtp", method!(Report::true_peak_dbtp, 0))?;
41
+ report.define_method("momentary_max_lufs", method!(Report::momentary_max_lufs, 0))?;
42
+ report.define_method(
43
+ "short_term_max_lufs",
44
+ method!(Report::short_term_max_lufs, 0),
45
+ )?;
46
+ report.define_method(
47
+ "programme_duration_seconds",
48
+ method!(Report::programme_duration_seconds, 0),
49
+ )?;
50
+
51
+ Ok(())
52
+ }
@@ -0,0 +1,209 @@
1
+ use crate::error::Error;
2
+ use grey_knights::memory_view::{Flags, FlagsChainable, ItemComponent, ValidatedMemoryView};
3
+ use magnus::{RArray, Ruby, TryConvert, Value, error::IntoError};
4
+
5
+ fn is_acceptable_component(component: ItemComponent) -> bool {
6
+ component.offset == 0 && component.repeat == 1 && is_acceptable_format(component.format)
7
+ }
8
+
9
+ fn is_acceptable_format(format: char) -> bool {
10
+ match format {
11
+ 'f' => true,
12
+
13
+ #[cfg(target_endian = "little")]
14
+ 'e' => true,
15
+
16
+ #[cfg(target_endian = "big")]
17
+ 'g' => true,
18
+
19
+ _ => false,
20
+ }
21
+ }
22
+
23
+ pub(crate) enum InterleavedSamples {
24
+ Array { samples: Vec<f32> },
25
+ MemoryView { view: ValidatedMemoryView<f32> },
26
+ }
27
+
28
+ impl TryConvert for InterleavedSamples {
29
+ fn try_convert(val: Value) -> Result<Self, magnus::Error> {
30
+ if let Some(view) = Self::consume_memory_view(val) {
31
+ Ok(Self::MemoryView { view })
32
+ } else if let Some(obj) = RArray::from_value(val) {
33
+ Ok(Self::Array {
34
+ samples: obj.to_vec()?,
35
+ })
36
+ } else {
37
+ Err(Error::argument(format!("unsupported samples type: {val}"))
38
+ .into_error(&Ruby::get_with(val)))
39
+ }
40
+ }
41
+ }
42
+
43
+ impl InterleavedSamples {
44
+ pub(crate) fn as_slice(&self) -> &[f32] {
45
+ match self {
46
+ Self::Array { samples } => samples,
47
+ Self::MemoryView { view } => view.data(),
48
+ }
49
+ }
50
+
51
+ fn consume_memory_view(val: Value) -> Option<ValidatedMemoryView<f32>> {
52
+ let view = ValidatedMemoryView::<f32>::new(val, Flags::any_contiguous());
53
+ if let Ok(mut view) = view {
54
+ if Self::is_acceptable(&mut view).unwrap_or(false) {
55
+ return Some(view);
56
+ }
57
+ }
58
+ let view = ValidatedMemoryView::<f32>::new(val, Flags::simple());
59
+ if let Ok(mut view) = view {
60
+ if Self::is_acceptable(&mut view).unwrap_or(false) {
61
+ return Some(view);
62
+ }
63
+ }
64
+ None
65
+ }
66
+
67
+ // TODO: Check format more strictly(size, other expression)
68
+ fn is_acceptable(view: &mut ValidatedMemoryView<f32>) -> Result<bool, Error> {
69
+ let item_desc = view.item_desc()?;
70
+ Ok(view.ndim() == 1
71
+ && item_desc.len() == 1
72
+ && item_desc
73
+ .into_iter()
74
+ .next()
75
+ .is_some_and(is_acceptable_component))
76
+ }
77
+ }
78
+
79
+ pub(crate) enum WritableInterleavedSamples {
80
+ Array { obj: RArray, samples: Vec<f32> },
81
+ MemoryView { view: ValidatedMemoryView<f32> },
82
+ }
83
+
84
+ impl TryConvert for WritableInterleavedSamples {
85
+ fn try_convert(val: Value) -> Result<Self, magnus::Error> {
86
+ if let Some(view) = Self::consume_memory_view(val) {
87
+ Ok(Self::MemoryView { view })
88
+ } else if let Some(obj) = RArray::from_value(val) {
89
+ Ok(Self::Array {
90
+ obj,
91
+ samples: obj.to_vec()?,
92
+ })
93
+ } else {
94
+ Err(Error::argument(format!("unsupported samples type: {val}"))
95
+ .into_error(&Ruby::get_with(val)))
96
+ }
97
+ }
98
+ }
99
+
100
+ impl WritableInterleavedSamples {
101
+ pub(crate) fn as_mut_slice(&mut self) -> &mut [f32] {
102
+ match self {
103
+ Self::Array { obj: _, samples } => samples,
104
+ Self::MemoryView { view } => view.data_as_mut(),
105
+ }
106
+ }
107
+
108
+ pub(crate) fn write_back_in_place(self) -> Result<(), Error> {
109
+ match self {
110
+ Self::Array { obj, samples } => {
111
+ let ruby = Ruby::get_with(obj);
112
+ obj.replace(ruby.ary_from_vec(samples))?;
113
+ }
114
+ Self::MemoryView { view: _ } => {}
115
+ }
116
+
117
+ Ok(())
118
+ }
119
+
120
+ fn consume_memory_view(val: Value) -> Option<ValidatedMemoryView<f32>> {
121
+ let view = ValidatedMemoryView::<f32>::new(val, Flags::writable().any_contiguous());
122
+ if let Ok(mut view) = view {
123
+ if Self::is_acceptable(&mut view).unwrap_or(false) {
124
+ return Some(view);
125
+ }
126
+ }
127
+ let view = ValidatedMemoryView::<f32>::new(val, Flags::simple());
128
+ if let Ok(mut view) = view {
129
+ if !view.is_readonly() && Self::is_acceptable(&mut view).unwrap_or(false) {
130
+ return Some(view);
131
+ }
132
+ }
133
+ None
134
+ }
135
+
136
+ fn is_acceptable(view: &mut ValidatedMemoryView<f32>) -> Result<bool, Error> {
137
+ let item_desc = view.item_desc()?;
138
+ Ok(view.ndim() == 1
139
+ && item_desc.len() == 1
140
+ && item_desc
141
+ .into_iter()
142
+ .next()
143
+ .is_some_and(is_acceptable_component))
144
+ }
145
+ }
146
+
147
+ pub(crate) enum PlanarSamples {
148
+ Array { samples: Vec<Vec<f32>> },
149
+ MemoryView { view: ValidatedMemoryView<f32> },
150
+ }
151
+
152
+ impl TryConvert for PlanarSamples {
153
+ fn try_convert(val: Value) -> Result<Self, magnus::Error> {
154
+ if let Some(view) = Self::consume_memory_view(val) {
155
+ Ok(Self::MemoryView { view })
156
+ } else if let Some(obj) = RArray::from_value(val) {
157
+ Ok(Self::Array {
158
+ samples: obj.to_vec()?,
159
+ })
160
+ } else {
161
+ Err(Error::argument(format!("unsupported samples type: {val}"))
162
+ .into_error(&Ruby::get_with(val)))
163
+ }
164
+ }
165
+ }
166
+
167
+ impl PlanarSamples {
168
+ pub fn channel_slices(&self) -> Vec<&[f32]> {
169
+ match self {
170
+ Self::Array { samples } => samples.iter().map(Vec::as_slice).collect(),
171
+ Self::MemoryView { view } => {
172
+ let shape = view.shape().expect("ndim > 1 is checked when calling ");
173
+ let n_channels = shape[0];
174
+ let channel_len = shape[1];
175
+ if channel_len == 0 {
176
+ (0..n_channels).map(|_| &view.data()[..0]).collect()
177
+ } else {
178
+ view.data().chunks_exact(channel_len).collect()
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ fn consume_memory_view(val: Value) -> Option<ValidatedMemoryView<f32>> {
185
+ let view = ValidatedMemoryView::<f32>::new(val, Flags::row_major());
186
+ if let Ok(mut view) = view {
187
+ if Self::is_acceptable(&mut view).unwrap_or(false) {
188
+ return Some(view);
189
+ }
190
+ }
191
+ let view = ValidatedMemoryView::<f32>::new(val, Flags::simple());
192
+ if let Ok(mut view) = view {
193
+ if Self::is_acceptable(&mut view).unwrap_or(false) {
194
+ return Some(view);
195
+ }
196
+ }
197
+ None
198
+ }
199
+
200
+ fn is_acceptable(view: &mut ValidatedMemoryView<f32>) -> Result<bool, Error> {
201
+ let item_desc = view.item_desc()?;
202
+ Ok(view.ndim() == 2
203
+ && item_desc.len() == 1
204
+ && item_desc
205
+ .into_iter()
206
+ .next()
207
+ .is_some_and(is_acceptable_component))
208
+ }
209
+ }
@@ -0,0 +1,49 @@
1
+ use crate::Error;
2
+ use ebur128_stream_rs as engine;
3
+ use magnus::{RModule, Ruby, method, prelude::*};
4
+
5
+ #[magnus::wrap(class = "EBUR128Stream::Snapshot")]
6
+ pub(crate) struct Snapshot {
7
+ pub(crate) snapshot: engine::Snapshot,
8
+ }
9
+
10
+ impl Snapshot {
11
+ fn momentary_lufs(&self) -> Option<f64> {
12
+ self.snapshot.momentary_lufs()
13
+ }
14
+
15
+ fn short_term_lufs(&self) -> Option<f64> {
16
+ self.snapshot.short_term_lufs()
17
+ }
18
+
19
+ fn integrated_lufs(&self) -> Option<f64> {
20
+ self.snapshot.integrated_lufs()
21
+ }
22
+
23
+ fn true_peak_dbtp(&self) -> Option<f64> {
24
+ self.snapshot.true_peak_dbtp()
25
+ }
26
+
27
+ fn loudness_range_lu(&self) -> Option<f64> {
28
+ self.snapshot.loudness_range_lu()
29
+ }
30
+
31
+ fn programme_duration_seconds(&self) -> f64 {
32
+ self.snapshot.programme_duration_seconds()
33
+ }
34
+ }
35
+
36
+ pub(crate) fn init(ruby: &Ruby, module: &RModule) -> Result<(), Error> {
37
+ let snapshot = module.define_class("Snapshot", ruby.class_object())?;
38
+ snapshot.define_method("momentary_lufs", method!(Snapshot::momentary_lufs, 0))?;
39
+ snapshot.define_method("short_term_lufs", method!(Snapshot::short_term_lufs, 0))?;
40
+ snapshot.define_method("integrated_lufs", method!(Snapshot::integrated_lufs, 0))?;
41
+ snapshot.define_method("loudness_range_lu", method!(Snapshot::loudness_range_lu, 0))?;
42
+ snapshot.define_method("true_peak_dbtp", method!(Snapshot::true_peak_dbtp, 0))?;
43
+ snapshot.define_method(
44
+ "programme_duration_seconds",
45
+ method!(Snapshot::programme_duration_seconds, 0),
46
+ )?;
47
+
48
+ Ok(())
49
+ }
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EBUR128Stream
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ebur128_stream/version"
4
+ require "ebur128_stream/ebur128_stream"
5
+
6
+ module EBUR128Stream
7
+ class Error < StandardError; end
8
+
9
+ module Reportable
10
+ def deconstruct_keys(keys = nil)
11
+ keys = self.class::ATTRS if keys.nil?
12
+ (keys & self.class::ATTRS).inject({}) {|deconstructed, attr|
13
+ deconstructed[attr] = send(attr)
14
+ deconstructed
15
+ }
16
+ end
17
+
18
+ def inspect
19
+ "#<%{class} %{attrs}>" % {
20
+ class: self.class,
21
+ attrs: self.class::ATTRS.collect {|attr| "#{attr}=#{send(attr).inspect}"}.join(" ")
22
+ }
23
+ end
24
+
25
+ def ==(other)
26
+ deconstruct_keys(nil) == other.deconstruct_keys(nil)
27
+ end
28
+ end
29
+
30
+ class Snapshot
31
+ include Reportable
32
+
33
+ ATTRS = [
34
+ :momentary_lufs,
35
+ :short_term_lufs,
36
+ :integrated_lufs,
37
+ :loudness_range_lu,
38
+ :true_peak_dbtp,
39
+ :programme_duration_seconds
40
+ ]
41
+ end
42
+
43
+ class Report
44
+ include Reportable
45
+
46
+ ATTRS = [
47
+ :integrated_lufs,
48
+ :loudness_range_lu,
49
+ :true_peak_dbtp,
50
+ :momentary_max_lufs,
51
+ :short_term_max_lufs,
52
+ :programme_duration_seconds
53
+ ]
54
+ end
55
+
56
+ class NormalizeReport
57
+ include Reportable
58
+
59
+ ATTRS = [
60
+ :measured_integrated_lufs,
61
+ :measured_true_peak_dbtp,
62
+ :target_lufs,
63
+ :true_peak_ceiling_dbtp,
64
+ :applied_gain_db,
65
+ :limited_by_true_peak,
66
+ ]
67
+ end
68
+ end