5 Commits

Author SHA1 Message Date
37b5bf3645 dummy example 2020-09-30 16:12:07 +01:00
183f1064ec todo 2020-09-30 12:47:26 +01:00
c7d94235e9 move readme again 2020-09-26 11:44:32 +01:00
575f0887df update readme 2020-09-26 11:26:45 +01:00
f54853fea2 move audio credits to separate readme 2020-09-26 11:24:40 +01:00
9 changed files with 335 additions and 145 deletions

4
.gitignore vendored
View File

@@ -1,6 +1,8 @@
/target /target
/Cargo.lock /Cargo.lock
/.idea/ /.idea/
/sounds/ /sounds/*.flac
/sounds/*.mp3
/sounds/*.wav
/sounds.zip /sounds.zip
/lull.zip /lull.zip

View File

@@ -8,7 +8,10 @@ edition = "2018"
crate_type = "bin" crate_type = "bin"
[dependencies] [dependencies]
clipboard = "^0.5.0"
dirs = "^3.0.1" dirs = "^3.0.1"
gio = "^0" glium = "^0.27.0"
gtk = "^0" imgui = "^0.5.0"
imgui-glium-renderer = "^0.5.0"
imgui-winit-support = "^0.5.0"
rodio = "^0.11.0" rodio = "^0.11.0"

View File

@@ -1,8 +1,14 @@
# lull # lull
https://ruin.itch.io/lull
a looping sound player. a looping sound player.
add your own favourite noises and blend them to create your ideal ambience. add your own favourite noises and blend them to create your ideal ambience.
created by [Max Bradbury](mailto:max@tinybird.info).
released under the MIT license.
## to do ## to do
* save volume preferences to disk * save volume preferences to disk
@@ -13,15 +19,9 @@ add your own favourite noises and blend them to create your ideal ambience.
* rain on tin roof * rain on tin roof
* wind * wind
* tape hiss * tape hiss
* vinyl crackle
* white noise? * white noise?
* fan * fan
* birdsong
* set a window icon * set a window icon
* create a nice icon? * create a nice icon?
## audio credits
*rain under parasol* and *waterfall* by [Samuel Strågefors](https://freesound.org/people/straget/)
*waves* by [Florian Reichelt](https://freesound.org/people/florianreichelt/)
*campfire* by [sagetyrtle](https://freesound.org/people/sagetyrtle/)

19
SOUNDS.md Normal file
View File

@@ -0,0 +1,19 @@
# lull - basic sounds pack
## installation
1. open *lull*
2. click "manage sounds" and the sounds folder will open in your file manager.
3. extract these sounds to the sounds folder
you can skip/remove any unwanted sounds, and add your own.
## credits
*waterfall* and *rain on parasol* by [Samuel Strågefors](https://freesound.org/people/straget/)
*waves* by [Florian Reichelt](https://freesound.org/people/florianreichelt/)
*campfire* by [sagetyrtle](https://freesound.org/people/sagetyrtle/)
*rain on glass* by [Benboncan](https://freesound.org/people/Benboncan/)

2
build.sh Normal file → Executable file
View File

@@ -6,4 +6,4 @@ strip lull
zip -r lull.zip README.md lull zip -r lull.zip README.md lull
rm lull rm lull
zip -r sounds.zip README.md sounds/*.mp3 zip -r sounds.zip SOUNDS.md sounds/*.mp3

View File

@@ -1,39 +1,39 @@
#![windows_subsystem = "windows"] #![windows_subsystem = "windows"]
use gio::prelude::*;
use gtk::prelude::*;
use gtk::Orientation;
use rodio::{Sink, Source}; use rodio::{Sink, Source};
use std::env::args; use std::env::args;
use std::fs::File; use std::fs::File;
use std::io::BufReader; use std::io::BufReader;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use imgui::*;
mod support;
const SPACING: i32 = 16; const SPACING: i32 = 16;
//
fn error_popup(message: &str) { // fn error_popup(message: &str) {
let popup = gtk::Window::new(gtk::WindowType::Toplevel); // let popup = gtk::Window::new(gtk::WindowType::Toplevel);
popup.set_title("error"); // popup.set_title("error");
popup.set_border_width(SPACING as u32); // popup.set_border_width(SPACING as u32);
popup.set_position(gtk::WindowPosition::Center); // popup.set_position(gtk::WindowPosition::Center);
popup.set_default_size(256, 64); // popup.set_default_size(256, 64);
//
let vertical = gtk::Box::new(Orientation::Vertical, SPACING); // let vertical = gtk::Box::new(Orientation::Vertical, SPACING);
popup.add(&vertical); // popup.add(&vertical);
//
let message = gtk::Label::new(Some(message)); // let message = gtk::Label::new(Some(message));
vertical.add(&message); // vertical.add(&message);
//
let button_ok = gtk::Button::with_label("OK"); // let button_ok = gtk::Button::with_label("OK");
vertical.add(&button_ok); // vertical.add(&button_ok);
//
popup.show_all(); // popup.show_all();
//
button_ok.connect_clicked(move |_| unsafe { // button_ok.connect_clicked(move |_| unsafe {
popup.destroy(); // popup.destroy();
}); // });
} // }
fn get_data_dir() -> PathBuf { fn get_data_dir() -> PathBuf {
let mut data_dir = dirs::data_dir().expect("Couldn't find user data directory"); let mut data_dir = dirs::data_dir().expect("Couldn't find user data directory");
@@ -46,115 +46,122 @@ fn get_data_dir() -> PathBuf {
data_dir data_dir
} }
//
fn build_ui(application: &gtk::Application) { // fn build_ui(application: &gtk::Application) {
let window = gtk::ApplicationWindow::new(application); // let window = gtk::ApplicationWindow::new(application);
//
window.set_title("lull"); // window.set_title("lull");
window.set_border_width(SPACING as u32); // window.set_border_width(SPACING as u32);
window.set_position(gtk::WindowPosition::Center); // window.set_position(gtk::WindowPosition::Center);
window.set_default_size(256, 256); // window.set_default_size(256, 256);
//
let vertical = gtk::Box::new(Orientation::Vertical, SPACING); // let vertical = gtk::Box::new(Orientation::Vertical, SPACING);
vertical.set_homogeneous(true); // vertical.set_homogeneous(true);
//
window.add(&vertical); // window.add(&vertical);
//
let device = rodio::default_output_device().unwrap(); // let device = rodio::default_output_device().unwrap();
//
let paths = std::fs::read_dir(get_data_dir()) // let paths = std::fs::read_dir(get_data_dir())
.expect("Couldn't read from lull data directory"); // .expect("Couldn't read from lull data directory");
//
for path in paths { // for path in paths {
let path = path.unwrap().path(); // let path = path.unwrap().path();
let name: &str = path.file_stem().unwrap().to_str().unwrap(); // let name: &str = path.file_stem().unwrap().to_str().unwrap();
//
let file = File::open(&path) // let file = File::open(&path)
.expect("Couldn't open audio file"); // .expect("Couldn't open audio file");
//
let source = rodio::Decoder::new( // let source = rodio::Decoder::new(
BufReader::new(file) // BufReader::new(file)
); // );
//
if source.is_err() { // if source.is_err() {
error_popup(&format!( // error_popup(&format!(
"Couldn't parse file {}. \n{}.", // "Couldn't parse file {}. \n{}.",
path.to_str().unwrap(), // path.to_str().unwrap(),
source.err().unwrap() // source.err().unwrap()
)); // ));
continue; // continue;
} // }
//
let source = source.unwrap().repeat_infinite(); // let source = source.unwrap().repeat_infinite();
//
let sink = Sink::new(&device); // let sink = Sink::new(&device);
sink.append(source); // sink.append(source);
sink.pause(); // sink.pause();
//
let row = gtk::Box::new(Orientation::Horizontal, SPACING); // let row = gtk::Box::new(Orientation::Horizontal, SPACING);
row.set_homogeneous(true); // row.set_homogeneous(true);
//
let label = gtk::Label::new(Some(name)); // let label = gtk::Label::new(Some(name));
row.add(&label); // row.add(&label);
//
let adjustment = gtk::Adjustment::new( // let adjustment = gtk::Adjustment::new(
0.0, // 0.0,
0.0, // 0.0,
1.0, // 1.0,
0.0, // 0.0,
0.0, // 0.0,
0.0 // 0.0
); // );
//
let slider = gtk::Scale::new( // let slider = gtk::Scale::new(
Orientation::Horizontal, // Orientation::Horizontal,
Some(&adjustment) // Some(&adjustment)
); // );
//
slider.set_draw_value(false); // slider.set_draw_value(false);
//
slider.connect_value_changed(move |scale| { // slider.connect_value_changed(move |scale| {
let volume = scale.get_value(); // let volume = scale.get_value();
//
if volume == 0. { // if volume == 0. {
sink.pause(); // sink.pause();
} else { // } else {
sink.play(); // sink.play();
sink.set_volume(volume as f32); // sink.set_volume(volume as f32);
} // }
}); // });
//
row.add(&slider); // row.add(&slider);
//
vertical.add(&row); // vertical.add(&row);
} // }
//
let row_add = gtk::Box::new(Orientation::Horizontal, SPACING); // let row_add = gtk::Box::new(Orientation::Horizontal, SPACING);
row_add.set_homogeneous(true); // row_add.set_homogeneous(true);
//
let button_manage_sounds = gtk::Button::with_label("manage sounds"); // let button_manage_sounds = gtk::Button::with_label("manage sounds");
//
button_manage_sounds.connect_clicked(|_| { // button_manage_sounds.connect_clicked(|_| {
let mut file_manager = Command::new("xdg-open"); // let mut file_manager = Command::new("xdg-open");
file_manager.arg(get_data_dir()); // file_manager.arg(get_data_dir());
file_manager.output().unwrap(); // file_manager.output().unwrap();
}); // });
//
row_add.add(&button_manage_sounds); // row_add.add(&button_manage_sounds);
vertical.add(&row_add); // vertical.add(&row_add);
//
window.show_all(); // window.show_all();
} // }
fn main() { fn main() {
let application = gtk::Application::new( let system = support::init(file!());
Some("dev.tinybird.max.lull"),
Default::default()
).expect("Initialization failed...");
application.connect_activate(|app| { system.main_loop(move |_, ui| {
build_ui(app); Window::new(im_str!("Hello world"))
.size([300.0, 100.0], Condition::FirstUseEver)
.build(&ui, || {
ui.text(im_str!("Hello world!"));
ui.text(im_str!("こんにちは世界!"));
ui.text(im_str!("This...is...imgui-rs!"));
ui.separator();
let mouse_pos = ui.io().mouse_pos;
ui.text(format!(
"Mouse Position: ({:.1},{:.1})",
mouse_pos[0], mouse_pos[1]
));
});
}); });
application.run(&args().collect::<Vec<_>>());
} }

19
src/support/clipboard.rs Normal file
View File

@@ -0,0 +1,19 @@
use clipboard::{ClipboardContext, ClipboardProvider};
use imgui::{ClipboardBackend, ImStr, ImString};
pub struct ClipboardSupport(ClipboardContext);
pub fn init() -> Option<ClipboardSupport> {
ClipboardContext::new()
.ok()
.map(|ctx| ClipboardSupport(ctx))
}
impl ClipboardBackend for ClipboardSupport {
fn get(&mut self) -> Option<ImString> {
self.0.get_contents().ok().map(|text| text.into())
}
fn set(&mut self, text: &ImStr) {
let _ = self.0.set_contents(text.to_str().to_owned());
}
}

BIN
src/support/helsinki.ttf Normal file

Binary file not shown.

140
src/support/mod.rs Normal file
View File

@@ -0,0 +1,140 @@
use glium::glutin;
use glium::glutin::event::{Event, WindowEvent};
use glium::glutin::event_loop::{ControlFlow, EventLoop};
use glium::glutin::window::WindowBuilder;
use glium::{Display, Surface};
use imgui::{Context, FontConfig, FontGlyphRanges, FontSource, Ui};
use imgui_glium_renderer::Renderer;
use imgui_winit_support::{HiDpiMode, WinitPlatform};
use std::time::Instant;
mod clipboard;
pub struct System {
pub event_loop: EventLoop<()>,
pub display: glium::Display,
pub imgui: Context,
pub platform: WinitPlatform,
pub renderer: Renderer,
pub font_size: f32,
}
pub fn init(title: &str) -> System {
let title = match title.rfind('/') {
Some(idx) => title.split_at(idx + 1).1,
None => title,
};
let event_loop = EventLoop::new();
let context = glutin::ContextBuilder::new().with_vsync(true);
let builder = WindowBuilder::new()
.with_title(title.to_owned())
.with_inner_size(glutin::dpi::LogicalSize::new(1024f64, 768f64));
let display =
Display::new(builder, context, &event_loop).expect("Failed to initialize display");
let mut imgui = Context::create();
imgui.set_ini_filename(None);
if let Some(backend) = clipboard::init() {
imgui.set_clipboard_backend(Box::new(backend));
} else {
eprintln!("Failed to initialize clipboard");
}
let mut platform = WinitPlatform::init(&mut imgui);
{
let gl_window = display.gl_window();
let window = gl_window.window();
platform.attach_window(imgui.io_mut(), &window, HiDpiMode::Rounded);
}
let hidpi_factor = platform.hidpi_factor();
let font_size = (13.0 * hidpi_factor) as f32;
imgui.fonts().add_font(&[
FontSource::DefaultFontData {
config: Some(FontConfig {
size_pixels: font_size,
..FontConfig::default()
}),
},
FontSource::TtfData {
data: include_bytes!("helsinki.ttf"),
size_pixels: font_size,
config: Some(FontConfig {
rasterizer_multiply: 1.75,
glyph_ranges: FontGlyphRanges::japanese(),
..FontConfig::default()
}),
},
]);
imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32;
let renderer = Renderer::init(&mut imgui, &display)
.expect("Failed to initialize renderer");
System {
event_loop,
display,
imgui,
platform,
renderer,
font_size,
}
}
impl System {
pub fn main_loop<F: FnMut(&mut bool, &mut Ui) + 'static>(self, mut run_ui: F) {
let System {
event_loop,
display,
mut imgui,
mut platform,
mut renderer,
..
} = self;
let mut last_frame = Instant::now();
event_loop.run(move |event, _, control_flow| match event {
Event::NewEvents(_) => {
let now = Instant::now();
imgui.io_mut().update_delta_time(now - last_frame);
last_frame = now;
}
Event::MainEventsCleared => {
let gl_window = display.gl_window();
platform
.prepare_frame(imgui.io_mut(), &gl_window.window())
.expect("Failed to prepare frame");
gl_window.window().request_redraw();
}
Event::RedrawRequested(_) => {
let mut ui = imgui.frame();
let mut run = true;
run_ui(&mut run, &mut ui);
if !run {
*control_flow = ControlFlow::Exit;
}
let gl_window = display.gl_window();
let mut target = display.draw();
target.clear_color_srgb(1.0, 1.0, 1.0, 1.0);
platform.prepare_render(&ui, gl_window.window());
let draw_data = ui.render();
renderer
.render(&mut target, draw_data)
.expect("Rendering failed");
target.finish().expect("Failed to swap buffers");
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
event => {
let gl_window = display.gl_window();
platform.handle_event(imgui.io_mut(), gl_window.window(), &event);
}
})
}
}