├── rustfmt.toml ├── .gitignore ├── src ├── macros │ ├── mod.rs │ ├── segment_for.rs │ └── pixel.rs ├── lib.rs ├── zero.rs ├── error.rs ├── direct.rs ├── misc.rs ├── multi.rs └── binned.rs ├── .travis.yml ├── LICENSE-APACHE ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── benches └── render.rs └── examples ├── multi.rs └── binned.rs /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 140 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.swp 4 | *.bk 5 | -------------------------------------------------------------------------------- /src/macros/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod pixel; 3 | 4 | #[macro_use] 5 | mod segment_for; 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | matrix: 7 | allow_failures: 8 | - rust: nightly 9 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Waveform image renderes meant to be used for audio visualization. 2 | 3 | #[cfg(feature = "rlibc")] 4 | extern crate rlibc; 5 | 6 | pub mod error; 7 | 8 | pub mod zero; 9 | 10 | pub mod misc; 11 | pub use misc::{Color, Sample, SampleSequence, TimeRange, WaveformConfig}; 12 | 13 | #[macro_use] 14 | mod macros; 15 | 16 | pub mod binned; 17 | pub use binned::BinnedWaveformRenderer; 18 | 19 | #[deprecated] 20 | pub mod direct; 21 | 22 | pub mod multi; 23 | pub use multi::MultiWaveformRenderer; 24 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright 2017 tdgne 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "waveform" 3 | version = "0.3.2" 4 | authors = ["tdgne"] 5 | description = "Audio waveform image renderers" 6 | repository = "https://github.com/tdgne/waveform-rs" 7 | categories = ["multimedia::audio", "multimedia::images", "visualization"] 8 | keywords = ["signal", "wave", "sound", "image", "raster"] 9 | readme = "README.md" 10 | license = "Apache-2.0/MIT" 11 | 12 | [features] 13 | default = ["rlibc"] 14 | example-gui = ["gtk", "gdk-pixbuf"] 15 | 16 | [dependencies] 17 | rlibc = {version = "1.0.0", optional = true} 18 | gtk = {version = "0.1.3", optional = true} 19 | gdk-pixbuf = {version = "0.1.3", optional = true} 20 | 21 | -------------------------------------------------------------------------------- /src/macros/segment_for.rs: -------------------------------------------------------------------------------- 1 | // Utility macro for generating segmented for-loops 2 | macro_rules! three_segment_for { 3 | (for $idx:ident in $a:expr, $b:expr, $c:expr, $d:expr, { 4 | $ab:expr , $bc:expr , $cd:expr 5 | }) => { 6 | for $idx in $a..$b { 7 | $ab; 8 | } 9 | for $idx in $b..$c { 10 | $bc; 11 | } 12 | for $idx in $c..$d { 13 | $cd; 14 | } 15 | }; 16 | } 17 | macro_rules! flipping_three_segment_for { 18 | (for $idx:ident in $a:expr, $b:expr, $c:expr, $d:expr, { 19 | $odd:expr , $even:expr 20 | }) => { 21 | three_segment_for!{ 22 | for $idx in $a, $b, $c, $d, { 23 | $odd, 24 | $even, 25 | $odd 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/zero.rs: -------------------------------------------------------------------------------- 1 | /// Zeros for each supported type are implemented here. 2 | /// 3 | /// Used for generic `Sample`s. 4 | 5 | pub trait Zero { 6 | fn zero() -> Self; 7 | } 8 | impl Zero for f64 { 9 | fn zero() -> Self { 10 | 0f64 11 | } 12 | } 13 | impl Zero for f32 { 14 | fn zero() -> Self { 15 | 0f32 16 | } 17 | } 18 | impl Zero for i16 { 19 | fn zero() -> Self { 20 | 0i16 21 | } 22 | } 23 | impl Zero for u16 { 24 | fn zero() -> Self { 25 | 0u16 26 | } 27 | } 28 | impl Zero for i32 { 29 | fn zero() -> Self { 30 | 0i32 31 | } 32 | } 33 | impl Zero for u32 { 34 | fn zero() -> Self { 35 | 0u32 36 | } 37 | } 38 | impl Zero for u8 { 39 | fn zero() -> Self { 40 | 0u8 41 | } 42 | } 43 | impl Zero for i8 { 44 | fn zero() -> Self { 45 | 0i8 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt; 3 | 4 | #[derive(Debug)] 5 | pub struct InvalidSizeError { 6 | pub var_name: String, 7 | } 8 | 9 | 10 | impl fmt::Display for InvalidSizeError { 11 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 12 | write!(f, "Invalid size of '{}'", self.var_name) 13 | } 14 | } 15 | impl Error for InvalidSizeError { 16 | fn description(&self) -> &str { 17 | "An numeric value of an invalid size has been passed." 18 | } 19 | } 20 | 21 | 22 | #[derive(Debug)] 23 | pub struct InconsistentFormatError; 24 | 25 | impl fmt::Display for InconsistentFormatError { 26 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 27 | write!( 28 | f, 29 | "Color formats of background and foreground must be consistent." 30 | ) 31 | } 32 | } 33 | impl Error for InconsistentFormatError { 34 | fn description(&self) -> &str { 35 | "Color formats of background and foreground must be consistent." 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 tdgne 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/macros/pixel.rs: -------------------------------------------------------------------------------- 1 | // Utility macro for accessing pixels 2 | macro_rules! pixel_pos { 3 | (H ; $w:expr, $h:expr, $l:expr ; $x:expr , $y:expr , $i:expr) 4 | => (((($x) + ($y) * ($w)) * ($l) + ($i))); 5 | 6 | (V ; $w:expr, $h:expr, $l:expr ; $x:expr , $y:expr , $i:expr) 7 | => (((($y) + ($x) * ($h)) * ($l) + ($i))); 8 | 9 | ($orientation:ident ; $w:expr, $h:expr, $l:expr ; $x:expr , $y:expr , $i1:expr => $i2:expr) 10 | => (pixel_pos!($orientation; $w, $h, $l; $x, $y, $i1) .. pixel_pos!($orientation; $w, $h, $l; $x, $y, $i2)); 11 | } 12 | 13 | macro_rules! pixel { 14 | ($name:ident [ $orientation:ident ; $w:expr, $h:expr, $l:expr ; 15 | $x:expr , $y:expr , $i:expr ]) 16 | => ($name[pixel_pos!($orientation; $w, $h, $l; $x, $y, $i)]); 17 | 18 | ($name:ident [ $orientation:ident ; $w:expr, $h:expr, $l:expr ; 19 | $x:expr , $y:expr , $i1:expr => $i2:expr ]) 20 | => ($name[pixel_pos!($orientation; $w, $h, $l; $x, $y, $i1 => $i2)]); 21 | 22 | 23 | ($name:ident [ $w:expr, $h:expr, $l:expr ; $x:expr , $y:expr , $i:expr ]) 24 | => (pixel!($name[H; $w, $h, $l; $x, $y, $i])); 25 | 26 | ($name:ident [ $w:expr, $h:expr, $l:expr ; $x:expr , $y:expr , $i1:expr => $i2:expr ]) 27 | => (pixel!($name[H; $w, $h, $l; $x, $y, $i1 => $i2])); 28 | 29 | 30 | ($name:ident [ H ; $w:expr, $h:expr ; $x:expr , $y:expr ]) 31 | => (pixel!($name[H; $w, $h, 1; $x, $y, 0])); 32 | 33 | ($name:ident [ V ; $w:expr, $h:expr ; $x:expr , $y:expr ]) 34 | => (pixel!($name[V; $w, $h, 1; $x, $y, 0])); 35 | 36 | ($name:ident [ $w:expr, $h:expr ; $x:expr , $y:expr ]) 37 | => (pixel!($name[H; $w, $h; $x, $y])); 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # waveform 2 | 3 | [![crates.io](https://img.shields.io/crates/v/waveform.svg)](https://crates.io/crates/waveform) [![docs.rs](https://docs.rs/waveform/badge.svg)](https://docs.rs/waveform/) [![Build Status](https://travis-ci.org/tdgne/waveform-rs.svg?branch=master)](https://travis-ci.org/tdgne/waveform-rs) 4 | 5 | waveform is a set of waveform image renderers written in Rust. 6 | 7 | It is speed-oriented for heavy use. 8 | 9 | ## Features 10 | 11 | * Generation of bicolored raster images (outputs are either returned as `Vec`s or written into a slice) 12 | * RGB (`Vector3`) or RGBA (`Vector4`) format images 13 | * Gray scale (`Scalar`) images for use as masks etc. 14 | * Fast rendering from binned min/max amplitudes 15 | * Multilevel binning for rendering in various resolutions 16 | * Time-range specification in either seconds (`f64`) or samples (`usize`) 17 | 18 | ## Some TODOs 19 | 20 | * Cached rendering 21 | * Guarantee thread safety (it probably is...) 22 | * Memory/time optimizations 23 | 24 | Requests and contributions are welcome! 25 | 26 | ## Screenshot 27 | 28 | ```sh 29 | # Demonstrates rendering using a single BinnedWaveformRenderer. 30 | cargo run --features "example-gui" --example binned 31 | ``` 32 | 33 | ```sh 34 | # The same but by using a MultiWaveformRenderer, which is 35 | # a combination of multiple BinnedWaveformRenderers. 36 | cargo run --features "example-gui" --example multi 37 | ``` 38 | 39 | ![examples/waveform.rs](https://user-images.githubusercontent.com/29127111/27250722-dd579ff6-5370-11e7-99c2-7dc3e7705c14.png) 40 | 41 | 42 | ## License 43 | 44 | Licensed under either of 45 | 46 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 47 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 48 | 49 | at your option. 50 | 51 | ### Contribution 52 | 53 | Unless you explicitly state otherwise, any contribution intentionally submitted 54 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 55 | additional terms or conditions. 56 | 57 | -------------------------------------------------------------------------------- /benches/render.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate test; 3 | extern crate waveform; 4 | 5 | use test::Bencher; 6 | use waveform::*; 7 | 8 | fn gen_samples() -> Vec { 9 | let mut samples: Vec = Vec::new(); 10 | for t in 0..441000 { 11 | // 10 seconds 12 | samples.push( 13 | ((t as f64) / 100f64 * 2f64 * 3.1415f64).sin() * ((t as f64) / 10000f64 * 2f64 * 3.1415f64).sin(), 14 | ); 15 | } 16 | samples 17 | } 18 | 19 | fn gen_config() -> WaveformConfig { 20 | WaveformConfig::new( 21 | -1f64, 22 | 1f64, 23 | Color::Vector4(0, 0, 0, 255), 24 | Color::Vector4(0, 0, 0, 0), 25 | ).unwrap() 26 | } 27 | 28 | #[bench] 29 | fn bench_binned_vec(b: &mut Bencher) { 30 | let width = 1000usize; // The width of the rendered image. 31 | let height = 100usize; // The height of the rendered image. 32 | let bin_size = 100usize; // Bin size for BinnedWaveformRenderer 33 | 34 | let samples: Vec = gen_samples(); 35 | 36 | let config = gen_config(); 37 | 38 | let wfr = BinnedWaveformRenderer::new( 39 | &SampleSequence { 40 | data: &samples[..], 41 | sample_rate: 44100f64, 42 | }, 43 | bin_size, 44 | config.clone(), 45 | ).unwrap(); 46 | 47 | let mut vec: Vec = Vec::new(); 48 | 49 | b.iter(|| { 50 | vec = wfr.render_vec(TimeRange::Seconds(0f64, 10f64), (width, height)).unwrap(); 51 | }); 52 | } 53 | 54 | #[bench] 55 | fn bench_binned_write(b: &mut Bencher) { 56 | let width = 1000usize; // The width of the rendered image. 57 | let height = 100usize; // The height of the rendered image. 58 | let bin_size = 100usize; // Bin size for BinnedWaveformRenderer 59 | 60 | let samples: Vec = gen_samples(); 61 | 62 | let config = gen_config(); 63 | 64 | let wfr = BinnedWaveformRenderer::new( 65 | &SampleSequence { 66 | data: &samples[..], 67 | sample_rate: 44100f64, 68 | }, 69 | bin_size, 70 | config.clone(), 71 | ).unwrap(); 72 | 73 | let mut img: Vec = vec![0; width*height*4]; 74 | 75 | b.iter(|| { 76 | wfr.render_write( 77 | TimeRange::Seconds(0f64, 10f64), 78 | (0, 0), 79 | (width, height), 80 | &mut img[..], 81 | (width, height), 82 | ).unwrap(); 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /examples/multi.rs: -------------------------------------------------------------------------------- 1 | // The GUI parts conditioned by the "example-gui" feature 2 | // depend on the gtk crate, which is an interface to the 3 | // native GTK libs. 4 | // Although, the other parts which are not conditioned 5 | // altogether demonstrate the basic use of this crate. 6 | 7 | extern crate waveform; 8 | 9 | #[cfg(feature = "example-gui")] 10 | extern crate gtk; 11 | #[cfg(feature = "example-gui")] 12 | extern crate gdk_pixbuf; 13 | 14 | use waveform::{ 15 | SampleSequence, 16 | WaveformConfig, 17 | Color, 18 | MultiWaveformRenderer, 19 | TimeRange, 20 | }; 21 | 22 | #[cfg(feature = "example-gui")] 23 | use gtk::{ContainerExt, Image, Inhibit, WidgetExt, Window, WindowExt, WindowType}; 24 | #[cfg(feature = "example-gui")] 25 | use gdk_pixbuf::Pixbuf; 26 | 27 | fn main() { 28 | #[cfg(feature = "example-gui")] 29 | { 30 | if gtk::init().is_err() { 31 | panic!("Failed to initialize gtk."); 32 | } 33 | } 34 | 35 | #[cfg(feature = "example-gui")] 36 | let window = Window::new(WindowType::Toplevel); 37 | #[cfg(feature = "example-gui")] 38 | { 39 | window.set_title("A simple waveform renderer test"); 40 | window.set_default_size(800, 100); 41 | window.connect_delete_event(|_, _| { 42 | gtk::main_quit(); 43 | Inhibit(false) 44 | }); 45 | } 46 | 47 | // Generate samples to show. 48 | let mut samples: Vec = Vec::new(); 49 | for t in 0..44100 { 50 | samples.push( 51 | ((t as f64) / 100f64 * 2f64 * 3.1415f64).sin() * ((t as f64) / 10000f64 * 2f64 * 3.1415f64).sin(), 52 | ); 53 | } 54 | 55 | // The renderer's config. 56 | let config = WaveformConfig::new( 57 | -1f64, // Minimum amplitude to show 58 | 1f64, // Maximum amplitude to show 59 | 60 | // Foreground color 61 | Color::Vector4(0, 0, 0, 255), 62 | 63 | // Background color 64 | Color::Vector4(0, 0, 0, 0) 65 | ).unwrap(); 66 | 67 | // Put a reference to the samples here along with its sample rate. 68 | // We need to set a sample rate because it will be used 69 | // when you specify the time range in seconds. 70 | let ss = SampleSequence { 71 | data: &samples[..], 72 | sample_rate: 44100f64, 73 | }; 74 | 75 | // Construct the renderer. 76 | // The second argument is a `&Vec` containing the bin sizes. 77 | // `MultiWaveformRenderer` will generate a `BinnedWaveformRenderer` for 78 | // each bin size. 79 | let mut wfg = MultiWaveformRenderer::new(&ss, &[10, 100, 1000], config).unwrap(); 80 | 81 | // Render! 82 | // Each time `MultiWaveformRenderer` will choose the appropriate bin size. 83 | // The largest bin size that is not larger than the average number of samples 84 | // that a pixel contains. 85 | let mut vec: Vec = 86 | wfg.render_vec(TimeRange::Seconds(0.0f64, 1.0f64), (800, 100)) 87 | .unwrap(); 88 | 89 | #[cfg(feature = "example-gui")] 90 | { 91 | let pixbuf = 92 | Pixbuf::new_from_vec(vec, 0, true, 8, 800, 100, 800 * 4); 93 | let image = Image::new_from_pixbuf(Some(&pixbuf)); 94 | window.add(&image); 95 | window.show_all(); 96 | gtk::main(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /examples/binned.rs: -------------------------------------------------------------------------------- 1 | // The GUI parts conditioned by the "example-gui" feature 2 | // depend on the gtk crate, which is an interface to the 3 | // native GTK libs. 4 | // Although, the other parts which are not conditioned 5 | // altogether demonstrate the basic use of this crate. 6 | 7 | extern crate waveform; 8 | 9 | #[cfg(feature = "example-gui")] 10 | extern crate gtk; 11 | #[cfg(feature = "example-gui")] 12 | extern crate gdk_pixbuf; 13 | 14 | use waveform::{ 15 | SampleSequence, 16 | WaveformConfig, 17 | Color, 18 | BinnedWaveformRenderer, 19 | TimeRange, 20 | }; 21 | 22 | #[cfg(feature = "example-gui")] 23 | use gtk::{ContainerExt, Image, Inhibit, WidgetExt, Window, WindowExt, WindowType}; 24 | #[cfg(feature = "example-gui")] 25 | use gdk_pixbuf::Pixbuf; 26 | 27 | fn main() { 28 | #[cfg(feature = "example-gui")] 29 | { 30 | if gtk::init().is_err() { 31 | panic!("Failed to initialize gtk."); 32 | } 33 | } 34 | 35 | #[cfg(feature = "example-gui")] 36 | let window = Window::new(WindowType::Toplevel); 37 | #[cfg(feature = "example-gui")] 38 | { 39 | window.set_title("A simple waveform renderer test"); 40 | window.set_default_size(800, 100); 41 | window.connect_delete_event(|_, _| { 42 | gtk::main_quit(); 43 | Inhibit(false) 44 | }); 45 | } 46 | 47 | // Generate samples to show. 48 | let mut samples: Vec = Vec::new(); 49 | for t in 0..44100 { 50 | samples.push( 51 | ((t as f64) / 100f64 * 2f64 * 3.1415f64).sin() * ((t as f64) / 10000f64 * 2f64 * 3.1415f64).sin(), 52 | ); 53 | } 54 | 55 | // The renderer's config. 56 | let config = WaveformConfig::new( 57 | -1f64, // Minimum amplitude to show 58 | 1f64, // Maximum amplitude to show 59 | 60 | // Foreground color 61 | Color::Vector4(0, 0, 0, 255), 62 | 63 | // Background color 64 | Color::Vector4(0, 0, 0, 0) 65 | ).unwrap(); 66 | 67 | // Put a reference to the samples here along with its sample rate. 68 | // We need to set a sample rate because it will be used 69 | // when you specify the time range in seconds. 70 | let ss = SampleSequence { 71 | data: &samples[..], 72 | sample_rate: 44100f64, 73 | }; 74 | 75 | // Construct the renderer. 76 | // The second argument is the bin size. 77 | // The renderer will bin the samples, and then 78 | // calculate the minimum and maximum amplitude values for 79 | // each bin. 80 | let wfg = BinnedWaveformRenderer::new(&ss, 10, config).unwrap(); 81 | 82 | // Render! 83 | // The renderer doesn't look at the actual audio samples here. 84 | // Instead it will use the binned min/max values calculated above, 85 | // making the rendering quite faster. 86 | let vec: Vec = 87 | wfg.render_vec(TimeRange::Seconds(0.0f64, 1.0f64), (800, 100)) 88 | .unwrap(); 89 | 90 | #[cfg(feature = "example-gui")] 91 | { 92 | let pixbuf = 93 | Pixbuf::new_from_vec(vec, 0, true, 8, 800, 100, 800 * 4); 94 | let image = Image::new_from_pixbuf(Some(&pixbuf)); 95 | window.add(&image); 96 | window.show_all(); 97 | gtk::main(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/direct.rs: -------------------------------------------------------------------------------- 1 | use misc::*; 2 | 3 | #[derive(Copy, Clone)] 4 | pub struct DirectWaveformRenderer { 5 | pub sample_rate: f64, 6 | pub config: WaveformConfig, 7 | } 8 | 9 | impl DirectWaveformRenderer { 10 | /// Generates an image as a `Vec` directly from the given samples. 11 | /// 12 | /// # Arguments 13 | /// 14 | /// * `samples` - The `Sample`s that will be used to render the image. 15 | /// * `shape` - The `(width, height)` of the resulting image. 16 | pub fn render_vec(&self, samples: &[T], shape: (usize, usize)) -> Option> { 17 | let (w, h) = shape; 18 | if w == 0 || h == 0 { 19 | return None; 20 | } 21 | let mut img = vec![0u8; w * h * 4]; 22 | let nb_samples = samples.len(); 23 | let samples_per_pixel = nb_samples / w; 24 | 25 | // Unlike BinnedWaveformRenderer, the minmax values corresponding to 26 | // each horizontal pixel is calculated beforehand, for the same reasons 27 | // discussed later in these comments. 28 | let mut minmax = MinMaxPairSequence { 29 | data: Vec::with_capacity(w), 30 | }; 31 | for x in 0..w { 32 | let mut min = samples[x * samples_per_pixel + 0]; 33 | let mut max = samples[x * samples_per_pixel + 0]; 34 | if samples_per_pixel > 1 { 35 | for i in 1..samples_per_pixel { 36 | let idx = x * samples_per_pixel + i; 37 | if idx >= nb_samples { 38 | break; 39 | } 40 | let s = samples[idx]; 41 | if s > max { 42 | max = s; 43 | } 44 | if s < min { 45 | min = s; 46 | } 47 | } 48 | } 49 | minmax.data.push(MinMaxPair { min: min, max: max }); 50 | } 51 | 52 | // Unlike BinnedWaveformRenderer, the `match` is outside the `for`s 53 | // because it's faster this way. 54 | // I've also tried it in BinnedWaveformRenderer but it didn't make a 55 | // significant improvement in speed, so it's left that way. 56 | match (self.config.get_background(), self.config.get_foreground()) { 57 | (Color::Scalar(ba), Color::Scalar(fa)) => for y in 0..h { 58 | let y_translated = ((h - y) as f64) / (h as f64) * (self.config.amp_max - self.config.amp_min) + self.config.amp_min; 59 | for x in 0..w { 60 | if y_translated < minmax.data[x].min.into() || y_translated > minmax.data[x].max.into() { 61 | img[1 * (y * w + x) + 0] = ba; 62 | } else { 63 | img[1 * (y * w + x) + 0] = fa; 64 | } 65 | } 66 | }, 67 | 68 | ( 69 | Color::Vector4(br, bg, bb, ba), 70 | Color::Vector4(fr, fg, fb, fa), 71 | ) => for y in 0..h { 72 | let y_translated = ((h - y) as f64) / (h as f64) * (self.config.amp_max - self.config.amp_min) + self.config.amp_min; 73 | for x in 0..w { 74 | if y_translated < minmax.data[x].min.into() || y_translated > minmax.data[x].max.into() { 75 | img[4 * (y * w + x) + 0] = br; 76 | img[4 * (y * w + x) + 1] = bg; 77 | img[4 * (y * w + x) + 2] = bb; 78 | img[4 * (y * w + x) + 3] = ba; 79 | } else { 80 | img[4 * (y * w + x) + 0] = fr; 81 | img[4 * (y * w + x) + 1] = fg; 82 | img[4 * (y * w + x) + 2] = fb; 83 | img[4 * (y * w + x) + 3] = fa; 84 | } 85 | } 86 | }, 87 | 88 | _ => { 89 | panic!("Color formats of background and foreground are inconsistent!"); 90 | } 91 | } 92 | Some(img) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/misc.rs: -------------------------------------------------------------------------------- 1 | use zero::Zero; 2 | use error::InconsistentFormatError; 3 | use std::error::Error; 4 | 5 | /// Color specifiers. 6 | #[derive(Copy, Clone)] 7 | pub enum Color { 8 | /// A format with only one value per pixel, or gray scale in other words. 9 | Scalar(u8), 10 | /// 3-dimensional format (e.g. RGB format). 11 | Vector3(u8, u8, u8), 12 | /// 4-dimensional format (e.g. RGBA format). 13 | Vector4(u8, u8, u8, u8), 14 | } 15 | 16 | /// Configurations for image generators. 17 | /// 18 | /// It contains the following information: 19 | /// 20 | /// * Range of the amplitudes to be rendered 21 | /// * Foreground and background `Color`s to be used 22 | #[derive(Copy, Clone)] 23 | pub struct WaveformConfig { 24 | pub amp_min: f64, 25 | pub amp_max: f64, 26 | foreground: Color, 27 | background: Color, 28 | } 29 | 30 | impl WaveformConfig { 31 | fn check_color_consistency(c1: Color, c2: Color) -> Result<(), Box> { 32 | match c1 { 33 | Color::Scalar(_) => { 34 | if let Color::Scalar(_) = c2 { 35 | return Ok(()); 36 | }else{ 37 | return Err(Box::new(InconsistentFormatError)); 38 | } 39 | }, 40 | Color::Vector3(..) => { 41 | if let Color::Vector3(..) = c2 { 42 | return Ok(()); 43 | }else{ 44 | return Err(Box::new(InconsistentFormatError)); 45 | } 46 | }, 47 | Color::Vector4(..) => { 48 | if let Color::Vector4(..) = c2 { 49 | return Ok(()); 50 | }else{ 51 | return Err(Box::new(InconsistentFormatError)); 52 | } 53 | }, 54 | } 55 | } 56 | 57 | /// The constructor. 58 | /// 59 | /// # Arguments 60 | /// * `amp_min` - Minimum value of amplitude to be rendered 61 | /// * `amp_max` - Maximum value of amplitude to be rendered 62 | /// * `foreground` - Foreground `Color` of the image, format must be consistent with background. 63 | /// * `background` - Background `Color` of the image, format must be consistent with foreground. 64 | pub fn new(amp_min: f64, amp_max: f64, foreground: Color, background: Color) -> Result> { 65 | match Self::check_color_consistency(background, foreground) { 66 | Err(e) => return Err(e), 67 | _ => (), 68 | } 69 | 70 | Ok(Self { 71 | amp_min, 72 | amp_max, 73 | background, 74 | foreground, 75 | }) 76 | } 77 | 78 | pub fn get_background(&self) -> Color { 79 | self.background 80 | } 81 | pub fn get_foreground(&self) -> Color { 82 | self.foreground 83 | } 84 | 85 | /// Sets `Color`s. 86 | /// 87 | /// # Arguments 88 | /// * `foreground` - Foreground `Color` of the image, format must be consistent with background. 89 | /// * `background` - Background `Color` of the image, format must be consistent with foreground. 90 | pub fn set_colors(&mut self, background: Color, foreground: Color) -> Result<(), Box> { 91 | match Self::check_color_consistency(background, foreground) { 92 | Err(e) => return Err(e), 93 | _ => (), 94 | } 95 | 96 | self.background = background; 97 | self.foreground = foreground; 98 | 99 | Ok(()) 100 | } 101 | } 102 | 103 | impl Default for WaveformConfig { 104 | fn default() -> Self { 105 | Self { 106 | amp_min: -1f64, 107 | amp_max: 1f64, 108 | foreground: Color::Scalar(255), 109 | background: Color::Scalar(0), 110 | } 111 | } 112 | } 113 | 114 | /// Time range specifiers used to determine which part of the wave to plot. 115 | #[derive(Copy, Clone)] 116 | pub enum TimeRange { 117 | Seconds(f64, f64), 118 | Samples(usize, usize), 119 | } 120 | 121 | impl TimeRange { 122 | pub fn to_sample_tuple(&self, sample_rate: f64) -> (usize, usize) { 123 | match self { 124 | &TimeRange::Seconds(b, e) => ( 125 | (b * sample_rate) as usize, 126 | (e * sample_rate) as usize, 127 | ), 128 | &TimeRange::Samples(b, e) => (b, e), 129 | } 130 | } 131 | } 132 | 133 | /// A sample. 134 | pub trait Sample: PartialOrd + Into + Copy + Zero {} 135 | impl Sample for T 136 | where 137 | T: PartialOrd + Into + Copy + Zero, 138 | { 139 | } 140 | 141 | /// A reference to a `slice` of `Sample`s 142 | /// (which describe a wave) combined with its sample rate. 143 | pub struct SampleSequence<'a, T: Sample + 'a> { 144 | pub data: &'a [T], 145 | pub sample_rate: f64, 146 | } 147 | 148 | /// A pair of a minimum and maximum amplitude values for internal use. 149 | #[derive(Copy, Clone)] 150 | pub struct MinMaxPair { 151 | pub min: T, 152 | pub max: T, 153 | } 154 | 155 | pub struct MinMaxPairSequence { 156 | pub data: Vec>, 157 | } 158 | -------------------------------------------------------------------------------- /src/multi.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::error::Error; 3 | 4 | use super::misc::*; 5 | use super::error::*; 6 | use super::binned::BinnedWaveformRenderer; 7 | 8 | /// A renderer that contains multiple `BinnedWaveformRenderer`s 9 | /// with different bin sizes. 10 | /// 11 | /// It will automatically choose an apropriate bin size each time 12 | /// it renders. 13 | pub struct MultiWaveformRenderer { 14 | pub binned: HashMap>, 15 | sample_rate: f64, 16 | } 17 | 18 | impl MultiWaveformRenderer { 19 | /// The constructor. 20 | /// 21 | /// # Arguments 22 | /// 23 | /// * `samples` - The samples that will be used to calculate binned min / max values. 24 | /// It must also contain the sample rate that is used by 25 | /// `BinnedWaveformRenderer` to render images when given a 26 | /// `TimeRange::Seconds`. 27 | /// * `bin_sizes` - The sizes of the bins which the min / max values will be binned 28 | /// into. 29 | /// * `config` - See `WaveformConfig`. 30 | pub fn new(samples: &SampleSequence, bin_sizes: &[usize], config: WaveformConfig) -> Result> { 31 | let mut r = MultiWaveformRenderer { 32 | binned: HashMap::new(), 33 | sample_rate: samples.sample_rate, 34 | }; 35 | let mut bss = Vec::with_capacity(bin_sizes.len()); 36 | for bs in bin_sizes { 37 | bss.push(*bs); 38 | } 39 | bss.sort(); 40 | 41 | // TODO: This is obviously improvable if we use the 42 | // result for smaller bin sizes for calculating the 43 | // larger bin sizes. 44 | for bs in bss.iter() { 45 | r.binned 46 | .insert(*bs, try!(BinnedWaveformRenderer::new(samples, *bs, config))); 47 | } 48 | 49 | Ok(r) 50 | } 51 | 52 | fn get_optimal_bin_size(&self, samples_per_pixel: f64) -> Option { 53 | 54 | let mut bin_sizes: Vec = self.binned.keys().map(|x| *x).collect(); 55 | if bin_sizes.len() == 0 { 56 | return None; 57 | } 58 | 59 | bin_sizes.sort(); 60 | let mut bin_size = bin_sizes[0]; 61 | for bs in bin_sizes.iter() { 62 | if (*bs as f64) <= samples_per_pixel { 63 | bin_size = *bs; 64 | } else { 65 | break; 66 | } 67 | } 68 | 69 | Some(bin_size) 70 | } 71 | 72 | /// Renders an image as a `Vec`. 73 | /// 74 | /// `None` will be returned if the area of the specified `shape` is equal to zero. 75 | /// 76 | /// # Arguments 77 | /// 78 | /// * `range` - The samples within this `TimeRange` will be rendered. 79 | /// * `shape` - The `(width, height)` of the resulting image in pixels. 80 | pub fn render_vec(&mut self, range: TimeRange, shape: (usize, usize)) -> Option> { 81 | let (w, h) = shape; 82 | if w == 0 || h == 0 { 83 | return None; 84 | } 85 | 86 | let (begin, end) = range.to_sample_tuple(self.sample_rate); 87 | 88 | let samples_per_pixel = ((end - begin) as f64) / (w as f64); 89 | 90 | if let Some(bin_size) = self.get_optimal_bin_size(samples_per_pixel) { 91 | return self.binned 92 | .get_mut(&bin_size) 93 | .unwrap() 94 | .render_vec(range, shape); 95 | }else{ 96 | return None; 97 | } 98 | } 99 | 100 | 101 | /// Writes the image into a mutable reference to a slice. 102 | /// 103 | /// It will raise an error if 104 | /// 105 | /// * the area of the specified `shape` is equal to zero. 106 | /// * either the width or height of the `shape` exceeds that of the `full_shape` 107 | /// of `img`. 108 | /// * the length of `img` is not long enough to contain the result. 109 | /// `(offsets.0 + shape.0) * (offsets.1 + shape.1) * (Bytes per pixel) <= img.len()` 110 | /// must be satisfied. 111 | /// 112 | /// # Arguments 113 | /// 114 | /// * `range` - The samples within this `TimeRange` will be rendered. 115 | /// * `offsets` - The `(x-offset, y-offset)` of the part of the `img` that is 116 | /// going to be overwritten in in pixels. 117 | /// Specifies the starting position to write into `img`. 118 | /// * `shape` - The `(width, height)` of the part of the `img` that is going 119 | /// to be overwritten in pixels. 120 | /// * `img` - A mutable reference to the slice to write the result into. 121 | /// * `full_shape` - The `(width, height)` of the whole `img` in pixels. 122 | /// 123 | pub fn render_write(&mut self, range: TimeRange, offsets: (usize, usize), shape: (usize, usize), img: &mut [u8], full_shape: (usize, usize)) -> Result<(), Box> { 124 | let (begin, end) = range.to_sample_tuple(self.sample_rate); 125 | 126 | let samples_per_pixel = ((end - begin) as f64) / (shape.0 as f64); 127 | 128 | if let Some(bin_size) = self.get_optimal_bin_size(samples_per_pixel) { 129 | return self.binned 130 | .get_mut(&bin_size) 131 | .unwrap() 132 | .render_write(range, offsets, shape, img, full_shape); 133 | }else{ 134 | return Err(Box::new(InvalidSizeError{var_name: "bin sizes".to_string()})); 135 | } 136 | } 137 | } 138 | 139 | #[cfg(test)] 140 | mod tests { 141 | use super::MultiWaveformRenderer; 142 | use misc::*; 143 | 144 | #[test] 145 | fn multi() { 146 | let data = vec![0f64; 50000]; 147 | let sample_rate = 44100f64; 148 | let ss = SampleSequence { 149 | data: &data[..], 150 | sample_rate, 151 | }; 152 | let foreground = Color::Vector4(255, 0, 0, 255); 153 | let background = Color::Vector4(0, 0, 0, 0); 154 | let config = WaveformConfig::new(-100f64, 100f64, foreground, background).unwrap(); 155 | let bss = vec![10, 50, 100]; 156 | let mut mwr = MultiWaveformRenderer::new(&ss, &bss, config).unwrap(); 157 | 158 | for bs in bss.iter() { 159 | assert_eq!(mwr.binned.get(bs).unwrap().get_bin_size(), *bs); 160 | assert_eq!(mwr.binned.get(bs).unwrap().get_sample_rate(), sample_rate); 161 | } 162 | 163 | mwr.render_vec(TimeRange::Seconds(0f64, 1f64), (1000, 100)) 164 | .unwrap(); 165 | } 166 | 167 | #[test] 168 | fn markers() { 169 | let c = Color::Scalar(0); 170 | let config = WaveformConfig::new(-1f64, 1f64, c, c).unwrap(); 171 | let wfr = MultiWaveformRenderer::new( 172 | &SampleSequence { 173 | data: &vec![0f64; 100], 174 | sample_rate: 44100f64, 175 | }, 176 | &vec![10usize], config 177 | ).unwrap(); 178 | let _test: &(Sync+Send) = 𝔴 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/binned.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::cmp; 3 | use error::InvalidSizeError; 4 | use misc::*; 5 | 6 | #[cfg(not(feature = "rlibc"))] 7 | use std::io::Write; 8 | 9 | #[cfg(feature = "rlibc")] 10 | use rlibc; 11 | 12 | 13 | /// A fast "binned" waveform renderer. 14 | /// 15 | /// Minimum / maximum amplitude values are binned to reduce 16 | /// calculation and memory usage. 17 | pub struct BinnedWaveformRenderer { 18 | pub config: WaveformConfig, 19 | sample_rate: f64, 20 | bin_size: usize, 21 | minmax: MinMaxPairSequence, 22 | } 23 | 24 | impl BinnedWaveformRenderer { 25 | /// The constructor. 26 | /// 27 | /// # Arguments 28 | /// 29 | /// * `samples` - The samples that will be used to calculate binned min / max values. 30 | /// It must also contain the sample rate that is used by 31 | /// `BinnedWaveformRenderer` to render images when given a 32 | /// `TimeRange::Seconds`. 33 | /// * `bin_size` - The size of the bins which the min / max values will be binned 34 | /// into. 35 | /// * `config` - See `WaveformConfig`. 36 | pub fn new(samples: &SampleSequence, bin_size: usize, config: WaveformConfig) -> Result, Box> { 37 | let mut data: Vec> = Vec::new(); 38 | let nb_samples = samples.data.len(); 39 | 40 | if bin_size > nb_samples { 41 | return Err(Box::new(InvalidSizeError { 42 | var_name: "bin_size".to_string(), 43 | })); 44 | } 45 | 46 | let nb_bins = (nb_samples as f64 / bin_size as f64).ceil() as usize; 47 | 48 | for x in 0..nb_bins { 49 | let mut min = samples.data[x * bin_size + 0]; 50 | let mut max = samples.data[x * bin_size + 0]; 51 | if bin_size > 1 { 52 | for i in 1..bin_size { 53 | let idx = x * bin_size + i; 54 | if idx >= nb_samples { 55 | break; 56 | } 57 | let s = samples.data[idx]; 58 | if s > max { 59 | max = s; 60 | } else if s < min { 61 | min = s; 62 | } 63 | } 64 | } 65 | data.push(MinMaxPair { min: min, max: max }); 66 | } 67 | let minmax = MinMaxPairSequence { data: data }; 68 | Ok(Self { 69 | config: config, 70 | bin_size: bin_size, 71 | minmax: minmax, 72 | sample_rate: samples.sample_rate, 73 | }) 74 | } 75 | 76 | 77 | /// Renders an image as a `Vec`. 78 | /// 79 | /// `None` will be returned if the area of the specified `shape` is equal to zero. 80 | /// 81 | /// # Arguments 82 | /// 83 | /// * `range` - The samples within this `TimeRange` will be rendered. 84 | /// * `shape` - The `(width, height)` of the resulting image in pixels. 85 | pub fn render_vec(&self, range: TimeRange, shape: (usize, usize)) -> Option> { 86 | let (w, h) = shape; 87 | if w == 0 || h == 0 { 88 | return None; 89 | } 90 | 91 | 92 | let mut img = match self.config.get_background() { 93 | Color::Scalar(_) => vec![0u8; w * h], 94 | Color::Vector3{..} => vec![0u8; w * h * 3], 95 | Color::Vector4{..} => vec![0u8; w * h * 4], 96 | }; 97 | 98 | self.render_write(range, (0, 0), shape, &mut img[..], shape).unwrap(); 99 | 100 | Some(img) 101 | } 102 | 103 | /// Writes the image into a mutable reference to a slice. 104 | /// 105 | /// It will raise an error if 106 | /// 107 | /// * the area of the specified `shape` is equal to zero. 108 | /// * either the width or height of the `shape` exceeds that of the `full_shape` 109 | /// of `img`. 110 | /// * the length of `img` is not long enough to contain the result. 111 | /// `(offsets.0 + shape.0) * (offsets.1 + shape.1) * (Bytes per pixel) <= img.len()` 112 | /// must be satisfied. 113 | /// 114 | /// # Arguments 115 | /// 116 | /// * `range` - The samples within this `TimeRange` will be rendered. 117 | /// * `offsets` - The `(x-offset, y-offset)` of the part of the `img` that is 118 | /// going to be overwritten in in pixels. 119 | /// Specifies the starting position to write into `img`. 120 | /// * `shape` - The `(width, height)` of the part of the `img` that is going 121 | /// to be overwritten in pixels. 122 | /// * `img` - A mutable reference to the slice to write the result into. 123 | /// * `full_shape` - The `(width, height)` of the whole `img` in pixels. 124 | /// 125 | pub fn render_write(&self, range: TimeRange, offsets: (usize, usize), shape: (usize, usize), img: &mut [u8], full_shape: (usize, usize)) -> Result<(), Box> { 126 | let (w, h) = shape; 127 | if w == 0 || h == 0 { 128 | return Err(Box::new(InvalidSizeError{var_name: "shape".to_string()})); 129 | } 130 | 131 | let (fullw, fullh) = full_shape; 132 | if fullw < w || fullh < h { 133 | return Err(Box::new(InvalidSizeError{var_name: "shape and/or full_shape".to_string()})); 134 | } 135 | 136 | let (offx, offy) = offsets; 137 | 138 | // Check if we have enough bytes in `img` 139 | match self.config.get_background() { 140 | Color::Scalar(_) => { 141 | if (offx + w) * (offy + h) > img.len() { 142 | return Err(Box::new(InvalidSizeError{var_name: "offsets and/or shape".to_string()})); 143 | } 144 | }, 145 | Color::Vector3{..} => { 146 | if (offx + w) * (offy + h) * 3 > img.len() { 147 | return Err(Box::new(InvalidSizeError{var_name: "offsets and/or shape".to_string()})); 148 | } 149 | }, 150 | Color::Vector4{..} => { 151 | if (offx + w) * (offy + h) * 4 > img.len() { 152 | return Err(Box::new(InvalidSizeError{var_name: "offsets and/or shape".to_string()})); 153 | } 154 | }, 155 | } 156 | 157 | let (begin, end) = match range { 158 | TimeRange::Seconds(b, e) => ( 159 | (b * self.sample_rate) as usize, 160 | (e * self.sample_rate) as usize, 161 | ), 162 | TimeRange::Samples(b, e) => (b, e), 163 | }; 164 | let nb_samples = end - begin; 165 | let samples_per_pixel = (nb_samples as f64) / (w as f64); 166 | let bins_per_pixel = samples_per_pixel / (self.bin_size as f64); 167 | let bins_per_pixel_floor = bins_per_pixel.floor() as usize; 168 | let bins_per_pixel_ceil = bins_per_pixel.ceil() as usize; 169 | 170 | let offset_bin_idx = begin / self.bin_size; 171 | let mut start_bin_idx = offset_bin_idx; 172 | for x in 0..w { 173 | let inc = if ((start_bin_idx - offset_bin_idx) as f64 + 1f64) / (x as f64) < bins_per_pixel { 174 | bins_per_pixel_ceil 175 | } else { 176 | bins_per_pixel_floor 177 | }; 178 | 179 | let mut min: T; 180 | let mut max: T; 181 | if start_bin_idx < self.minmax.data.len() - 1 { 182 | let ref d = self.minmax.data[start_bin_idx]; 183 | min = d.min; 184 | max = d.max; 185 | let range_start = start_bin_idx; 186 | let range_end = if start_bin_idx + inc <= self.minmax.data.len() { 187 | start_bin_idx + inc 188 | } else { 189 | self.minmax.data.len() 190 | }; 191 | for b in self.minmax.data[range_start..range_end].iter() { 192 | if b.min < min { 193 | min = b.min 194 | } 195 | if b.max > max { 196 | max = b.max 197 | } 198 | } 199 | start_bin_idx = range_end; 200 | } else { 201 | min = T::zero(); 202 | max = T::zero(); 203 | } 204 | 205 | let scale = 1f64 / (self.config.amp_max - self.config.amp_min) * (h as f64); 206 | let min_translated: usize = h - 207 | cmp::max( 208 | 0, 209 | cmp::min( 210 | h as i32, 211 | ((min.into() - self.config.amp_min) * scale).floor() as i32, 212 | ), 213 | ) as usize; 214 | let max_translated: usize = h - 215 | cmp::max( 216 | 0, 217 | cmp::min( 218 | h as i32, 219 | ((max.into() - self.config.amp_min) * scale).floor() as i32, 220 | ), 221 | ) as usize; 222 | 223 | // Putting this `match` outside for loops improved the speed. 224 | match (self.config.get_background(), self.config.get_foreground()) { 225 | (Color::Scalar(ba), Color::Scalar(fa)) => { 226 | flipping_three_segment_for!{ 227 | for y in 0, max_translated, min_translated, h, { 228 | pixel!(img[fullw, fullh; offx+x, offy+y]) = ba, 229 | pixel!(img[fullw, fullh; offx+x, offy+y]) = fa 230 | } 231 | } 232 | }, 233 | 234 | ( 235 | Color::Vector3 (br, bg, bb), 236 | Color::Vector3 (fr, fg, fb), 237 | ) => { 238 | // Order the RGB values so we can directly 239 | // copy them into the image. 240 | let bg_colors: [u8; 3] = [br, bg, bb]; 241 | let fg_colors: [u8; 3] = [fr, fg, fb]; 242 | 243 | // Each `flipping_three_segment_for` macro 244 | // will be expanded into three for loops below. 245 | // 246 | // I could have used just one for loop (and I did once) 247 | // but this made a significant difference in 248 | // the performance. 249 | // 250 | // The `pixel` macro is used to access pixels. 251 | // 252 | // See src/macros/*.rs for the defenitions. 253 | 254 | 255 | #[cfg(feature = "rlibc")] 256 | unsafe { 257 | flipping_three_segment_for!{ 258 | for y in 0, max_translated, min_translated, h, { 259 | rlibc::memcpy( 260 | &mut pixel!(img[fullw, fullh, 3; offx+x, offy+y, 0]) as _, 261 | &bg_colors[0] as _, 262 | 3 263 | ), 264 | rlibc::memcpy( 265 | &mut pixel!(img[fullw, fullh, 3; offx+x, offy+y, 0]) as _, 266 | &fg_colors[0] as _, 267 | 3 268 | ) 269 | } 270 | } 271 | } 272 | 273 | // A similar implementation is possible without 274 | // the rlibc crate, but it appeared to be 275 | // slightly slower. 276 | #[cfg(not(feature = "rlibc"))] 277 | { 278 | flipping_three_segment_for!{ 279 | for y in 0, max_translated, min_translated, h, { 280 | (&mut pixel!(img[fullw, fullh, 3; offx+x, offy+y, 0 => 4])) 281 | .write(&bg_colors).unwrap(), 282 | (&mut pixel!(img[fullw, fullh, 3; offx+x, offy+y, 0 => 4])) 283 | .write(&fg_colors).unwrap() 284 | } 285 | } 286 | } 287 | 288 | }, 289 | 290 | ( 291 | Color::Vector4 (br, bg, bb, ba), 292 | Color::Vector4 (fr, fg, fb, fa), 293 | ) => { 294 | 295 | // Order the RGBA values so we can directly 296 | // copy them into the image. 297 | let bg_colors: [u8; 4] = [br, bg, bb, ba]; 298 | let fg_colors: [u8; 4] = [fr, fg, fb, fa]; 299 | 300 | // Each `flipping_three_segment_for` macro 301 | // will be expanded into three for loops below. 302 | // 303 | // I could have used just one for loop (and I did once) 304 | // but this made a significant difference in 305 | // the performance. 306 | // 307 | // The `pixel` macro is used to access pixels. 308 | // 309 | // See src/macros/*.rs for the defenitions. 310 | 311 | 312 | #[cfg(feature = "rlibc")] 313 | unsafe { 314 | flipping_three_segment_for!{ 315 | for y in 0, max_translated, min_translated, h, { 316 | rlibc::memcpy( 317 | &mut pixel!(img[fullw, fullh, 4; offx+x, offy+y, 0]) as _, 318 | &bg_colors[0] as _, 319 | 4 320 | ), 321 | rlibc::memcpy( 322 | &mut pixel!(img[fullw, fullh, 4; offx+x, offy+y, 0]) as _, 323 | &fg_colors[0] as _, 324 | 4 325 | ) 326 | } 327 | } 328 | } 329 | 330 | // A similar implementation is possible without 331 | // the rlibc crate, but it appeared to be 332 | // slightly slower. 333 | #[cfg(not(feature = "rlibc"))] 334 | { 335 | flipping_three_segment_for!{ 336 | for y in 0, max_translated, min_translated, h, { 337 | (&mut pixel!(img[fullw, fullh, 4; offx+x, offy+y, 0 => 4])) 338 | .write(&bg_colors).unwrap(), 339 | (&mut pixel!(img[fullw, fullh, 4; offx+x, offy+y, 0 => 4])) 340 | .write(&fg_colors).unwrap() 341 | } 342 | } 343 | } 344 | }, 345 | 346 | // This case is unreachable because inconsistent 347 | // `Color` formats are checked whenever a user 348 | // creates a `WaveformConfig`. 349 | (_, _) => unreachable!(), 350 | } 351 | } 352 | 353 | Ok(()) 354 | } 355 | 356 | pub fn get_bin_size(&self) -> usize { 357 | self.bin_size 358 | } 359 | pub fn get_sample_rate(&self) -> f64 { 360 | self.sample_rate 361 | } 362 | } 363 | 364 | #[cfg(test)] 365 | mod tests { 366 | use super::BinnedWaveformRenderer; 367 | use ::misc::*; 368 | 369 | #[test] 370 | fn render_vec_and_write_eq() { 371 | let tr = TimeRange::Seconds(0f64, 10f64); 372 | let (width, height) = (1000, 100); 373 | let mut samples: Vec = Vec::new(); 374 | for t in 0u32..44100u32 { 375 | samples.push(((t as f64) * 0.01f64 * 2f64 * 3.1415f64).sin()); 376 | } 377 | let config = WaveformConfig::new( 378 | -1f64, 379 | 1f64, 380 | Color::Vector4(0, 0, 0, 255), 381 | Color::Vector4(0, 0, 0, 255) 382 | ).unwrap(); 383 | let wfr = BinnedWaveformRenderer::new( 384 | &SampleSequence { 385 | data: &samples[..], 386 | sample_rate: 44100f64, 387 | }, 388 | 10, 389 | config, 390 | ).unwrap(); 391 | 392 | let v1 = wfr.render_vec(tr, (width, height)).unwrap(); 393 | 394 | let mut v2: Vec = vec![0; width*height*4]; 395 | 396 | wfr.render_write(tr, (0, 0), (width, height), &mut v2[..], (width, height)).unwrap(); 397 | 398 | assert_eq!(v1, v2); 399 | } 400 | 401 | #[test] 402 | fn markers() { 403 | let c = Color::Scalar(0); 404 | let config = WaveformConfig::new(-1f64, 1f64, c, c).unwrap(); 405 | let wfr = BinnedWaveformRenderer::new( 406 | &SampleSequence { 407 | data: &vec![0f64; 10], 408 | sample_rate: 44100f64, 409 | }, 410 | 10, config 411 | ).unwrap(); 412 | let _test: &(Sync+Send) = 𝔴 413 | } 414 | } 415 | --------------------------------------------------------------------------------