├── .github └── FUNDING.yml ├── po ├── LINGUAS ├── meson.build ├── POTFILES ├── chromatic.pot ├── fr.po ├── de.po ├── it.po └── pt_br.po ├── src ├── style.css ├── config.rs.in ├── chromatic.gresource.xml ├── ui │ ├── help-overlay.ui │ ├── preferences_window.ui │ └── window.ui ├── util.rs ├── toasts.rs ├── meson.build ├── main.rs ├── application.rs ├── i18n.rs ├── window.rs ├── preferences_window.rs ├── recorder.rs └── gauge.rs ├── setup.sh ├── data ├── screenshots │ └── chromatic--0.png ├── io.github.nate_xyz.Chromatic.desktop.in ├── icons │ ├── meson.build │ └── hicolor │ │ └── symbolic │ │ └── apps │ │ └── io.github.nate_xyz.Chromatic-symbolic.svg ├── io.github.nate_xyz.Chromatic.gschema.xml ├── meson.build └── io.github.nate_xyz.Chromatic.appdata.xml.in ├── run.sh ├── Cargo.toml ├── meson.build ├── README.md ├── flatpak └── io.github.nate_xyz.Chromatic.json ├── Cargo.lock └── COPYING /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: natexyz -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | fr 2 | it 3 | de 4 | pt_br 5 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | .tab-nums { 2 | font-variant-numeric: tabular-nums 3 | } -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sudo rm -R _builddir && \ 4 | meson setup _builddir && \ 5 | sh run.sh -------------------------------------------------------------------------------- /data/screenshots/chromatic--0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nate-xyz/chromatic/HEAD/data/screenshots/chromatic--0.png -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | meson compile -C _builddir --verbose && \ 4 | RUST_LOG=trace meson devenv -C _builddir ./src/chromatic; exit; -------------------------------------------------------------------------------- /src/config.rs.in: -------------------------------------------------------------------------------- 1 | pub static VERSION: &str = @VERSION@; 2 | pub static GETTEXT_PACKAGE: &str = @GETTEXT_PACKAGE@; 3 | pub static LOCALEDIR: &str = @LOCALEDIR@; 4 | pub static PKGDATADIR: &str = @PKGDATADIR@; 5 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | # i18n.gettext('chromatic', preset: 'glib') 2 | 3 | i18n.gettext( 4 | 'chromatic', 5 | args: [ 6 | '--keyword=i18n', 7 | '--keyword=i18n_f', 8 | '--keyword=i18n_k', 9 | '--keyword=ni18n:1,2', 10 | '--keyword=ni18n_f:1,2', 11 | '--keyword=ni18n_k:1,2', 12 | ], 13 | preset: 'glib', 14 | ) 15 | -------------------------------------------------------------------------------- /po/POTFILES: -------------------------------------------------------------------------------- 1 | data/io.github.nate_xyz.Chromatic.desktop.in 2 | data/io.github.nate_xyz.Chromatic.appdata.xml.in 3 | data/io.github.nate_xyz.Chromatic.gschema.xml 4 | 5 | src/window.rs 6 | src/util.rs 7 | src/recorder.rs 8 | src/gauge.rs 9 | src/preferences_window.rs 10 | src/toasts.rs 11 | src/ui/window.ui 12 | src/ui/help-overlay.ui 13 | src/ui/preferences_window.ui -------------------------------------------------------------------------------- /src/chromatic.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ui/window.ui 5 | ui/help-overlay.ui 6 | ui/preferences_window.ui 7 | style.css 8 | 9 | -------------------------------------------------------------------------------- /data/io.github.nate_xyz.Chromatic.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Chromatic 3 | GenericName=Instrument Tuner 4 | Comment=Fine-tune your instruments 5 | Exec=chromatic 6 | Icon=io.github.nate_xyz.Chromatic 7 | Terminal=false 8 | Type=Application 9 | Categories=GNOME;GTK;Audio;Utility; 10 | StartupNotify=true 11 | # Translators: Search terms to find this application. Do not translate or localize the semicolons! The list must also end with a semicolon. 12 | Keywords=instrument;tuner;chromatic; -------------------------------------------------------------------------------- /data/icons/meson.build: -------------------------------------------------------------------------------- 1 | application_id = 'io.github.nate_xyz.Chromatic' 2 | 3 | scalable_dir = join_paths('hicolor', 'scalable', 'apps') 4 | install_data( 5 | join_paths(scalable_dir, ('@0@.svg').format(application_id)), 6 | install_dir: join_paths(get_option('datadir'), 'icons', scalable_dir) 7 | ) 8 | 9 | symbolic_dir = join_paths('hicolor', 'symbolic', 'apps') 10 | install_data( 11 | join_paths(symbolic_dir, ('@0@-symbolic.svg').format(application_id)), 12 | install_dir: join_paths(get_option('datadir'), 'icons', symbolic_dir) 13 | ) 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chromatic" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | gettext-rs = { version = "0.7", features = ["gettext-system"] } 8 | gtk = { version = "0.5", package = "gtk4" } 9 | log = "0.4" 10 | pretty_env_logger = "0.4" 11 | regex = "1.7.1" 12 | fuzzy-matcher = "0.3.7" 13 | once_cell = "1.10" 14 | pulsectl-rs = "0.3.2" 15 | portaudio = "0.7.0" 16 | aubio = { version = "0.2.0", package = "aubio-rs" } 17 | pitch_calc = "0.12.0" 18 | 19 | [dependencies.adw] 20 | package = "libadwaita" 21 | version = "0.2" 22 | features = ["v1_2"] -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('chromatic', 'rust', 2 | version: '0.1.2', 3 | meson_version: '>= 0.59.0', 4 | default_options: [ 'warning_level=3', 'werror=false', ], 5 | ) 6 | 7 | i18n = import('i18n') 8 | gnome = import('gnome') 9 | 10 | dependency('gtk4', version: '>= 4.6.0') 11 | dependency('libadwaita-1', version: '>= 1.2.0') 12 | 13 | cargo = find_program('cargo', required: true) 14 | cargo_sources = files( 15 | 'Cargo.toml', 16 | 'Cargo.lock', 17 | ) 18 | 19 | subdir('data') 20 | subdir('src') 21 | subdir('po') 22 | 23 | gnome.post_install( 24 | glib_compile_schemas: true, 25 | gtk_update_icon_cache: true, 26 | update_desktop_database: true, 27 | ) 28 | 29 | summary({ 30 | 'prefix': get_option('prefix'), 31 | 'libdir': get_option('libdir'), 32 | 'datadir': get_option('datadir'), 33 | 'bindir': get_option('bindir'), 34 | }, 35 | section: 'Directories', 36 | ) 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Chromatic 4 | ====== 5 | 6 | Fine-tune your instruments with Chromatic. 7 | Chromatic detects the frequency of audio input, converts it to a musical note with the correct semitone and octave, and displays the cents error. Cents are displayed on an analog gauge to make tuning more visually intuitive. Requires PulseAudio or PipeWire. 8 | 9 |

10 | 11 | ![Screenshot 0](./data/screenshots/chromatic--0.png) 12 | 13 | Flatpak 14 | -------------- 15 | 16 | You can install stable builds of Chromatic from [Flathub](https://flathub.org) 17 | by using this command: 18 | 19 | flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo 20 | flatpak install flathub io.github.nate_xyz.Chromatic 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/ui/help-overlay.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | True 5 | 6 | 7 | shortcuts 8 | 10 9 | 10 | 11 | General 12 | 13 | 14 | Show Shortcuts 15 | win.show-help-overlay 16 | 17 | 18 | 19 | 20 | Quit 21 | app.quit 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use gtk::prelude::*; 2 | use gtk::gio; 3 | 4 | use std::{cell::RefCell, rc::Rc}; 5 | 6 | use super::window::Window; 7 | use super::recorder::Recorder; 8 | use super::gauge::Gauge; 9 | 10 | pub fn settings_manager() -> gio::Settings { 11 | // // We ship a single schema for both default and development profiles 12 | // let app_id = APPLICATION_ID.trim_end_matches(".Devel"); 13 | let app_id = "io.github.nate_xyz.Chromatic"; 14 | gio::Settings::new(app_id) 15 | } 16 | 17 | 18 | pub fn window() -> Window { 19 | active_window() 20 | .unwrap() 21 | .downcast::() 22 | .unwrap() 23 | } 24 | 25 | pub fn active_window() -> Option { 26 | let app = gio::Application::default() 27 | .expect("Failed to retrieve application singleton") 28 | .downcast::() 29 | .unwrap(); 30 | 31 | let win = app 32 | .active_window(); 33 | 34 | win 35 | } 36 | 37 | pub fn recorder() -> Rc{ 38 | active_window() 39 | .unwrap() 40 | .downcast::() 41 | .unwrap() 42 | .recorder() 43 | } 44 | 45 | 46 | pub fn gauge() -> Rc>> { 47 | active_window() 48 | .unwrap() 49 | .downcast::() 50 | .unwrap() 51 | .gauge() 52 | } -------------------------------------------------------------------------------- /data/io.github.nate_xyz.Chromatic.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | Manually select audio device 8 | 9 | 10 | 11 | "" 12 | 13 | 14 | 15 | true 16 | Show gauge widget 17 | 18 | 19 | 20 | 21 | false 22 | Show window title 23 | 24 | 25 | 26 | 27 | 6144.0 28 | Stream buffer size 29 | 30 | 31 | 32 | 33 | 1 34 | 35 | 36 | 37 | 38 | 3 39 | 40 | 41 | 42 | 43 | -45 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | desktop_file = i18n.merge_file( 2 | input: 'io.github.nate_xyz.Chromatic.desktop.in', 3 | output: 'io.github.nate_xyz.Chromatic.desktop', 4 | type: 'desktop', 5 | po_dir: '../po', 6 | install: true, 7 | install_dir: join_paths(get_option('datadir'), 'applications') 8 | ) 9 | 10 | desktop_utils = find_program('desktop-file-validate', required: false) 11 | if desktop_utils.found() 12 | test('Validate desktop file', desktop_utils, args: [desktop_file]) 13 | endif 14 | 15 | appstream_file = i18n.merge_file( 16 | input: 'io.github.nate_xyz.Chromatic.appdata.xml.in', 17 | output: 'io.github.nate_xyz.Chromatic.appdata.xml', 18 | po_dir: '../po', 19 | install: true, 20 | install_dir: join_paths(get_option('datadir'), 'appdata') 21 | ) 22 | 23 | appstream_util = find_program('appstream-util', required: false) 24 | if appstream_util.found() 25 | test('Validate appstream file', appstream_util, args: ['validate', appstream_file]) 26 | endif 27 | 28 | install_data('io.github.nate_xyz.Chromatic.gschema.xml', 29 | install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas') 30 | ) 31 | 32 | # Compile schemas locally, so we can run uninstalled under a devenv 33 | gnome.compile_schemas() 34 | 35 | 36 | compile_schemas = find_program('glib-compile-schemas', required: false) 37 | if compile_schemas.found() 38 | test('Validate schema file', 39 | compile_schemas, 40 | args: ['--strict', '--dry-run', meson.current_source_dir()]) 41 | endif 42 | 43 | subdir('icons') 44 | -------------------------------------------------------------------------------- /src/toasts.rs: -------------------------------------------------------------------------------- 1 | use gtk::prelude::*; 2 | use gtk::gio; 3 | 4 | use super::i18n::i18n_k; 5 | use super::window::Window; 6 | 7 | pub static SUCCESS_GREEN: &str = "\"#57e389\""; 8 | pub static ERROR_RED: &str = "\"#c01c28\""; 9 | 10 | #[allow(dead_code)] 11 | pub fn add_toast_markup(msg: &str) { 12 | let app = gio::Application::default() 13 | .expect("Failed to retrieve application singleton") 14 | .downcast::() 15 | .unwrap(); 16 | 17 | let win = app 18 | .active_window() 19 | .unwrap() 20 | .downcast::() 21 | .unwrap(); 22 | 23 | let toast = adw::Toast::new(msg); 24 | toast.set_timeout(1); 25 | win.add_toast(&toast); 26 | } 27 | 28 | #[allow(dead_code)] 29 | pub fn add_success_toast(verb: &str, msg: &str) { 30 | let app = gio::Application::default() 31 | .expect("Failed to retrieve application singleton") 32 | .downcast::() 33 | .unwrap(); 34 | 35 | let win = app 36 | .active_window() 37 | .unwrap() 38 | .downcast::() 39 | .unwrap(); 40 | 41 | let toast = adw::Toast::new(format!("{} {}", SUCCESS_GREEN, verb, msg).as_str()); 42 | toast.set_timeout(1); 43 | win.add_toast(&toast); 44 | } 45 | 46 | #[allow(dead_code)] 47 | pub fn add_error_toast(msg: String) { 48 | let app = gio::Application::default() 49 | .expect("Failed to retrieve application singleton") 50 | .downcast::() 51 | .unwrap(); 52 | 53 | let win = app 54 | .active_window() 55 | .unwrap() 56 | .downcast::() 57 | .unwrap(); 58 | 59 | // Translators: Only replace "Error!". Reorder if necessary 60 | let toast = adw::Toast::new(&i18n_k("Error! {error_msg}", &[("ERROR_RED", ERROR_RED), ("error_msg", &msg)])); 61 | 62 | toast.set_timeout(1); 63 | win.add_toast(&toast); 64 | } -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name()) 2 | gnome = import('gnome') 3 | 4 | gnome.compile_resources('chromatic', 5 | 'chromatic.gresource.xml', 6 | gresource_bundle: true, 7 | install: true, 8 | install_dir: pkgdatadir, 9 | ) 10 | 11 | conf = configuration_data() 12 | conf.set_quoted('VERSION', meson.project_version()) 13 | conf.set_quoted('GETTEXT_PACKAGE', 'chromatic') 14 | conf.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir'))) 15 | conf.set_quoted('PKGDATADIR', pkgdatadir) 16 | 17 | configure_file( 18 | input: 'config.rs.in', 19 | output: 'config.rs', 20 | configuration: conf 21 | ) 22 | 23 | # Copy the config.rs output to the source directory. 24 | run_command( 25 | 'cp', 26 | join_paths(meson.project_build_root(), 'src', 'config.rs'), 27 | join_paths(meson.project_source_root(), 'src', 'config.rs'), 28 | check: true 29 | ) 30 | 31 | cargo_bin = find_program('cargo') 32 | cargo_opt = [ '--manifest-path', meson.project_source_root() / 'Cargo.toml' ] 33 | cargo_opt += [ '--target-dir', meson.project_build_root() / 'src' ] 34 | cargo_env = [ 'CARGO_HOME=' + meson.project_build_root() / 'cargo-home' ] 35 | 36 | if get_option('buildtype') == 'release' 37 | cargo_options += [ '--release' ] 38 | rust_target = 'release' 39 | else 40 | rust_target = 'debug' 41 | endif 42 | 43 | rust_sources = files( 44 | 'application.rs', 45 | 'config.rs', 46 | 'main.rs', 47 | 'window.rs', 48 | 'i18n.rs', 49 | 'recorder.rs', 50 | 'gauge.rs', 51 | 'util.rs', 52 | 'preferences_window.rs', 53 | 'toasts.rs', 54 | ) 55 | 56 | sources = [cargo_sources, rust_sources] 57 | 58 | cargo_build = custom_target( 59 | 'cargo-build', 60 | build_by_default: true, 61 | build_always_stale: true, 62 | input: sources, 63 | output: meson.project_name(), 64 | console: true, 65 | install: true, 66 | install_dir: get_option('bindir'), 67 | command: [ 68 | 'env', cargo_env, 69 | cargo_bin, 'build', 70 | cargo_opt, '&&', 'cp', 'src' / rust_target / meson.project_name(), '@OUTPUT@', 71 | ] 72 | ) 73 | -------------------------------------------------------------------------------- /data/icons/hicolor/symbolic/apps/io.github.nate_xyz.Chromatic-symbolic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /flatpak/io.github.nate_xyz.Chromatic.json: -------------------------------------------------------------------------------- 1 | { 2 | "app-id" : "io.github.nate_xyz.Chromatic", 3 | "runtime" : "org.gnome.Platform", 4 | "runtime-version" : "43", 5 | "sdk" : "org.gnome.Sdk", 6 | "sdk-extensions" : [ 7 | "org.freedesktop.Sdk.Extension.rust-stable" 8 | ], 9 | "command" : "chromatic", 10 | "finish-args" : [ 11 | "--share=ipc", 12 | "--socket=fallback-x11", 13 | "--device=all", 14 | "--socket=wayland", 15 | "--socket=pulseaudio" 16 | ], 17 | "build-options" : { 18 | "append-path" : "/usr/lib/sdk/rust-stable/bin", 19 | "build-args" : [ 20 | "--share=network" 21 | ], 22 | "env" : { 23 | "RUST_BACKTRACE" : "1", 24 | "RUST_LOG" : "trace,chromatic=debug,glib=debug" 25 | } 26 | }, 27 | "cleanup" : [ 28 | "/include", 29 | "/lib/pkgconfig", 30 | "/man", 31 | "/share/doc", 32 | "/share/gtk-doc", 33 | "/share/man", 34 | "/share/pkgconfig", 35 | "*.la", 36 | "*.a" 37 | ], 38 | "modules" : [ 39 | { 40 | "name": "portaudio", 41 | "buildsystem": "cmake-ninja", 42 | "cleanup": [ 43 | "/lib/cmake", 44 | "/share/doc" 45 | ], 46 | "sources": [ 47 | { 48 | "type": "git", 49 | "url": "https://github.com/PortAudio/portaudio.git", 50 | "tag": "v19.7.0", 51 | "commit": "147dd722548358763a8b649b3e4b41dfffbcfbb6" 52 | } 53 | ] 54 | }, 55 | { 56 | "name": "chromatic", 57 | "buildsystem": "meson", 58 | "sources": [ 59 | "cargo-sources.json", 60 | { 61 | "type" : "shell", 62 | "commands" : [ 63 | "mkdir .cargo", 64 | "cp cargo/config .cargo/" 65 | ] 66 | }, 67 | { 68 | "type": "dir", 69 | "path": "." 70 | } 71 | ] 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /data/io.github.nate_xyz.Chromatic.appdata.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.github.nate_xyz.Chromatic.desktop 4 | CC0-1.0 5 | GPL-3.0-or-later 6 | Chromatic 7 | Fine-tune your instruments 8 | nate-xyz 9 | 10 |

Fine-tune your instruments with Chromatic. Chromatic detects the frequency of audio input and converts it to a music note with the correct semitone and octave, and displays the cents error. Cents are displayed on an analog gauge to make tuning more visually intuitive.

11 |
12 | 13 | io.github.nate_xyz.Chromatic.desktop 14 | io.github.nate_xyz.Chromatic.desktop 15 | 16 | https://github.com/nate-xyz/chromatic 17 | https://github.com/nate-xyz/chromatic/issues 18 | https://ko-fi.com/natexyz 19 | 20 | 21 | 22 | 23 |
    24 |
  • Window Title UI changes
  • 25 |
  • Added French translation
  • 26 |
  • Audio Device Switch bugfix
  • 27 |
28 |
29 |
30 | 31 | 32 |
    33 |
  • Added Audio and UI settings
  • 34 |
  • Added Italian translation
  • 35 |
36 |
37 |
38 | 39 |
40 | 41 | chromatic 42 | 43 | ModernToolkit 44 | HiDpiIcon 45 | 46 | 47 | workstation 48 | 49 | 50 | 51 | 52 | Audio 53 | Music 54 | Utility 55 | 56 | 57 | 58 | 59 | https://raw.githubusercontent.com/nate-xyz/chromatic/main/data/screenshots/chromatic--0.png 60 | 61 | 62 | 63 |
64 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | /* main.rs 2 | * 3 | * Copyright 2023 nate-xyz 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * SPDX-License-Identifier: GPL-3.0-or-later 19 | */ 20 | 21 | mod application; 22 | mod config; 23 | mod window; 24 | mod i18n; 25 | mod recorder; 26 | mod gauge; 27 | mod util; 28 | mod preferences_window; 29 | mod toasts; 30 | 31 | use self::application::App; 32 | use self::window::Window; 33 | 34 | use config::{GETTEXT_PACKAGE, LOCALEDIR, PKGDATADIR}; 35 | 36 | use std::{env, process}; 37 | use log::{debug, error}; 38 | use gettextrs::{bind_textdomain_codeset, bindtextdomain, textdomain}; 39 | use gtk::{gio, prelude::*}; 40 | 41 | fn main() { 42 | pretty_env_logger::init(); 43 | 44 | // Set up gettext translations 45 | debug!("Setting up locale data"); 46 | bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR).expect("Unable to bind the text domain"); 47 | bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8") 48 | .expect("Unable to set the text domain encoding"); 49 | textdomain(GETTEXT_PACKAGE).expect("Unable to switch to the text domain"); 50 | 51 | // Load resources 52 | debug!("Loading resources"); 53 | let resources = match env::var("MESON_DEVENV") { 54 | Err(_) => gio::Resource::load(PKGDATADIR.to_owned() + "/chromatic.gresource") 55 | .expect("Unable to find chromatic.gresource"), 56 | Ok(_) => match env::current_exe() { 57 | Ok(path) => { 58 | let mut resource_path = path; 59 | resource_path.pop(); 60 | resource_path.push("chromatic.gresource"); 61 | gio::Resource::load(&resource_path) 62 | .expect("Unable to find chromatic.gresource in devenv") 63 | } 64 | Err(err) => { 65 | error!("Unable to find the current path: {}", err); 66 | process::exit(0x0100); 67 | } 68 | }, 69 | }; 70 | gio::resources_register(&resources); 71 | 72 | // Create a new GtkApplication. The application manages our main loop, 73 | // application windows, integration with the window manager/compositor, and 74 | // desktop features such as file opening and single-instance applications. 75 | let app = App::new("io.github.nate_xyz.Chromatic", &gio::ApplicationFlags::empty()); 76 | 77 | // Run the application. This function will block until the application 78 | // exits. Upon return, we have our exit code to return to the shell. (This 79 | // is the code you see when you do `echo $?` after running a command in a 80 | // terminal. 81 | std::process::exit(app.run()); 82 | } 83 | -------------------------------------------------------------------------------- /src/application.rs: -------------------------------------------------------------------------------- 1 | /* application.rs 2 | * 3 | * Copyright 2023 nate-xyz 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * SPDX-License-Identifier: GPL-3.0-or-later 19 | */ 20 | 21 | use gtk::prelude::*; 22 | use adw::subclass::prelude::*; 23 | use gtk::{gio, glib}; 24 | 25 | 26 | use super::config::VERSION; 27 | use super::Window; 28 | 29 | use super::i18n::i18n; 30 | 31 | use super::preferences_window::PreferencesWindow; 32 | 33 | 34 | mod imp { 35 | use super::*; 36 | 37 | #[derive(Debug, Default)] 38 | pub struct App {} 39 | 40 | #[glib::object_subclass] 41 | impl ObjectSubclass for App { 42 | const NAME: &'static str = "App"; 43 | type Type = super::App; 44 | type ParentType = adw::Application; 45 | } 46 | 47 | impl ObjectImpl for App { 48 | fn constructed(&self) { 49 | self.parent_constructed(); 50 | let obj = self.instance(); 51 | obj.setup_gactions(); 52 | obj.set_accels_for_action("app.quit", &["q"]); 53 | } 54 | } 55 | 56 | impl ApplicationImpl for App { 57 | // We connect to the activate callback to create a window when the application 58 | // has been launched. Additionally, this callback notifies us when the user 59 | // tries to launch a "second instance" of the application. When they try 60 | // to do that, we'll just present any existing window. 61 | fn activate(&self) { 62 | let application = self.instance(); 63 | // Get the current window or create one if necessary 64 | let window = if let Some(window) = application.active_window() { 65 | window 66 | } else { 67 | let window = Window::new(&*application); 68 | window.upcast() 69 | }; 70 | 71 | // Ask the window manager/compositor to present the window 72 | window.present(); 73 | } 74 | } 75 | 76 | impl GtkApplicationImpl for App {} 77 | impl AdwApplicationImpl for App {} 78 | } 79 | 80 | glib::wrapper! { 81 | pub struct App(ObjectSubclass) 82 | @extends gio::Application, gtk::Application, adw::Application, 83 | @implements gio::ActionGroup, gio::ActionMap; 84 | } 85 | 86 | impl App { 87 | pub fn new(application_id: &str, flags: &gio::ApplicationFlags) -> Self { 88 | glib::Object::new(&[("application-id", &application_id), ("flags", flags)]) 89 | } 90 | 91 | fn setup_gactions(&self) { 92 | let quit_action = gio::ActionEntry::builder("quit") 93 | .activate(move |app: &Self, _, _| app.quit()) 94 | .build(); 95 | let about_action = gio::ActionEntry::builder("about") 96 | .activate(move |app: &Self, _, _| app.show_about()) 97 | .build(); 98 | let preferences_action = gio::ActionEntry::builder("preferences") 99 | .activate(move |app: &Self, _, _| app.show_preferences()) 100 | .build(); 101 | 102 | self.add_action_entries([quit_action, about_action, preferences_action]).unwrap(); 103 | } 104 | 105 | fn show_about(&self) { 106 | let window = self.active_window().unwrap(); 107 | let about = adw::AboutWindow::builder() 108 | .transient_for(&window) 109 | .application_name("Chromatic") 110 | .application_icon("io.github.nate_xyz.Chromatic") 111 | .developer_name("nate-xyz") 112 | .version(VERSION) 113 | .developers(vec!["nate-xyz".into()]) 114 | .copyright("© 2023 nate-xyz") 115 | .license_type(gtk::License::Gpl30Only) 116 | .website("https://github.com/nate-xyz/chromatic") 117 | .issue_url("https://github.com/nate-xyz/chromatic/issues") 118 | .build(); 119 | 120 | // Translator credits. Replace "translator-credits" with your name/username, and optionally an email or URL. 121 | // One name per line, please do not remove previous names. 122 | about.set_translator_credits(&i18n("translator-credits")); 123 | 124 | // Translators: only replace "Inspired by " 125 | let ack: String = i18n("Inspired by LINGOT"); 126 | 127 | about.add_acknowledgement_section(Some(&ack), 128 | &["lingot website https://www.nongnu.org/lingot/", "github repo https://github.com/ibancg/lingot"]); 129 | 130 | 131 | about.present(); 132 | } 133 | 134 | fn show_preferences(&self) { 135 | let preferences = PreferencesWindow::new(); 136 | let window = self.active_window().unwrap(); 137 | preferences.set_transient_for(Some(&window)); 138 | preferences.show(); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /po/chromatic.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the chromatic package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: chromatic\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2023-04-14 13:51-0400\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: data/io.github.nate_xyz.Chromatic.desktop.in:3 21 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:6 src/ui/window.ui:28 22 | msgid "Chromatic" 23 | msgstr "" 24 | 25 | #: data/io.github.nate_xyz.Chromatic.desktop.in:4 26 | msgid "Instrument Tuner" 27 | msgstr "" 28 | 29 | #: data/io.github.nate_xyz.Chromatic.desktop.in:5 30 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:7 31 | msgid "Fine-tune your instruments" 32 | msgstr "" 33 | 34 | #. Translators: Search terms to find this application. Do not translate or localize the semicolons! The list must also end with a semicolon. 35 | #: data/io.github.nate_xyz.Chromatic.desktop.in:12 36 | msgid "instrument;tuner;chromatic;" 37 | msgstr "" 38 | 39 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:8 40 | msgid "nate-xyz" 41 | msgstr "" 42 | 43 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:10 44 | msgid "" 45 | "Fine-tune your instruments with Chromatic. Chromatic detects the frequency " 46 | "of audio input and converts it to a music note with the correct semitone and " 47 | "octave, and displays the cents error. Cents are displayed on an analog gauge " 48 | "to make tuning more visually intuitive." 49 | msgstr "" 50 | 51 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:24 52 | msgid "Window Title UI changes" 53 | msgstr "" 54 | 55 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:25 56 | msgid "Added French translation" 57 | msgstr "" 58 | 59 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:26 60 | msgid "Audio Device Switch bugfix" 61 | msgstr "" 62 | 63 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:33 64 | msgid "Added Audio and UI settings" 65 | msgstr "" 66 | 67 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:34 68 | msgid "Added Italian translation" 69 | msgstr "" 70 | 71 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:7 72 | msgid "Manually select audio device" 73 | msgstr "" 74 | 75 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:16 76 | msgid "Show gauge widget" 77 | msgstr "" 78 | 79 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:22 80 | msgid "Show window title" 81 | msgstr "" 82 | 83 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:28 84 | msgid "Stream buffer size" 85 | msgstr "" 86 | 87 | #: src/window.rs:244 88 | msgid "Unable to initialize audio backend." 89 | msgstr "" 90 | 91 | #: src/recorder.rs:155 src/recorder.rs:176 src/recorder.rs:206 92 | #: src/recorder.rs:241 93 | msgid "Unable to retrieve device ({device_name})" 94 | msgstr "" 95 | 96 | #. Translators: Only replace "Error!". Reorder if necessary 97 | #: src/toasts.rs:60 98 | msgid "Error! {error_msg}" 99 | msgstr "" 100 | 101 | #: src/ui/window.ui:187 102 | msgid "_Preferences" 103 | msgstr "" 104 | 105 | #: src/ui/window.ui:195 106 | msgid "_About Chromatic" 107 | msgstr "" 108 | 109 | #: src/ui/help-overlay.ui:11 110 | msgctxt "shortcut window" 111 | msgid "General" 112 | msgstr "" 113 | 114 | #: src/ui/help-overlay.ui:14 115 | msgctxt "shortcut window" 116 | msgid "Show Shortcuts" 117 | msgstr "" 118 | 119 | #: src/ui/help-overlay.ui:20 120 | msgctxt "shortcut window" 121 | msgid "Quit" 122 | msgstr "" 123 | 124 | #: src/ui/preferences_window.ui:18 125 | msgid "Audio" 126 | msgstr "" 127 | 128 | #: src/ui/preferences_window.ui:22 129 | msgid "Manually Select" 130 | msgstr "" 131 | 132 | #: src/ui/preferences_window.ui:23 133 | msgid "" 134 | "Chromatic will try to automatically detect running input devices if disabled." 135 | msgstr "" 136 | 137 | #: src/ui/preferences_window.ui:40 138 | msgid "Device" 139 | msgstr "" 140 | 141 | #: src/ui/preferences_window.ui:47 142 | msgid "Buffer Size" 143 | msgstr "" 144 | 145 | #: src/ui/preferences_window.ui:48 146 | msgid "Stream buffer size, changes frequency update interval." 147 | msgstr "" 148 | 149 | #: src/ui/preferences_window.ui:68 150 | msgid "UI" 151 | msgstr "" 152 | 153 | #: src/ui/preferences_window.ui:72 154 | msgid "Show Window Title" 155 | msgstr "" 156 | 157 | #: src/ui/preferences_window.ui:88 158 | msgid "Show Gauge" 159 | msgstr "" 160 | 161 | #: src/ui/preferences_window.ui:104 162 | msgid "Gauge Pin Hover Time" 163 | msgstr "" 164 | 165 | #: src/ui/preferences_window.ui:105 166 | msgid "" 167 | "Duration (in seconds) the gauge pin will hover before returning to baseline." 168 | msgstr "" 169 | 170 | #: src/ui/preferences_window.ui:122 171 | msgid "Label Update Hang Time" 172 | msgstr "" 173 | 174 | #: src/ui/preferences_window.ui:123 175 | msgid "Duration (in seconds) the labels will hang before clearing display." 176 | msgstr "" 177 | 178 | #: src/ui/preferences_window.ui:139 179 | msgid "Gauge Pin Resting Position" 180 | msgstr "" 181 | 182 | #: src/ui/preferences_window.ui:140 183 | msgid "Baseline position the pin returns to after hover time." 184 | msgstr "" 185 | -------------------------------------------------------------------------------- /po/fr.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the chromatic package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: chromatic\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2023-04-14 13:51-0400\n" 11 | "PO-Revision-Date: 2023-03-07 14:34+0100\n" 12 | "Last-Translator: Irénée Thirion \n" 13 | "Language-Team: \n" 14 | "Language: fr\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 19 | "X-Generator: Poedit 3.2.2\n" 20 | 21 | #: data/io.github.nate_xyz.Chromatic.desktop.in:3 22 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:6 src/ui/window.ui:28 23 | msgid "Chromatic" 24 | msgstr "Chromatique" 25 | 26 | #: data/io.github.nate_xyz.Chromatic.desktop.in:4 27 | msgid "Instrument Tuner" 28 | msgstr "Accordeur d’instruments" 29 | 30 | #: data/io.github.nate_xyz.Chromatic.desktop.in:5 31 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:7 32 | msgid "Fine-tune your instruments" 33 | msgstr "Ajustez et accordez vos instruments" 34 | 35 | #. Translators: Search terms to find this application. Do not translate or localize the semicolons! The list must also end with a semicolon. 36 | #: data/io.github.nate_xyz.Chromatic.desktop.in:12 37 | msgid "instrument;tuner;chromatic;" 38 | msgstr "instrument;accordeur;chromatique;" 39 | 40 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:8 41 | msgid "nate-xyz" 42 | msgstr "nate-xyz" 43 | 44 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:10 45 | msgid "" 46 | "Fine-tune your instruments with Chromatic. Chromatic detects the frequency " 47 | "of audio input and converts it to a music note with the correct semitone and " 48 | "octave, and displays the cents error. Cents are displayed on an analog gauge " 49 | "to make tuning more visually intuitive." 50 | msgstr "" 51 | 52 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:24 53 | msgid "Window Title UI changes" 54 | msgstr "" 55 | 56 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:25 57 | msgid "Added French translation" 58 | msgstr "" 59 | 60 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:26 61 | #, fuzzy 62 | msgid "Audio Device Switch bugfix" 63 | msgstr "Périphérique audio" 64 | 65 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:33 66 | msgid "Added Audio and UI settings" 67 | msgstr "" 68 | 69 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:34 70 | msgid "Added Italian translation" 71 | msgstr "" 72 | 73 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:7 74 | msgid "Manually select audio device" 75 | msgstr "Sélectionner manuellement le périphérique audio" 76 | 77 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:16 78 | msgid "Show gauge widget" 79 | msgstr "Afficher le widget de la jauge" 80 | 81 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:22 82 | msgid "Show window title" 83 | msgstr "" 84 | 85 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:28 86 | msgid "Stream buffer size" 87 | msgstr "" 88 | 89 | #: src/window.rs:244 90 | msgid "Unable to initialize audio backend." 91 | msgstr "" 92 | 93 | #: src/recorder.rs:155 src/recorder.rs:176 src/recorder.rs:206 94 | #: src/recorder.rs:241 95 | msgid "Unable to retrieve device ({device_name})" 96 | msgstr "" 97 | 98 | #. Translators: Only replace "Error!". Reorder if necessary 99 | #: src/toasts.rs:60 100 | msgid "Error! {error_msg}" 101 | msgstr "" 102 | 103 | #: src/ui/window.ui:187 104 | msgid "_Preferences" 105 | msgstr "_Préférences" 106 | 107 | #: src/ui/window.ui:195 108 | msgid "_About Chromatic" 109 | msgstr "À _propos de Chromatique" 110 | 111 | #: src/ui/help-overlay.ui:11 112 | msgctxt "shortcut window" 113 | msgid "General" 114 | msgstr "Général" 115 | 116 | #: src/ui/help-overlay.ui:14 117 | msgctxt "shortcut window" 118 | msgid "Show Shortcuts" 119 | msgstr "Afficher les raccourcis" 120 | 121 | #: src/ui/help-overlay.ui:20 122 | msgctxt "shortcut window" 123 | msgid "Quit" 124 | msgstr "Quitter" 125 | 126 | #: src/ui/preferences_window.ui:18 127 | msgid "Audio" 128 | msgstr "" 129 | 130 | #: src/ui/preferences_window.ui:22 131 | msgid "Manually Select" 132 | msgstr "Sélectionner manuellement" 133 | 134 | #: src/ui/preferences_window.ui:23 135 | msgid "" 136 | "Chromatic will try to automatically detect running input devices if disabled." 137 | msgstr "" 138 | "Chromatique essaiera de détecter automatiquement les périphériques d’entrée " 139 | "si l’option est désactivée." 140 | 141 | #: src/ui/preferences_window.ui:40 142 | msgid "Device" 143 | msgstr "Périphérique" 144 | 145 | #: src/ui/preferences_window.ui:47 146 | msgid "Buffer Size" 147 | msgstr "" 148 | 149 | #: src/ui/preferences_window.ui:48 150 | msgid "Stream buffer size, changes frequency update interval." 151 | msgstr "" 152 | 153 | #: src/ui/preferences_window.ui:68 154 | msgid "UI" 155 | msgstr "Interface" 156 | 157 | #: src/ui/preferences_window.ui:72 158 | msgid "Show Window Title" 159 | msgstr "" 160 | 161 | #: src/ui/preferences_window.ui:88 162 | msgid "Show Gauge" 163 | msgstr "Afficher la jauge" 164 | 165 | #: src/ui/preferences_window.ui:104 166 | msgid "Gauge Pin Hover Time" 167 | msgstr "" 168 | 169 | #: src/ui/preferences_window.ui:105 170 | msgid "" 171 | "Duration (in seconds) the gauge pin will hover before returning to baseline." 172 | msgstr "" 173 | 174 | #: src/ui/preferences_window.ui:122 175 | msgid "Label Update Hang Time" 176 | msgstr "" 177 | 178 | #: src/ui/preferences_window.ui:123 179 | msgid "Duration (in seconds) the labels will hang before clearing display." 180 | msgstr "" 181 | 182 | #: src/ui/preferences_window.ui:139 183 | msgid "Gauge Pin Resting Position" 184 | msgstr "" 185 | 186 | #: src/ui/preferences_window.ui:140 187 | msgid "Baseline position the pin returns to after hover time." 188 | msgstr "" 189 | 190 | #~ msgid "" 191 | #~ "Fine-tune your instruments with Chromatic. Displays exact frequency and " 192 | #~ "equivalent note with octave, as well as the cents of error." 193 | #~ msgstr "" 194 | #~ "Accordez vos instruments avec Chromatique. L’application affiche la " 195 | #~ "fréquence exacte et la note équivalente avec l’octave, ainsi qu’une marge " 196 | #~ "d’erreur au centième." 197 | -------------------------------------------------------------------------------- /po/de.po: -------------------------------------------------------------------------------- 1 | # ITALIAN TRANSLATION. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the chromatic package. 4 | # Fabian Geißer , 2023. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: chromatic\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2023-04-14 13:51-0400\n" 11 | "PO-Revision-Date: 2023-03-11 10:51+0100\n" 12 | "Last-Translator: Fabian Geißer \n" 13 | "Language-Team: \n" 14 | "Language: de\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n > 1)\n" 19 | "X-Generator: Gtranslator 42.0\n" 20 | 21 | #: data/io.github.nate_xyz.Chromatic.desktop.in:3 22 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:6 src/ui/window.ui:28 23 | msgid "Chromatic" 24 | msgstr "Chromatic" 25 | 26 | #: data/io.github.nate_xyz.Chromatic.desktop.in:4 27 | msgid "Instrument Tuner" 28 | msgstr "Stimmgerät" 29 | 30 | #: data/io.github.nate_xyz.Chromatic.desktop.in:5 31 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:7 32 | msgid "Fine-tune your instruments" 33 | msgstr "Stimme deine Instrumente" 34 | 35 | #. Translators: Search terms to find this application. Do not translate or localize the semicolons! The list must also end with a semicolon. 36 | #: data/io.github.nate_xyz.Chromatic.desktop.in:12 37 | msgid "instrument;tuner;chromatic;" 38 | msgstr "Instrument; stimmen; Stimmgerät; chromatisch;" 39 | 40 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:8 41 | msgid "nate-xyz" 42 | msgstr "nate-xyz" 43 | 44 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:10 45 | msgid "" 46 | "Fine-tune your instruments with Chromatic. Chromatic detects the frequency " 47 | "of audio input and converts it to a music note with the correct semitone and " 48 | "octave, and displays the cents error. Cents are displayed on an analog gauge " 49 | "to make tuning more visually intuitive." 50 | msgstr "" 51 | "Stimme deine Instrumente mit Chromatic. Chromatic erkennt die Frequenz auf " 52 | "dem Audio-Eingang, konvertiert sie in die passende Musiknote und Oktave und " 53 | "zeigt die Abweichungen in Cent an. Die Abweichung wird zusätzlich auf einer " 54 | "analogen Anzeige angezeigt, um das Stimmen intuitiver zu machen." 55 | 56 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:24 57 | msgid "Window Title UI changes" 58 | msgstr "" 59 | 60 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:25 61 | msgid "Added French translation" 62 | msgstr "" 63 | 64 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:26 65 | #, fuzzy 66 | msgid "Audio Device Switch bugfix" 67 | msgstr "Dispositivo audio" 68 | 69 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:33 70 | msgid "Added Audio and UI settings" 71 | msgstr "" 72 | 73 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:34 74 | msgid "Added Italian translation" 75 | msgstr "" 76 | 77 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:7 78 | msgid "Manually select audio device" 79 | msgstr "" 80 | 81 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:16 82 | msgid "Show gauge widget" 83 | msgstr "Zeige analogen Zeiger" 84 | 85 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:22 86 | msgid "Show window title" 87 | msgstr "" 88 | 89 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:28 90 | msgid "Stream buffer size" 91 | msgstr "Größe des Eingabebuffers" 92 | 93 | #: src/window.rs:244 94 | msgid "Unable to initialize audio backend." 95 | msgstr "" 96 | 97 | #: src/recorder.rs:155 src/recorder.rs:176 src/recorder.rs:206 98 | #: src/recorder.rs:241 99 | msgid "Unable to retrieve device ({device_name})" 100 | msgstr "" 101 | 102 | #. Translators: Only replace "Error!". Reorder if necessary 103 | #: src/toasts.rs:60 104 | msgid "Error! {error_msg}" 105 | msgstr "" 106 | 107 | #: src/ui/window.ui:187 108 | msgid "_Preferences" 109 | msgstr "_Einstellungen" 110 | 111 | #: src/ui/window.ui:195 112 | msgid "_About Chromatic" 113 | msgstr "_über Chromatic" 114 | 115 | #: src/ui/help-overlay.ui:11 116 | msgctxt "shortcut window" 117 | msgid "General" 118 | msgstr "Allgemein" 119 | 120 | #: src/ui/help-overlay.ui:14 121 | msgctxt "shortcut window" 122 | msgid "Show Shortcuts" 123 | msgstr "Tastenkombinationen" 124 | 125 | #: src/ui/help-overlay.ui:20 126 | msgctxt "shortcut window" 127 | msgid "Quit" 128 | msgstr "Beenden" 129 | 130 | #: src/ui/preferences_window.ui:18 131 | msgid "Audio" 132 | msgstr "Audio" 133 | 134 | #: src/ui/preferences_window.ui:22 135 | msgid "Manually Select" 136 | msgstr "Wähle manuell" 137 | 138 | #: src/ui/preferences_window.ui:23 139 | msgid "" 140 | "Chromatic will try to automatically detect running input devices if disabled." 141 | msgstr "" 142 | "Wenn deaktiviert, wird Chromatic versuchen automatisch passende " 143 | "Eingabegeräte auszuwählen." 144 | 145 | #: src/ui/preferences_window.ui:40 146 | msgid "Device" 147 | msgstr "Gerät" 148 | 149 | #: src/ui/preferences_window.ui:47 150 | msgid "Buffer Size" 151 | msgstr "Größe des Zwischenspeichers" 152 | 153 | #: src/ui/preferences_window.ui:48 154 | msgid "Stream buffer size, changes frequency update interval." 155 | msgstr "" 156 | "Größe des Zwischenspeichers. Ändert die Häufigkeit der " 157 | "Frequenzaktualisierungen" 158 | 159 | #: src/ui/preferences_window.ui:68 160 | msgid "UI" 161 | msgstr "Benutzeroberfläche" 162 | 163 | #: src/ui/preferences_window.ui:72 164 | msgid "Show Window Title" 165 | msgstr "" 166 | 167 | #: src/ui/preferences_window.ui:88 168 | msgid "Show Gauge" 169 | msgstr "Zeige analogen Zeiger" 170 | 171 | #: src/ui/preferences_window.ui:104 172 | msgid "Gauge Pin Hover Time" 173 | msgstr "Verweildauer des Zeigers" 174 | 175 | #: src/ui/preferences_window.ui:105 176 | msgid "" 177 | "Duration (in seconds) the gauge pin will hover before returning to baseline." 178 | msgstr "" 179 | "Zeitdauer (in Sekunden), die der Zeiger die Position hält, bis er zur " 180 | "Grundlinie zurückkehrt" 181 | 182 | #: src/ui/preferences_window.ui:122 183 | msgid "Label Update Hang Time" 184 | msgstr "Verweildauer der Tonanzeige" 185 | 186 | #: src/ui/preferences_window.ui:123 187 | msgid "Duration (in seconds) the labels will hang before clearing display." 188 | msgstr "Zeitdauer (in Sekunden), für die Ton und Frequenz angezeigt werden." 189 | 190 | #: src/ui/preferences_window.ui:139 191 | msgid "Gauge Pin Resting Position" 192 | msgstr "Ruheposition des Zeigers" 193 | 194 | #: src/ui/preferences_window.ui:140 195 | msgid "Baseline position the pin returns to after hover time." 196 | msgstr "Position, an die der Zeiger zurückkehrt, wenn keine Eingabe erfolgt." 197 | 198 | #~ msgid "" 199 | #~ "Fine-tune your instruments with Chromatic. Displays exact frequency and " 200 | #~ "equivalent note with octave, as well as the cents of error." 201 | #~ msgstr "" 202 | #~ "Accorda i tuoi strumenti con Chromatic. Visualizza la frequenza esatta e " 203 | #~ "la nota equivalente con l'ottava, nonché i centesimi di errore." 204 | -------------------------------------------------------------------------------- /src/i18n.rs: -------------------------------------------------------------------------------- 1 | // i18n.rs 2 | // 3 | // Copyright 2020 Christopher Davis 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // SPDX-License-Identifier: GPL-3.0-or-later 19 | 20 | use gettextrs::{gettext, ngettext, npgettext, pgettext}; 21 | use regex::{Captures, Regex}; 22 | 23 | #[allow(dead_code)] 24 | fn freplace(input: String, args: &[&str]) -> String { 25 | let mut parts = input.split("{}"); 26 | let mut output = parts.next().unwrap_or_default().to_string(); 27 | for (p, a) in parts.zip(args.iter()) { 28 | output += &(a.to_string() + p); 29 | } 30 | output 31 | } 32 | 33 | #[allow(dead_code)] 34 | fn kreplace(input: String, kwargs: &[(&str, &str)]) -> String { 35 | let mut s = input; 36 | for (k, v) in kwargs { 37 | if let Ok(re) = Regex::new(&format!("\\{{{}\\}}", k)) { 38 | s = re 39 | .replace_all(&s, |_: &Captures<'_>| v.to_string()) 40 | .to_string(); 41 | } 42 | } 43 | 44 | s 45 | } 46 | 47 | // Simple translations functions 48 | 49 | #[allow(dead_code)] 50 | pub fn i18n(format: &str) -> String { 51 | gettext(format) 52 | } 53 | 54 | #[allow(dead_code)] 55 | pub fn i18n_f(format: &str, args: &[&str]) -> String { 56 | let s = gettext(format); 57 | freplace(s, args) 58 | } 59 | 60 | #[allow(dead_code)] 61 | pub fn i18n_k(format: &str, kwargs: &[(&str, &str)]) -> String { 62 | let s = gettext(format); 63 | kreplace(s, kwargs) 64 | } 65 | 66 | // Singular and plural translations functions 67 | 68 | #[allow(dead_code)] 69 | pub fn ni18n(single: &str, multiple: &str, number: u32) -> String { 70 | ngettext(single, multiple, number) 71 | } 72 | 73 | #[allow(dead_code)] 74 | pub fn ni18n_f(single: &str, multiple: &str, number: u32, args: &[&str]) -> String { 75 | let s = ngettext(single, multiple, number); 76 | freplace(s, args) 77 | } 78 | 79 | #[allow(dead_code)] 80 | pub fn ni18n_k(single: &str, multiple: &str, number: u32, kwargs: &[(&str, &str)]) -> String { 81 | let s = ngettext(single, multiple, number); 82 | kreplace(s, kwargs) 83 | } 84 | 85 | // Translations with context functions 86 | 87 | #[allow(dead_code)] 88 | pub fn pi18n(ctx: &str, format: &str) -> String { 89 | pgettext(ctx, format) 90 | } 91 | 92 | #[allow(dead_code)] 93 | pub fn pi18n_f(ctx: &str, format: &str, args: &[&str]) -> String { 94 | let s = pgettext(ctx, format); 95 | freplace(s, args) 96 | } 97 | 98 | #[allow(dead_code)] 99 | pub fn pi18n_k(ctx: &str, format: &str, kwargs: &[(&str, &str)]) -> String { 100 | let s = pgettext(ctx, format); 101 | kreplace(s, kwargs) 102 | } 103 | 104 | // Singular and plural with context 105 | 106 | #[allow(dead_code)] 107 | pub fn pni18n(ctx: &str, single: &str, multiple: &str, number: u32) -> String { 108 | npgettext(ctx, single, multiple, number) 109 | } 110 | 111 | #[allow(dead_code)] 112 | pub fn pni18n_f(ctx: &str, single: &str, multiple: &str, number: u32, args: &[&str]) -> String { 113 | let s = npgettext(ctx, single, multiple, number); 114 | freplace(s, args) 115 | } 116 | 117 | #[allow(dead_code)] 118 | pub fn pni18n_k( 119 | ctx: &str, 120 | single: &str, 121 | multiple: &str, 122 | number: u32, 123 | kwargs: &[(&str, &str)], 124 | ) -> String { 125 | let s = npgettext(ctx, single, multiple, number); 126 | kreplace(s, kwargs) 127 | } 128 | 129 | #[cfg(test)] 130 | mod tests { 131 | use super::*; 132 | #[test] 133 | fn test_i18n() { 134 | let out = i18n("translate1"); 135 | assert_eq!(out, "translate1"); 136 | 137 | let out = ni18n("translate1", "translate multi", 1); 138 | assert_eq!(out, "translate1"); 139 | let out = ni18n("translate1", "translate multi", 2); 140 | assert_eq!(out, "translate multi"); 141 | } 142 | 143 | #[test] 144 | fn test_i18n_f() { 145 | let out = i18n_f("{} param", &["one"]); 146 | assert_eq!(out, "one param"); 147 | 148 | let out = i18n_f("middle {} param", &["one"]); 149 | assert_eq!(out, "middle one param"); 150 | 151 | let out = i18n_f("end {}", &["one"]); 152 | assert_eq!(out, "end one"); 153 | 154 | let out = i18n_f("multiple {} and {}", &["one", "two"]); 155 | assert_eq!(out, "multiple one and two"); 156 | 157 | let out = ni18n_f("singular {} and {}", "plural {} and {}", 2, &["one", "two"]); 158 | assert_eq!(out, "plural one and two"); 159 | let out = ni18n_f("singular {} and {}", "plural {} and {}", 1, &["one", "two"]); 160 | assert_eq!(out, "singular one and two"); 161 | } 162 | 163 | #[test] 164 | fn test_i18n_k() { 165 | let out = i18n_k("{one} param", &[("one", "one")]); 166 | assert_eq!(out, "one param"); 167 | 168 | let out = i18n_k("middle {one} param", &[("one", "one")]); 169 | assert_eq!(out, "middle one param"); 170 | 171 | let out = i18n_k("end {one}", &[("one", "one")]); 172 | assert_eq!(out, "end one"); 173 | 174 | let out = i18n_k("multiple {one} and {two}", &[("one", "1"), ("two", "two")]); 175 | assert_eq!(out, "multiple 1 and two"); 176 | 177 | let out = i18n_k("multiple {two} and {one}", &[("one", "1"), ("two", "two")]); 178 | assert_eq!(out, "multiple two and 1"); 179 | 180 | let out = i18n_k("multiple {one} and {one}", &[("one", "1"), ("two", "two")]); 181 | assert_eq!(out, "multiple 1 and 1"); 182 | 183 | let out = ni18n_k( 184 | "singular {one} and {two}", 185 | "plural {one} and {two}", 186 | 1, 187 | &[("one", "1"), ("two", "two")], 188 | ); 189 | assert_eq!(out, "singular 1 and two"); 190 | let out = ni18n_k( 191 | "singular {one} and {two}", 192 | "plural {one} and {two}", 193 | 2, 194 | &[("one", "1"), ("two", "two")], 195 | ); 196 | assert_eq!(out, "plural 1 and two"); 197 | } 198 | 199 | #[test] 200 | fn test_pi18n() { 201 | let out = pi18n("This is the context", "translate1"); 202 | assert_eq!(out, "translate1"); 203 | 204 | let out = pni18n("context", "translate1", "translate multi", 1); 205 | assert_eq!(out, "translate1"); 206 | let out = pni18n("The context string", "translate1", "translate multi", 2); 207 | assert_eq!(out, "translate multi"); 208 | 209 | let out = pi18n_f("Context for translation", "{} param", &["one"]); 210 | assert_eq!(out, "one param"); 211 | 212 | let out = pi18n_k("context", "{one} param", &[("one", "one")]); 213 | assert_eq!(out, "one param"); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /po/it.po: -------------------------------------------------------------------------------- 1 | # ITALIAN TRANSLATION. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the chromatic package. 4 | # ALBANO BATTISTELLA , 2023. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: chromatic\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2023-04-14 13:51-0400\n" 11 | "PO-Revision-Date: 2023-03-07 19:57+0100\n" 12 | "Last-Translator: Albano Battistella \n" 13 | "Language-Team: Italian\n" 14 | "Language: it_IT\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | "X-Generator: Poedit 3.2.2\n" 20 | 21 | #: data/io.github.nate_xyz.Chromatic.desktop.in:3 22 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:6 src/ui/window.ui:28 23 | msgid "Chromatic" 24 | msgstr "Chromatic" 25 | 26 | #: data/io.github.nate_xyz.Chromatic.desktop.in:4 27 | msgid "Instrument Tuner" 28 | msgstr "Accordatore di strumenti" 29 | 30 | #: data/io.github.nate_xyz.Chromatic.desktop.in:5 31 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:7 32 | msgid "Fine-tune your instruments" 33 | msgstr "Accorda i tuoi strumenti" 34 | 35 | #. Translators: Search terms to find this application. Do not translate or localize the semicolons! The list must also end with a semicolon. 36 | #: data/io.github.nate_xyz.Chromatic.desktop.in:12 37 | msgid "instrument;tuner;chromatic;" 38 | msgstr "strumento; accordatore; cromatico;" 39 | 40 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:8 41 | msgid "nate-xyz" 42 | msgstr "nate-xyz" 43 | 44 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:10 45 | msgid "" 46 | "Fine-tune your instruments with Chromatic. Chromatic detects the frequency " 47 | "of audio input and converts it to a music note with the correct semitone and " 48 | "octave, and displays the cents error. Cents are displayed on an analog gauge " 49 | "to make tuning more visually intuitive." 50 | msgstr "" 51 | "Perfeziona i tuoi strumenti con Chromatic. Chromatic rileva la frequenza " 52 | "dell'ingresso audio e lo converte in una nota musicale con il semitono " 53 | "corretto e ottava e visualizza l'errore dei centesimi. I centesimi vengono " 54 | "visualizzati su un indicatore analogico per rendere l'accordatura " 55 | "visivamente più intuitiva." 56 | 57 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:24 58 | msgid "Window Title UI changes" 59 | msgstr "" 60 | 61 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:25 62 | #, fuzzy 63 | msgid "Added French translation" 64 | msgstr "Aggiunta traduzione italiana" 65 | 66 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:26 67 | #, fuzzy 68 | msgid "Audio Device Switch bugfix" 69 | msgstr "Dispositivo audio" 70 | 71 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:33 72 | msgid "Added Audio and UI settings" 73 | msgstr "Aggiunte impostazioni audio e interfaccia utente" 74 | 75 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:34 76 | msgid "Added Italian translation" 77 | msgstr "Aggiunta traduzione italiana" 78 | 79 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:7 80 | msgid "Manually select audio device" 81 | msgstr "Seleziona manualmente il dispositivo audio" 82 | 83 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:16 84 | msgid "Show gauge widget" 85 | msgstr "Mostra indicatore widget" 86 | 87 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:22 88 | msgid "Show window title" 89 | msgstr "" 90 | 91 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:28 92 | msgid "Stream buffer size" 93 | msgstr "Dimensione del flusso di buffer" 94 | 95 | #: src/window.rs:244 96 | msgid "Unable to initialize audio backend." 97 | msgstr "" 98 | 99 | #: src/recorder.rs:155 src/recorder.rs:176 src/recorder.rs:206 100 | #: src/recorder.rs:241 101 | msgid "Unable to retrieve device ({device_name})" 102 | msgstr "" 103 | 104 | #. Translators: Only replace "Error!". Reorder if necessary 105 | #: src/toasts.rs:60 106 | msgid "Error! {error_msg}" 107 | msgstr "" 108 | 109 | #: src/ui/window.ui:187 110 | msgid "_Preferences" 111 | msgstr "_Preferenze" 112 | 113 | #: src/ui/window.ui:195 114 | msgid "_About Chromatic" 115 | msgstr "_Informazioni su Chromatic" 116 | 117 | #: src/ui/help-overlay.ui:11 118 | msgctxt "shortcut window" 119 | msgid "General" 120 | msgstr "Generale" 121 | 122 | #: src/ui/help-overlay.ui:14 123 | msgctxt "shortcut window" 124 | msgid "Show Shortcuts" 125 | msgstr "Mostra scorciatoie" 126 | 127 | #: src/ui/help-overlay.ui:20 128 | msgctxt "shortcut window" 129 | msgid "Quit" 130 | msgstr "Esci" 131 | 132 | #: src/ui/preferences_window.ui:18 133 | msgid "Audio" 134 | msgstr "Audio" 135 | 136 | #: src/ui/preferences_window.ui:22 137 | msgid "Manually Select" 138 | msgstr "Selezione manuale" 139 | 140 | #: src/ui/preferences_window.ui:23 141 | msgid "" 142 | "Chromatic will try to automatically detect running input devices if disabled." 143 | msgstr "" 144 | "Chromatic proverà a rilevare automaticamente i dispositivi di input in " 145 | "esecuzione se disabilitato." 146 | 147 | #: src/ui/preferences_window.ui:40 148 | msgid "Device" 149 | msgstr "Dispositivo" 150 | 151 | #: src/ui/preferences_window.ui:47 152 | msgid "Buffer Size" 153 | msgstr "Dimensione buffer" 154 | 155 | #: src/ui/preferences_window.ui:48 156 | msgid "Stream buffer size, changes frequency update interval." 157 | msgstr "" 158 | "Dimensione del flusso di buffer, cambia l'intervallo di aggiornamento della " 159 | "frequenza." 160 | 161 | #: src/ui/preferences_window.ui:68 162 | msgid "UI" 163 | msgstr "Interfaccia utente" 164 | 165 | #: src/ui/preferences_window.ui:72 166 | msgid "Show Window Title" 167 | msgstr "" 168 | 169 | #: src/ui/preferences_window.ui:88 170 | msgid "Show Gauge" 171 | msgstr "Mostra indicatore" 172 | 173 | #: src/ui/preferences_window.ui:104 174 | msgid "Gauge Pin Hover Time" 175 | msgstr "Tempo di passaggio del mouse sul perno del misuratore" 176 | 177 | #: src/ui/preferences_window.ui:105 178 | msgid "" 179 | "Duration (in seconds) the gauge pin will hover before returning to baseline." 180 | msgstr "" 181 | "Durata (in secondi) in cui il perno del misuratore rimarrà sospeso prima di " 182 | "tornare alla linea di base." 183 | 184 | #: src/ui/preferences_window.ui:122 185 | msgid "Label Update Hang Time" 186 | msgstr "Tempo di sospensione dell'aggiornamento dell'etichetta" 187 | 188 | #: src/ui/preferences_window.ui:123 189 | msgid "Duration (in seconds) the labels will hang before clearing display." 190 | msgstr "" 191 | "Durata (in secondi) in cui le etichette rimarranno bloccate prima di " 192 | "cancellare la visualizzazione." 193 | 194 | #: src/ui/preferences_window.ui:139 195 | msgid "Gauge Pin Resting Position" 196 | msgstr "Posizione di riposo del perno del misuratore" 197 | 198 | #: src/ui/preferences_window.ui:140 199 | msgid "Baseline position the pin returns to after hover time." 200 | msgstr "" 201 | "Posizione di base in cui ritorna il perno dopo il tempo di passaggio del " 202 | "mouse." 203 | 204 | #~ msgid "" 205 | #~ "Fine-tune your instruments with Chromatic. Displays exact frequency and " 206 | #~ "equivalent note with octave, as well as the cents of error." 207 | #~ msgstr "" 208 | #~ "Accorda i tuoi strumenti con Chromatic. Visualizza la frequenza esatta e " 209 | #~ "la nota equivalente con l'ottava, nonché i centesimi di errore." 210 | -------------------------------------------------------------------------------- /po/pt_br.po: -------------------------------------------------------------------------------- 1 | # BRAZILIAN PORTUGUESE TRANSLATION. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the chromatic package. 4 | # FIRST AUTHOR , 2023. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: chromatic\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2023-04-14 13:51-0400\n" 11 | "PO-Revision-Date: 2023-08-01 12:04+0100\n" 12 | "Last-Translator: Matheus Tessarini Ricci \n" 13 | "Language-Team: \n" 14 | "Language: pt_br\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 19 | "X-Generator: GNOME Builder 43.6\n" 20 | 21 | #: data/io.github.nate_xyz.Chromatic.desktop.in:3 22 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:6 src/ui/window.ui:28 23 | msgid "Chromatic" 24 | msgstr "Chromatic" 25 | 26 | #: data/io.github.nate_xyz.Chromatic.desktop.in:4 27 | msgid "Instrument Tuner" 28 | msgstr "Afinador de instrumentos" 29 | 30 | #: data/io.github.nate_xyz.Chromatic.desktop.in:5 31 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:7 32 | msgid "Fine-tune your instruments" 33 | msgstr "Afine seus instrumentos" 34 | 35 | #. Translators: Search terms to find this application. Do not translate or localize the semicolons! The list must also end with a semicolon. 36 | #: data/io.github.nate_xyz.Chromatic.desktop.in:12 37 | msgid "instrument;tuner;chromatic;" 38 | msgstr "instrumento;afinador;chromatic;" 39 | 40 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:8 41 | msgid "nate-xyz" 42 | msgstr "nate-xyz" 43 | 44 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:10 45 | msgid "" 46 | "Fine-tune your instruments with Chromatic. Chromatic detects the frequency " 47 | "of audio input and converts it to a music note with the correct semitone and " 48 | "octave, and displays the cents error. Cents are displayed on an analog gauge " 49 | "to make tuning more visually intuitive." 50 | msgstr "Afine seus instrumentos com o Chromatic. Ele detecta as frequências " 51 | "de entrada de áudio e as converte em notas musicais com o semitom correto e " 52 | "oitavas, mostrando os erros de centésimas. Essas são mostradas de forma " 53 | "analógica, afim de deixar a afinação mais intuitiva. " 54 | 55 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:24 56 | msgid "Window Title UI changes" 57 | msgstr "Mudanças no título da janela da interface" 58 | 59 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:25 60 | msgid "Added French translation" 61 | msgstr "Adicionada tradução para o francês" 62 | 63 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:26 64 | #, fuzzy 65 | msgid "Audio Device Switch bugfix" 66 | msgstr "Correção de bugs na mudança de dispositivos" 67 | 68 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:33 69 | msgid "Added Audio and UI settings" 70 | msgstr "Adicionada configurações da interface e áudio" 71 | 72 | #: data/io.github.nate_xyz.Chromatic.appdata.xml.in:34 73 | msgid "Added Italian translation" 74 | msgstr "Adicionada tradução para o italiano" 75 | 76 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:7 77 | msgid "Manually select audio device" 78 | msgstr "Selecionar manualmente o dispositivo de áudio" 79 | 80 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:16 81 | msgid "Show gauge widget" 82 | msgstr "Afficher le widget de la jauge" 83 | 84 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:22 85 | msgid "Show window title" 86 | msgstr "Mostrar título da janela" 87 | 88 | #: data/io.github.nate_xyz.Chromatic.gschema.xml:28 89 | msgid "Stream buffer size" 90 | msgstr "Tamanho do stream buffer" 91 | 92 | #: src/window.rs:244 93 | msgid "Unable to initialize audio backend." 94 | msgstr "Não foi possível iniciar o beckend de áudio. " 95 | 96 | #: src/recorder.rs:155 src/recorder.rs:176 src/recorder.rs:206 97 | #: src/recorder.rs:241 98 | msgid "Unable to retrieve device ({device_name})" 99 | msgstr "Não foi possível buscar o dispositivo ({device_name})" 100 | 101 | #. Translators: Only replace "Error!". Reorder if necessary 102 | #: src/toasts.rs:60 103 | msgid "Error! {error_msg}" 104 | msgstr "Error! {error_msg}" 105 | 106 | #: src/ui/window.ui:187 107 | msgid "_Preferences" 108 | msgstr "_Preferências" 109 | 110 | #: src/ui/window.ui:195 111 | msgid "_About Chromatic" 112 | msgstr "_Sobre o Chromatic" 113 | 114 | #: src/ui/help-overlay.ui:11 115 | msgctxt "shortcut window" 116 | msgid "General" 117 | msgstr "Geral" 118 | 119 | #: src/ui/help-overlay.ui:14 120 | msgctxt "shortcut window" 121 | msgid "Show Shortcuts" 122 | msgstr "Mostrar atalhos" 123 | 124 | #: src/ui/help-overlay.ui:20 125 | msgctxt "shortcut window" 126 | msgid "Quit" 127 | msgstr "Sair" 128 | 129 | #: src/ui/preferences_window.ui:18 130 | msgid "Audio" 131 | msgstr "Áudio" 132 | 133 | #: src/ui/preferences_window.ui:22 134 | msgid "Manually Select" 135 | msgstr "Selecionar manualmente" 136 | 137 | #: src/ui/preferences_window.ui:23 138 | msgid "" 139 | "Chromatic will try to automatically detect running input devices if disabled." 140 | msgstr "" 141 | "Se desativado, Chromatic vai tentar detectar dispositivos de entrada " 142 | "automaticamente. " 143 | 144 | #: src/ui/preferences_window.ui:40 145 | msgid "Device" 146 | msgstr "Dispositivo" 147 | 148 | #: src/ui/preferences_window.ui:47 149 | msgid "Buffer Size" 150 | msgstr "Tamanho do buffer" 151 | 152 | #: src/ui/preferences_window.ui:48 153 | msgid "Stream buffer size, changes frequency update interval." 154 | msgstr "Tamanho do buffer, muda o intervalo de atualização de frequência" 155 | 156 | #: src/ui/preferences_window.ui:68 157 | msgid "UI" 158 | msgstr "Interface" 159 | 160 | #: src/ui/preferences_window.ui:72 161 | msgid "Show Window Title" 162 | msgstr "Mostrar Título da Janela" 163 | 164 | #: src/ui/preferences_window.ui:88 165 | msgid "Show Gauge" 166 | msgstr "Mostrar medidor analógico" 167 | 168 | #: src/ui/preferences_window.ui:104 169 | msgid "Gauge Pin Hover Time" 170 | msgstr "Tempo de oscilação do ponteiro do medidor analógico" 171 | 172 | #: src/ui/preferences_window.ui:105 173 | msgid "" 174 | "Duration (in seconds) the gauge pin will hover before returning to baseline." 175 | msgstr "Duração (em segundos) que o ponteiro do medidor irá oscilar antes de" 176 | "retornar ao estado inicial. " 177 | 178 | #: src/ui/preferences_window.ui:122 179 | msgid "Label Update Hang Time" 180 | msgstr "Rótulo de Atualização do tempo de oscilação" 181 | 182 | #: src/ui/preferences_window.ui:123 183 | msgid "Duration (in seconds) the labels will hang before clearing display." 184 | msgstr "Duração (em segundos) que o rótulo irá aparecer depois de limpar" 185 | "o display. " 186 | 187 | #: src/ui/preferences_window.ui:139 188 | msgid "Gauge Pin Resting Position" 189 | msgstr "Posição de descanso do ponteiro do medidor analógico" 190 | 191 | #: src/ui/preferences_window.ui:140 192 | msgid "Baseline position the pin returns to after hover time." 193 | msgstr "Posição inicial do ponteiro depois de oscilar. " 194 | 195 | #~ msgid "" 196 | #~ "Fine-tune your instruments with Chromatic. Displays exact frequency and " 197 | #~ "equivalent note with octave, as well as the cents of error." 198 | #~ msgstr "" 199 | #~ "Accordez vos instruments avec Chromatique. L’application affiche la " 200 | #~ "fréquence exacte et la note équivalente avec l’octave, ainsi qu’une marge " 201 | #~ "d’erreur au centième." 202 | -------------------------------------------------------------------------------- /src/ui/preferences_window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 162 | 163 | 164 | 10240 165 | 1024 166 | 6144 167 | 1024 168 | 10 169 | 170 | 171 | 172 | 5 173 | 0.5 174 | 1 175 | 0.5 176 | 10 177 | 178 | 179 | 180 | 5 181 | 0.5 182 | 1 183 | 0.5 184 | 10 185 | 186 | 187 | 188 | 50 189 | -50 190 | -45 191 | 5 192 | 10 193 | 194 | 195 | -------------------------------------------------------------------------------- /src/ui/window.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 184 | 185 |
186 | 187 | _Preferences 188 | app.preferences 189 | 190 | 194 | 195 | _About Chromatic 196 | app.about 197 | 198 |
199 |
200 |
201 | -------------------------------------------------------------------------------- /src/window.rs: -------------------------------------------------------------------------------- 1 | /* window.rs 2 | * 3 | * Copyright 2023 nate-xyz 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * SPDX-License-Identifier: GPL-3.0-or-later 19 | */ 20 | 21 | use adw::prelude::*; 22 | use adw::subclass::prelude::*; 23 | use gtk::{gio, gio::SettingsBindFlags, glib, glib::clone, glib::Receiver}; 24 | 25 | use std::{cell::{Cell, RefCell}, rc::Rc}; 26 | use std::time::{Duration, Instant}; 27 | use log::debug; 28 | 29 | use pitch_calc::{Hz, LetterOctave}; 30 | 31 | use super::i18n::i18n; 32 | use super::recorder::Recorder; 33 | use super::gauge::Gauge; 34 | use super::util; 35 | use super::toasts; 36 | 37 | 38 | 39 | #[derive(Clone, Debug)] 40 | pub enum AudioAction { 41 | RawAudio(Vec), 42 | Pitch(f32), 43 | } 44 | 45 | mod imp { 46 | use super::*; 47 | 48 | #[derive(Debug, gtk::CompositeTemplate)] 49 | #[template(resource = "/io/github/nate_xyz/Chromatic/window.ui")] 50 | pub struct Window { 51 | #[template_child(id = "toast_overlay")] 52 | pub toast_overlay: TemplateChild, 53 | 54 | #[template_child(id = "window_title")] 55 | pub window_title: TemplateChild, 56 | 57 | #[template_child(id = "note_label")] 58 | pub note_label: TemplateChild, 59 | 60 | #[template_child(id = "frequency_label")] 61 | pub frequency_label: TemplateChild, 62 | 63 | #[template_child(id = "cents_label")] 64 | pub cents_label: TemplateChild, 65 | 66 | #[template_child(id = "gauge_bin")] 67 | pub gauge_bin: TemplateChild, 68 | 69 | #[template_child(id = "gauge_box")] 70 | pub gauge_box: TemplateChild, 71 | 72 | 73 | #[template_child(id = "note_box")] 74 | pub note_box: TemplateChild, 75 | 76 | pub gauge: Rc>>, 77 | pub base_pitch: Cell, 78 | pub frequency: Cell, 79 | pub cents: Cell, 80 | pub recorder: Rc, 81 | pub receiver: RefCell>>, 82 | pub settings: gio::Settings, 83 | pub show_gauge: Cell, 84 | 85 | pub hang_duration: Cell, 86 | pub hang_time: RefCell> 87 | } 88 | 89 | #[glib::object_subclass] 90 | impl ObjectSubclass for Window { 91 | const NAME: &'static str = "Window"; 92 | type Type = super::Window; 93 | type ParentType = adw::ApplicationWindow; 94 | 95 | fn class_init(klass: &mut Self::Class) { 96 | klass.bind_template(); 97 | } 98 | 99 | fn instance_init(obj: &glib::subclass::InitializingObject) { 100 | obj.init_template(); 101 | } 102 | 103 | fn new() -> Self{ 104 | let (sender, r) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); 105 | 106 | Self { 107 | toast_overlay: TemplateChild::default(), 108 | window_title: TemplateChild::default(), 109 | note_label: TemplateChild::default(), 110 | frequency_label: TemplateChild::default(), 111 | cents_label: TemplateChild::default(), 112 | gauge_bin: TemplateChild::default(), 113 | gauge_box: TemplateChild::default(), 114 | note_box: TemplateChild::default(), 115 | gauge: Rc::new(RefCell::new(None)), 116 | base_pitch: Cell::new(440.0), 117 | frequency: Cell::new(0.0), 118 | cents: Cell::new(0), 119 | recorder: Rc::new(Recorder::new(sender)), 120 | receiver: RefCell::new(Some(r)), 121 | settings: util::settings_manager(), 122 | show_gauge: Cell::new(true), 123 | hang_duration: Cell::new(3), 124 | hang_time: RefCell::new(None), 125 | } 126 | } 127 | } 128 | 129 | impl ObjectImpl for Window {} 130 | impl WidgetImpl for Window {} 131 | impl WindowImpl for Window {} 132 | impl ApplicationWindowImpl for Window {} 133 | impl AdwApplicationWindowImpl for Window {} 134 | } 135 | 136 | glib::wrapper! { 137 | pub struct Window(ObjectSubclass) 138 | @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow, 139 | @implements gio::ActionGroup, gio::ActionMap; 140 | } 141 | 142 | impl Window { 143 | pub fn new>(application: &P) -> Self { 144 | let window: Window = glib::Object::new(&[("application", application)]); 145 | window.setup(); 146 | window 147 | } 148 | 149 | fn setup(&self) { 150 | let imp = self.imp(); 151 | let gauge = Gauge::new(300, 500); 152 | imp.gauge_bin.set_child(Some(&gauge)); 153 | 154 | self.imp().settings 155 | .bind("show-gauge", imp.gauge_box.upcast_ref::(), "visible") 156 | .flags(SettingsBindFlags::DEFAULT) 157 | .build(); 158 | 159 | let show = imp.settings.boolean("show-gauge"); 160 | if show { 161 | imp.gauge_box.set_height_request(200); 162 | imp.note_box.set_vexpand(false); 163 | } else { 164 | imp.gauge_box.set_height_request(0); 165 | imp.note_box.set_vexpand(true); 166 | } 167 | imp.show_gauge.set(show); 168 | 169 | imp.settings.connect_changed( 170 | Some("show-gauge"), 171 | clone!(@strong self as this => move |_settings, _name| { 172 | let imp = this.imp(); 173 | let show = imp.settings.boolean("show-gauge"); 174 | if show { 175 | imp.gauge_box.set_height_request(200); 176 | imp.note_box.set_vexpand(false); 177 | } else { 178 | imp.gauge_box.set_height_request(0); 179 | imp.note_box.set_vexpand(true); 180 | } 181 | imp.show_gauge.set(show); 182 | }), 183 | ); 184 | 185 | imp.gauge.replace(Some(gauge)); 186 | 187 | self.imp().settings 188 | .bind("show-title", imp.window_title.upcast_ref::(), "visible") 189 | .flags(SettingsBindFlags::DEFAULT) 190 | .build(); 191 | 192 | self.setup_channel(); 193 | self.bind_signals(); 194 | } 195 | 196 | fn setup_channel(&self) { 197 | let imp = self.imp(); 198 | let receiver = imp.receiver.borrow_mut().take().unwrap(); 199 | receiver.attach( 200 | None, 201 | clone!(@strong self as this => move |action| this.clone().process_action(action)), 202 | ); 203 | } 204 | 205 | 206 | fn process_action(&self, action: AudioAction) -> glib::Continue { 207 | match action { 208 | AudioAction::RawAudio(buffer) => { 209 | debug!("BUFFER {:?}", buffer); 210 | }, 211 | AudioAction::Pitch(freq) => { 212 | self.update_frequency(freq); 213 | }, 214 | // _ => debug!("Received action {:?}", action), 215 | } 216 | glib::Continue(true) 217 | } 218 | 219 | fn bind_signals(&self) { 220 | debug!("bind signals - window"); 221 | let imp = self.imp(); 222 | 223 | imp.recorder.connect_local( 224 | "frequency", 225 | false, 226 | clone!(@weak self as this => @default-return None, move |value| { 227 | let freq_val = value.get(1); 228 | match freq_val { 229 | Some(freq_val) => { 230 | let freq = freq_val.get::().ok().unwrap(); 231 | this.update_frequency(freq); 232 | }, 233 | None => (), 234 | } 235 | 236 | None 237 | }), 238 | ); 239 | 240 | match imp.recorder.setup() { 241 | Ok(_) => (), 242 | Err(e) => { 243 | debug!("Error recorder setup: {}", e); 244 | toasts::add_error_toast(i18n("Unable to initialize audio backend.")); 245 | }, 246 | } 247 | } 248 | 249 | 250 | pub fn update_settings(&self) { 251 | let imp = self.imp(); 252 | imp.hang_duration.set((1000.0 * imp.settings.double("label-hang")) as u64) 253 | } 254 | 255 | pub fn update_frequency(&self, frequency: f32) { 256 | let imp = self.imp(); 257 | if frequency <= 0.0 { 258 | if imp.hang_time.borrow().is_none() { 259 | imp.hang_time.replace(Some(Instant::now())); 260 | } else { 261 | if imp.hang_time.borrow().as_ref().unwrap().elapsed() > Duration::from_millis(imp.hang_duration.get()) { 262 | imp.note_label.set_label("--"); 263 | imp.frequency_label.set_label("-- Hz"); 264 | imp.cents_label.set_label(""); 265 | imp.hang_time.replace(None); 266 | } 267 | } 268 | 269 | } else { 270 | if !imp.hang_time.borrow().is_none() { 271 | imp.hang_time.replace(None); 272 | } 273 | 274 | imp.frequency_label.set_label(&format!("{:.2} Hz", frequency)); 275 | 276 | let letter_octave = Hz(frequency).letter_octave(); 277 | 278 | let letter = match letter_octave.0 as u64 { 279 | 0 => "C", 280 | 1 => "C♯", 281 | 2 => "D♭", 282 | 3 => "D", 283 | 4 => "D♯", 284 | 5 => "E♭", 285 | 6 => "E", 286 | 7 => "F", 287 | 8 => "F♯", 288 | 9 => "G♭", 289 | 10 => "G", 290 | 11 => "G♯", 291 | 12 => "A♭", 292 | 13 => "A", 293 | 14 => "A♯", 294 | 15 => "B♭", 295 | 16 => "B", 296 | 17_u64..=u64::MAX => "?", 297 | }; 298 | 299 | imp.note_label.set_label(&format!("{}{}", letter, letter_octave.1)); 300 | 301 | let closest_freq = LetterOctave(letter_octave.0, letter_octave.1).to_hz(); 302 | 303 | let cents = (1200.0 * (frequency / closest_freq.0).log2()) as i32; 304 | 305 | if cents > 0 { 306 | imp.cents_label.set_label(&format!("+{} cents", cents)); 307 | } else if cents < 0 { 308 | imp.cents_label.set_label(&format!("{} cents", cents)); 309 | } else { 310 | imp.cents_label.set_label(""); 311 | } 312 | 313 | imp.cents.set(cents); 314 | 315 | if self.imp().show_gauge.get() { 316 | imp.gauge.borrow().as_ref().unwrap().set_gauge_position(cents); 317 | } 318 | 319 | 320 | } 321 | } 322 | 323 | pub fn add_toast(&self, toast: &adw::Toast) { 324 | self.imp().toast_overlay.add_toast(toast); 325 | } 326 | 327 | pub fn recorder(&self) -> Rc { 328 | self.imp().recorder.clone() 329 | } 330 | 331 | pub fn gauge(&self) -> Rc>> { 332 | self.imp().gauge.clone() 333 | } 334 | 335 | 336 | } 337 | -------------------------------------------------------------------------------- /src/preferences_window.rs: -------------------------------------------------------------------------------- 1 | /* preferences_window.rs 2 | * 3 | * Copyright 2023 nate-xyz 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * SPDX-License-Identifier: GPL-3.0-or-later 19 | */ 20 | 21 | use adw::prelude::*; 22 | use adw::subclass::prelude::*; 23 | use gtk::{gio, gio::SettingsBindFlags, glib, glib::clone}; 24 | 25 | use std::{cell::RefCell, error::Error}; 26 | use log::{debug, error}; 27 | 28 | use fuzzy_matcher::skim::SkimMatcherV2; 29 | use fuzzy_matcher::FuzzyMatcher; 30 | 31 | use pulsectl::controllers::DeviceControl; 32 | use pulsectl::controllers::SourceController; 33 | 34 | use super::util; 35 | 36 | mod imp { 37 | use super::*; 38 | 39 | #[derive(Debug, gtk::CompositeTemplate)] 40 | #[template(resource = "/io/github/nate_xyz/Chromatic/preferences_window.ui")] 41 | pub struct PreferencesWindow { 42 | #[template_child(id = "switch_device_select")] 43 | pub switch_device_select: TemplateChild, 44 | 45 | #[template_child(id = "switch_gauge_visible")] 46 | pub switch_gauge_visible: TemplateChild, 47 | 48 | #[template_child(id = "switch_title_visible")] 49 | pub switch_title_visible: TemplateChild, 50 | 51 | #[template_child(id = "device_row")] 52 | pub device_row: TemplateChild, 53 | 54 | #[template_child(id = "buffer_adj")] 55 | pub buffer_adj: TemplateChild, 56 | 57 | #[template_child(id = "gauge_hang_adj")] 58 | pub gauge_hang_adj: TemplateChild, 59 | 60 | #[template_child(id = "label_hang_adj")] 61 | pub label_hang_adj: TemplateChild, 62 | 63 | #[template_child(id = "gauge_rest_adj")] 64 | pub gauge_rest_adj: TemplateChild, 65 | 66 | #[template_child(id = "buffer_spin")] 67 | pub buffer_spin: TemplateChild, 68 | 69 | pub settings: gio::Settings, 70 | pub devices_model: gtk::StringList, 71 | pub selected_device: RefCell, 72 | } 73 | 74 | #[glib::object_subclass] 75 | impl ObjectSubclass for PreferencesWindow { 76 | const NAME: &'static str = "PreferencesWindow"; 77 | type Type = super::PreferencesWindow; 78 | type ParentType = adw::PreferencesWindow; 79 | 80 | fn class_init(klass: &mut Self::Class) { 81 | klass.bind_template(); 82 | } 83 | 84 | fn instance_init(obj: &glib::subclass::InitializingObject) { 85 | obj.init_template(); 86 | } 87 | 88 | fn new() -> Self { 89 | Self { 90 | switch_device_select: TemplateChild::default(), 91 | switch_gauge_visible: TemplateChild::default(), 92 | switch_title_visible: TemplateChild::default(), 93 | device_row: TemplateChild::default(), 94 | buffer_adj: TemplateChild::default(), 95 | gauge_hang_adj: TemplateChild::default(), 96 | label_hang_adj: TemplateChild::default(), 97 | gauge_rest_adj: TemplateChild::default(), 98 | buffer_spin: TemplateChild::default(), 99 | settings: util::settings_manager(), 100 | devices_model: gtk::StringList::new(&[]), 101 | selected_device: RefCell::new("".to_string()), 102 | } 103 | } 104 | } 105 | 106 | impl ObjectImpl for PreferencesWindow { 107 | fn constructed(&self) { 108 | self.parent_constructed(); 109 | let obj = self.obj(); 110 | match obj.setup_settings() { 111 | Ok(_) => (), 112 | Err(e) => error!("unable to load settings: {}", e), 113 | } 114 | } 115 | } 116 | 117 | impl WidgetImpl for PreferencesWindow {} 118 | impl WindowImpl for PreferencesWindow {} 119 | impl AdwWindowImpl for PreferencesWindow {} 120 | impl PreferencesWindowImpl for PreferencesWindow {} 121 | } 122 | 123 | glib::wrapper! { 124 | pub struct PreferencesWindow(ObjectSubclass) 125 | @extends gtk::Widget, gtk::Window, adw::Window, adw::PreferencesWindow, 126 | @implements gtk::Accessible; 127 | } 128 | 129 | impl PreferencesWindow { 130 | pub fn new() -> PreferencesWindow { 131 | let prefences: PreferencesWindow = glib::Object::builder::().build(); 132 | prefences 133 | } 134 | 135 | fn setup_settings(&self) -> Result<(), Box> { 136 | let imp = self.imp(); 137 | 138 | debug!("pref window -> setup"); 139 | 140 | let devices = self.input_devices()?; 141 | 142 | debug!("pref window -> devices"); 143 | 144 | imp.device_row.set_model(Some(&imp.devices_model)); 145 | for d in devices { 146 | imp.devices_model.append(&d); 147 | } 148 | 149 | imp.settings 150 | .bind("choose-device", &*imp.switch_device_select, "active") 151 | .flags(SettingsBindFlags::DEFAULT) 152 | .build(); 153 | 154 | imp.settings 155 | .bind("choose-device", &*imp.device_row, "sensitive") 156 | .flags(SettingsBindFlags::GET) 157 | .build(); 158 | 159 | imp.settings 160 | .bind("show-gauge", &*imp.switch_gauge_visible, "active") 161 | .flags(SettingsBindFlags::DEFAULT) 162 | .build(); 163 | 164 | imp.settings 165 | .bind("show-title", &*imp.switch_title_visible, "active") 166 | .flags(SettingsBindFlags::DEFAULT) 167 | .build(); 168 | 169 | imp.settings.connect_changed( 170 | Some("selected-device"), 171 | clone!(@strong self as this => move |_settings, _name| { 172 | debug!("Pref window -> settings update selected device"); 173 | let imp = this.imp(); 174 | let device_name = imp.settings.string("selected-device").to_string(); 175 | 176 | 177 | let selected = imp.device_row.selected(); 178 | let device_name_row = imp.devices_model.string(selected).unwrap().to_string(); 179 | 180 | if device_name != device_name_row { 181 | match util::recorder().switch_stream(Some(device_name.clone())) { 182 | Ok(_) => { 183 | debug!("switched streams"); 184 | imp.device_row.set_subtitle(&device_name); 185 | this.set_device_selected(device_name); 186 | }, 187 | Err(e) => debug!("{}", e), 188 | } 189 | } else { 190 | debug!("already set from row"); 191 | } 192 | 193 | 194 | }), 195 | ); 196 | 197 | 198 | imp.settings.connect_changed( 199 | Some("choose-device"), 200 | clone!(@strong self as this => move |_settings, _name| { 201 | debug!("Pref window -> settings choose-device"); 202 | let imp = this.imp(); 203 | let manual = imp.settings.boolean("choose-device"); 204 | let device_name = imp.settings.string("selected-device").to_string(); 205 | 206 | 207 | 208 | if manual { 209 | match util::recorder().switch_stream(None) { 210 | Ok(_) => debug!("switched streams"), 211 | Err(e) => debug!("{}", e), 212 | } 213 | } else { 214 | match util::recorder().switch_stream(Some(device_name)) { 215 | Ok(_) => debug!("switched streams"), 216 | Err(e) => debug!("{}", e), 217 | } 218 | } 219 | 220 | }), 221 | ); 222 | 223 | //SET THE DEVICE ROW CURRENT UI FROM SETTINGS 224 | let device_name = imp.settings.string("selected-device").to_string(); 225 | imp.device_row.set_subtitle(&device_name); 226 | self.set_device_selected(device_name); 227 | 228 | imp.device_row.connect_selected_notify( 229 | clone!(@weak self as this => @default-panic, move |_value| { 230 | debug!("Pref window -> device row select notify"); 231 | let imp = this.imp(); 232 | let selected = imp.device_row.selected(); 233 | let device_name = imp.devices_model.string(selected).unwrap().to_string(); 234 | imp.device_row.set_subtitle(device_name.as_str()); // set the subtitle of the AdwComboRow 235 | 236 | match util::recorder().switch_stream(Some(device_name)) { 237 | Ok(_) => debug!("switched streams"), 238 | Err(e) => debug!("{}", e), 239 | } 240 | }), 241 | ); 242 | 243 | 244 | imp.settings 245 | .bind("buffer-size", &*imp.buffer_adj, "value") 246 | .flags(SettingsBindFlags::DEFAULT) 247 | .build(); 248 | 249 | imp.settings.connect_changed( 250 | Some("buffer-size"), 251 | clone!(@strong self as this => move |_settings, _name| { 252 | let imp = this.imp(); 253 | let device_name = imp.settings.string("selected-device").to_string(); 254 | imp.buffer_spin.set_sensitive(false); 255 | match util::recorder().switch_stream(Some(device_name.clone())) { 256 | Ok(_) => { 257 | debug!("switched streams"); 258 | imp.device_row.set_subtitle(&device_name); 259 | this.set_device_selected(device_name); 260 | }, 261 | Err(e) => debug!("{}", e), 262 | } 263 | imp.buffer_spin.set_sensitive(true); 264 | }), 265 | ); 266 | 267 | imp.settings 268 | .bind("gauge-hang", &*imp.gauge_hang_adj, "value") 269 | .flags(SettingsBindFlags::DEFAULT) 270 | .build(); 271 | 272 | imp.settings.connect_changed( 273 | Some("gauge-hang"), 274 | clone!(@strong self as this => move |_settings, _name| { 275 | util::gauge().borrow().as_ref().unwrap().start_drawing_thread(); 276 | }), 277 | ); 278 | 279 | imp.settings 280 | .bind("label-hang", &*imp.label_hang_adj, "value") 281 | .flags(SettingsBindFlags::DEFAULT) 282 | .build(); 283 | 284 | imp.settings.connect_changed( 285 | Some("label-hang"), 286 | clone!(@strong self as this => move |_settings, _name| { 287 | util::window().update_settings(); 288 | }), 289 | ); 290 | 291 | imp.settings 292 | .bind("gauge-rest-position", &*imp.gauge_rest_adj, "value") 293 | .flags(SettingsBindFlags::DEFAULT) 294 | .build(); 295 | 296 | imp.settings.connect_changed( 297 | Some("gauge-rest-position"), 298 | clone!(@strong self as this => move |_settings, _name| { 299 | util::gauge().borrow().as_ref().unwrap().start_drawing_thread(); 300 | }), 301 | ); 302 | 303 | 304 | Ok(()) 305 | } 306 | 307 | fn set_device_selected(&self, selected_name: String) { 308 | let imp = self.imp(); 309 | let mut ratio = 0; 310 | let matcher = SkimMatcherV2::default(); 311 | let mut index = 0; 312 | 313 | for i in 0..imp.devices_model.n_items() { 314 | let current_name = imp.devices_model.string(i).unwrap().to_string(); 315 | 316 | match matcher.fuzzy_match(&selected_name, ¤t_name) { 317 | Some(val) => { 318 | if val > ratio { 319 | ratio = val; 320 | index = i; 321 | } 322 | } 323 | None => (), 324 | } 325 | } 326 | 327 | debug!("settings window updated device row {:#?}", index); 328 | 329 | imp.device_row.set_selected(index); 330 | } 331 | 332 | //get input devices from pulseaudio 333 | pub fn input_devices(&self) -> Result, Box> { 334 | let mut array = Vec::new(); 335 | let mut handler = SourceController::create().unwrap(); 336 | for device in handler.list_devices()? { 337 | if device.monitor.is_none() { 338 | array.push(device.description.unwrap()); 339 | } 340 | } 341 | 342 | Ok(array) 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /src/recorder.rs: -------------------------------------------------------------------------------- 1 | /* recorder.rs 2 | * 3 | * Copyright 2023 nate-xyz 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * SPDX-License-Identifier: GPL-3.0-or-later 19 | */ 20 | 21 | use adw::prelude::*; 22 | use adw::subclass::prelude::*; 23 | use gtk::{gio, glib, glib::Sender}; 24 | 25 | use std::{cell::Cell, cell::RefCell, error::Error, rc::Rc, fmt, thread}; 26 | use std::sync::mpsc; 27 | use std::sync::mpsc::*; 28 | use log::{debug, error}; 29 | 30 | use pulsectl::controllers::DeviceControl; 31 | use pulsectl::controllers::SourceController; 32 | 33 | use portaudio; 34 | 35 | use aubio::{Pitch, PitchMode}; 36 | 37 | use fuzzy_matcher::skim::SkimMatcherV2; 38 | use fuzzy_matcher::FuzzyMatcher; 39 | 40 | use super::window::AudioAction; 41 | use super::util; 42 | use super::toasts; 43 | use super::i18n::i18n_k; 44 | 45 | 46 | 47 | #[derive(Debug)] 48 | struct RecorderError(String); 49 | 50 | impl fmt::Display for RecorderError { 51 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 52 | write!(f, "There is an error: {}", self.0) 53 | } 54 | } 55 | 56 | impl Error for RecorderError {} 57 | 58 | mod imp { 59 | use super::*; 60 | use glib::subclass::Signal; 61 | use once_cell::sync::Lazy; 62 | 63 | #[derive(Debug)] 64 | pub struct RecorderPriv { 65 | pub sample_rate: Cell, //44800 66 | pub buffer_size: Cell, 67 | pub pa: RefCell>>, 68 | pub sender: RefCell>>, 69 | 70 | pub tx: RefCell>>, 71 | // pub rx: RefCell>>, 72 | pub settings: gio::Settings, 73 | } 74 | 75 | #[glib::object_subclass] 76 | impl ObjectSubclass for RecorderPriv { 77 | const NAME: &'static str = "Recorder"; 78 | type Type = super::Recorder; 79 | type ParentType = glib::Object; 80 | 81 | fn new() -> Self { 82 | Self { 83 | sample_rate: Cell::new(44100), 84 | buffer_size: Cell::new(1024), 85 | pa: RefCell::new(None), 86 | sender: RefCell::new(None), 87 | tx: RefCell::new(None), 88 | // rx: RefCell::new(None), 89 | settings: util::settings_manager(), 90 | } 91 | } 92 | } 93 | 94 | impl ObjectImpl for RecorderPriv { 95 | fn constructed(&self) { 96 | self.parent_constructed(); 97 | } 98 | 99 | fn signals() -> &'static [Signal] { 100 | static SIGNALS: Lazy> = Lazy::new(|| { 101 | vec![Signal::builder("frequency") 102 | .param_types([::static_type()]) 103 | .build()] 104 | }); 105 | 106 | SIGNALS.as_ref() 107 | } 108 | } 109 | } 110 | 111 | glib::wrapper! { 112 | pub struct Recorder(ObjectSubclass); 113 | } 114 | 115 | impl Default for Recorder { 116 | fn default() -> Self { 117 | glib::Object::builder::().build() 118 | } 119 | } 120 | 121 | impl Recorder { 122 | pub fn new(sender: Sender) -> Recorder { 123 | let recorder: Recorder = Self::default(); 124 | recorder.load_sender(sender); 125 | recorder 126 | } 127 | 128 | fn load_sender(&self, sender: Sender) { 129 | self.imp().sender.replace(Some(sender)); 130 | } 131 | 132 | 133 | pub fn setup(&self) -> Result<(), Box> { 134 | let imp = self.imp(); 135 | 136 | //get settings vars 137 | let manual: bool = imp.settings.boolean("choose-device"); 138 | let manual_device_name = imp.settings.string("selected-device").to_string(); 139 | 140 | // Construct a portaudio instance that will connect to a native audio API 141 | imp.pa.replace(Some(Rc::new( 142 | portaudio::PortAudio::new().expect("Unable to init PortAudio"), 143 | ))); 144 | 145 | //if manual set, try to stream with manual 146 | if manual && manual_device_name != "" { 147 | match self.running_mic_index(manual_device_name.clone()) { 148 | // and start the stream with the index 149 | Ok(index) => { 150 | debug!("got manual device... {}", manual_device_name); 151 | return self.start_stream(Some(index)); 152 | }, 153 | Err(e) => { 154 | error!("unable to retrieve device {}", e); 155 | toasts::add_error_toast(i18n_k("Unable to retrieve device ({device_name})", &[("device_name", &manual_device_name)])); 156 | }, 157 | } 158 | } 159 | 160 | //try to get running input device from pulse audio 161 | match self.running_device() { 162 | //if able to retrieve, get the device's index from portaudio 163 | Ok(device_name) => { 164 | debug!("retrieved {}", device_name); 165 | 166 | match self.running_mic_index(device_name.clone()) { 167 | // and start the stream with the index 168 | Ok(index) => { 169 | debug!("got running device..."); 170 | self.start_stream(Some(index))?; 171 | imp.settings.set_string("selected-device", &device_name)?; 172 | return Ok(()); 173 | }, 174 | Err(e) => { 175 | error!("{},unable to retrieve device ", e); 176 | toasts::add_error_toast(i18n_k("Unable to retrieve device ({device_name})", &[("device_name", &device_name)])); 177 | }, 178 | } 179 | } 180 | Err(e) => { 181 | error!("{}, starting stream w/ default option", e); 182 | } 183 | } 184 | 185 | //otherwise, just start the stream with the default portaudio option 186 | return self.start_stream(None); 187 | } 188 | 189 | 190 | pub fn switch_stream(&self, device_option: Option) -> Result<(), Box> { 191 | let imp = self.imp(); 192 | 193 | if device_option.is_none() { 194 | let manual: bool = imp.settings.boolean("choose-device"); 195 | let manual_device_name = imp.settings.string("selected-device").to_string(); 196 | 197 | if manual && manual_device_name != "" { 198 | match self.running_mic_index(manual_device_name.clone()) { 199 | // and start the stream with the index 200 | Ok(index) => { 201 | debug!("switch_stream -> got manual device... {}", manual_device_name); 202 | return self.start_stream(Some(index)); 203 | }, 204 | Err(e) => { 205 | error!("switch_stream -> unable to retrieve device {}", e); 206 | toasts::add_error_toast(i18n_k("Unable to retrieve device ({device_name})", &[("device_name", &manual_device_name)])); 207 | }, 208 | } 209 | } 210 | } 211 | 212 | let device_name = match device_option { 213 | Some(name) => name, 214 | None => { 215 | match self.running_device() { 216 | //if able to retrieve, get the device's index from portaudio 217 | Ok(name) => { 218 | debug!("switch_stream -> retrieved {}", name); 219 | name 220 | }, 221 | Err(e) => { 222 | error!("switch_stream -> {}, starting stream w/ default option", e); 223 | 224 | //otherwise, just start the stream with the default portaudio option 225 | return self.start_stream(None); 226 | } 227 | } 228 | }, 229 | }; 230 | 231 | match self.running_mic_index(device_name.clone()) { 232 | // and start the stream with the index 233 | Ok(index) => { 234 | debug!("switch_stream -> got running device..."); 235 | self.start_stream(Some(index))?; 236 | imp.settings.set_string("selected-device", &device_name)?; 237 | return Ok(()); 238 | }, 239 | Err(e) => { 240 | error!("switch_stream -> {},unable to retrieve device ", e); 241 | toasts::add_error_toast(i18n_k("Unable to retrieve device ({device_name})", &[("device_name", &device_name)])); 242 | }, 243 | } 244 | 245 | 246 | //otherwise, just start the stream with the default portaudio option 247 | return self.start_stream(None); 248 | } 249 | 250 | fn running_device(&self) -> Result> { 251 | let mut handler = SourceController::create().unwrap(); 252 | for device in handler.list_devices()? { 253 | if device.state == pulsectl::controllers::types::DevState::Running { 254 | debug!("Pulse Audio Source: {}", device.name.unwrap()); 255 | return Ok(device.description.unwrap().to_owned()); 256 | } 257 | } 258 | return Err(Box::new(RecorderError("No devices found".into()))); 259 | } 260 | 261 | fn running_mic_index(&self, device_name: String) -> Result> { 262 | let mut ratio = 0; 263 | let mut mic_index = self.pa().default_input_device()?; 264 | let matcher = SkimMatcherV2::default(); 265 | 266 | for device in self.pa().devices()? { 267 | let (idx, info) = device?; 268 | let in_channels = info.max_input_channels; 269 | 270 | if in_channels > 0 { 271 | 272 | match matcher.fuzzy_match(&device_name, &info.name) { 273 | Some(val) => { 274 | if val > ratio { 275 | ratio = val; 276 | mic_index = idx; 277 | } 278 | } 279 | None => (), 280 | } 281 | } 282 | } 283 | 284 | debug!("{:#?} {}", &mic_index, ratio); 285 | 286 | Ok(mic_index) 287 | } 288 | 289 | 290 | 291 | pub fn start_stream(&self, mic_option: Option) -> Result<(), Box> { 292 | // MANY THANKS TO https://dev.to/maniflames/audio-visualization-with-rust-4nhg 293 | 294 | let mic_index = match mic_option { 295 | Some(index) => index, 296 | None => self.pa().default_input_device()?, 297 | }; 298 | 299 | debug!("recorder -> start stream {:?}", mic_index); 300 | 301 | let imp = self.imp(); 302 | let default_low_input_latency = self.pa().device_info(mic_index)?.default_low_input_latency; 303 | let default_sample_rate = self.pa().device_info(mic_index)?.default_sample_rate; 304 | 305 | // Set parameters for the stream settings. 306 | // We pass which mic should be used, how many channels are used, 307 | // whether all the values of all the channels should be passed in a 308 | // single audiobuffer and the latency that should be considered 309 | let input_params = 310 | portaudio::StreamParameters::::new(mic_index, 1, true, default_low_input_latency); 311 | 312 | // Settings for an inputstream. 313 | // Here we pass the stream parameters we set before, 314 | // the sample rate of the mic and the amount values we want to receive 315 | 316 | let buffer_size_setting: u32 = imp.settings.double("buffer-size") as u32; 317 | let buffer_size: u32 = 1024 * (buffer_size_setting / 1024); 318 | 319 | debug!("buffer size {}", buffer_size); 320 | 321 | let input_settings = 322 | portaudio::InputStreamSettings::new(input_params, default_sample_rate, buffer_size); 323 | 324 | // Creating a channel so we can receive audio values asynchronously 325 | let (sender, receiver) = channel(); 326 | 327 | // Additional channel to kill thread when switching devices 328 | //let (tx, rx) = crossbeam_channel::unbounded::<()>(); 329 | let (tx, rx) = mpsc::channel::<()>(); 330 | 331 | if !imp.tx.borrow().as_ref().is_none() { 332 | debug!("killing previous thread with drop send"); 333 | drop(imp.tx.borrow().as_ref()); //drop should kill previous stream 334 | } 335 | imp.tx.replace(Some(tx)); 336 | 337 | // A callback function that should be as short as possible so we send all the info to a different thread 338 | let callback = 339 | move | portaudio::InputStreamCallbackArgs { buffer, .. } | match sender.send(buffer) { 340 | Ok(_) => portaudio::Continue, 341 | Err(_) => portaudio::Complete, 342 | }; 343 | 344 | // Creating & starting the input stream with our settings & callback 345 | let mut stream = self 346 | .pa() 347 | .open_non_blocking_stream(input_settings, callback)?; 348 | 349 | stream.start()?; 350 | 351 | //Printing values every time we receive new ones while the stream is active 352 | let glib_sender = imp.sender.borrow().as_ref().unwrap().clone(); 353 | 354 | //RECEIVE AUDIO BUFFER AND SEND TO GLIB LOOP 355 | thread::spawn(move || { 356 | let mut pitch_detector = Pitch::new( 357 | PitchMode::Yin, 358 | buffer_size as usize, 359 | buffer_size as usize / 2, 360 | default_sample_rate as u32, 361 | ) 362 | .unwrap(); 363 | 364 | debug!("recorder -> stream thread"); 365 | 366 | while stream.is_active().unwrap() { 367 | while let Ok(buffer) = receiver.try_recv() { 368 | let pitch = pitch_detector.do_result(&buffer).unwrap(); 369 | 370 | //aubio bugs out sometimes? 371 | if pitch < 95999.98 { 372 | match glib_sender.send(AudioAction::Pitch(pitch)) { 373 | Ok(_) => (), 374 | Err(e) => { 375 | error!("SEND ERROR {}", e); 376 | } 377 | } 378 | } 379 | 380 | 381 | } 382 | 383 | //kill stream thread if channel disconnect 384 | match rx.try_recv() { 385 | Ok(_) | Err(mpsc::TryRecvError::Disconnected) => { 386 | error!("disconnected channel, stream should end"); 387 | break; 388 | } 389 | Err(mpsc::TryRecvError::Empty) => {} 390 | } 391 | 392 | } 393 | 394 | debug!("stream closing ..."); 395 | stream.close().unwrap(); 396 | }); 397 | 398 | Ok(()) 399 | } 400 | 401 | fn pa(&self) -> Rc { 402 | self.imp().pa.borrow().as_ref().unwrap().clone() 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /src/gauge.rs: -------------------------------------------------------------------------------- 1 | /* gauge.rs 2 | * 3 | * Copyright 2023 nate-xyz 4 | * 5 | * Thanks to lingot! https://github.com/ibancg/lingot 6 | * https://github.com/ibancg/lingot/blob/master/src/lingot-gui-gauge.c 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | * 21 | * SPDX-License-Identifier: GPL-3.0-or-later 22 | */ 23 | 24 | use adw::prelude::*; 25 | use adw::subclass::prelude::*; 26 | 27 | use gtk::{cairo, glib, glib::clone, glib::Receiver, glib::Sender, gio}; 28 | 29 | use std::{cell::Cell, cell::RefCell, error::Error, f64::consts::PI}; 30 | 31 | use log::{debug, error}; 32 | 33 | use std::thread; 34 | use std::time::{Duration, Instant}; 35 | 36 | //use crossbeam_channel; 37 | use std::sync::mpsc; 38 | 39 | 40 | use super::util; 41 | 42 | #[derive(Clone, Debug)] 43 | pub enum GaugeAction { 44 | UpdateGaugePos(i32), 45 | } 46 | 47 | mod imp { 48 | use super::*; 49 | 50 | #[derive(Debug)] 51 | pub struct Gauge { 52 | pub width: Cell, 53 | pub height: Cell, 54 | pub gauge_pos: Cell, 55 | //pub gauge_end_pos: Cell, 56 | pub gauge_range: Cell, 57 | pub hover_time: Cell, 58 | pub gauge_rest_position: Cell, 59 | 60 | pub drawing_area: gtk::DrawingArea, 61 | 62 | pub sender: RefCell>>, 63 | pub receiver: RefCell>>, 64 | 65 | pub tx: RefCell>>, 66 | 67 | pub settings: gio::Settings, 68 | 69 | } 70 | 71 | #[glib::object_subclass] 72 | impl ObjectSubclass for Gauge { 73 | const NAME: &'static str = "Gauge"; 74 | type Type = super::Gauge; 75 | type ParentType = adw::Bin; 76 | 77 | fn new() -> Self { 78 | let (sender, receiver) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); 79 | 80 | Self { 81 | width: Cell::new(0), 82 | height: Cell::new(0), 83 | gauge_pos: Cell::new(0.0), 84 | //gauge_end_pos: Cell::new(0.0), 85 | gauge_range: Cell::new(0.0), 86 | hover_time: Cell::new(1.0), 87 | gauge_rest_position: Cell::new(-45), 88 | drawing_area: gtk::DrawingArea::new(), 89 | sender: RefCell::new(Some(sender)), 90 | receiver: RefCell::new(Some(receiver)), 91 | tx: RefCell::new(None), 92 | settings: util::settings_manager(), 93 | } 94 | } 95 | } 96 | 97 | impl ObjectImpl for Gauge { 98 | fn constructed(&self) { 99 | self.parent_constructed(); 100 | } 101 | } 102 | 103 | impl WidgetImpl for Gauge {} 104 | impl BinImpl for Gauge {} 105 | } 106 | 107 | glib::wrapper! { 108 | pub struct Gauge(ObjectSubclass) 109 | @extends gtk::Widget, adw::Bin; 110 | } 111 | 112 | impl Gauge { 113 | pub fn new(width: u32, height: u32) -> Gauge { 114 | let object: Gauge = glib::Object::builder::().build(); 115 | object.setup_channel(); 116 | object.construct(width, height); 117 | object 118 | } 119 | 120 | fn setup_channel(&self) { 121 | let imp = self.imp(); 122 | let receiver = imp.receiver.borrow_mut().take().unwrap(); 123 | receiver.attach( 124 | None, 125 | clone!(@strong self as this => move |action| this.clone().process_action(action)), 126 | ); 127 | } 128 | 129 | fn process_action(&self, action: GaugeAction) -> glib::Continue { 130 | match action { 131 | GaugeAction::UpdateGaugePos(pos) => { 132 | self.update_gauge_position(pos); 133 | self.imp().drawing_area.queue_draw(); 134 | } 135 | // _ => debug!("Received action {:?}", action), 136 | } 137 | 138 | glib::Continue(true) 139 | } 140 | 141 | fn construct(&self, width: u32, height: u32) { 142 | debug!("GAUGE CONSTRUCT"); 143 | let imp = self.imp(); 144 | imp.width.set(width); 145 | imp.height.set(height); 146 | 147 | self.set_hexpand(true); 148 | self.set_vexpand(true); 149 | self.set_halign(gtk::Align::Fill); 150 | self.set_valign(gtk::Align::Fill); 151 | 152 | imp.gauge_range.set(100.0); 153 | 154 | //let drawing_area = gtk::DrawingArea::new(); 155 | imp.drawing_area.set_hexpand(true); 156 | imp.drawing_area.set_vexpand(true); 157 | imp.drawing_area.set_halign(gtk::Align::Fill); 158 | imp.drawing_area.set_valign(gtk::Align::Fill); 159 | imp.drawing_area 160 | .set_draw_func(clone!(@strong self as this => move |_, context, _, _| { 161 | match this.draw(context) { 162 | Ok(_) => (), 163 | Err(e) => { 164 | error!("{}", e); 165 | }, 166 | } 167 | })); 168 | 169 | self.set_child(Some(&imp.drawing_area)); 170 | 171 | self.start_drawing_thread(); 172 | } 173 | 174 | //update position externally 175 | pub fn set_gauge_position(&self, cents: i32) { 176 | if !self.imp().tx.borrow().as_ref().is_none() { 177 | match self.imp().tx.borrow().as_ref().unwrap().send(cents) { 178 | Ok(_) => (), 179 | Err(e) => error!("{}", e), 180 | } 181 | } 182 | } 183 | 184 | //update from internal channel 185 | fn update_gauge_position(&self, pos: i32) { 186 | let pos = pos as f64; 187 | //debug!("update_gauge_position {}", pos); 188 | if self.imp().gauge_pos.get() != pos { 189 | self.imp().gauge_pos.set(pos); 190 | } 191 | } 192 | 193 | fn retrieve_settings(&self) { 194 | let imp = self.imp(); 195 | 196 | imp.hover_time.set( imp.settings.double("gauge-hang")); 197 | imp.gauge_rest_position.set(imp.settings.int("gauge-rest-position")) 198 | } 199 | 200 | 201 | pub fn start_drawing_thread(&self) { 202 | let imp = self.imp(); 203 | 204 | self.retrieve_settings(); 205 | 206 | let mut end_goal = 0; //where the gauge is try to get to at any moment. changes from input on channel 207 | let mut current_pos = imp.gauge_pos.get() as i32; //current gauge position 208 | let refresh_milli = 8; //thread refresh rate 209 | let hover_amount = (1000.0 * imp.hover_time.get()) as u64; //hover time when no msg received on channel before return to baseline position 210 | let rest_position = imp.gauge_rest_position.get(); //baseline position 211 | let mut hover_time: Option = None; 212 | debug!("hover time {}, rest_position {}", hover_amount, rest_position); 213 | 214 | let glib_sender = imp.sender.borrow().as_ref().unwrap().clone(); 215 | let (tx, rx) = mpsc::channel::(); 216 | 217 | if !imp.tx.borrow().as_ref().is_none() { 218 | debug!("killing previous thread with drop send"); 219 | drop(imp.tx.borrow().as_ref()); //drop should kill previous stream 220 | } 221 | imp.tx.replace(Some(tx)); 222 | 223 | thread::spawn(move || loop { 224 | thread::sleep(Duration::from_millis(refresh_milli)); 225 | 226 | match rx.try_recv() { 227 | Ok(new_goal) => { 228 | end_goal = new_goal; 229 | //hover_time = hover_amount; 230 | if !hover_time.is_none() { 231 | hover_time = None; 232 | } 233 | }, 234 | 235 | Err(mpsc::TryRecvError::Empty) => { 236 | if current_pos == end_goal { 237 | //reached goal 238 | if end_goal == rest_position { 239 | match rx.recv() { //block until new goal 240 | Ok(new_goal) => end_goal = new_goal, 241 | Err(_) => (), 242 | } 243 | } else { //hover on location 244 | if hover_time.is_none() { 245 | hover_time = Some(Instant::now()); 246 | } else { 247 | if hover_time.unwrap().elapsed() > Duration::from_millis(hover_amount) { 248 | end_goal = rest_position; 249 | hover_time = None; 250 | } 251 | } 252 | } 253 | } 254 | }, 255 | 256 | Err(mpsc::TryRecvError::Disconnected) => { 257 | error!("disconnected channel, stream should end"); 258 | break; 259 | }, 260 | 261 | } 262 | 263 | 264 | 265 | if current_pos != end_goal { 266 | if (current_pos - end_goal).abs() < 2 { 267 | current_pos = end_goal 268 | } else if current_pos < end_goal { 269 | current_pos += 1; 270 | } else { 271 | current_pos -= 1; 272 | } 273 | } 274 | 275 | match glib_sender.send(GaugeAction::UpdateGaugePos(current_pos)) { 276 | Ok(_) => (), 277 | Err(e) => { 278 | error!("Gauge draw thread error: {}", e); 279 | } 280 | } 281 | }); 282 | } 283 | 284 | fn draw(&self, context: &cairo::Context) -> Result<(), Box> { 285 | //let bg_color = "#2c3338"; 286 | //let color = self.hex_to_rgb(bg_color); 287 | //context.set_source_rgb(0.0, 0.0, 0.0); 288 | // /context.paint()?; 289 | self.redraw_bg(context)?; 290 | self.redraw_gauge(context)?; 291 | Ok(()) 292 | } 293 | 294 | fn draw_gauge_tic( 295 | &self, 296 | context: &cairo::Context, 297 | gauge_center: (f64, f64), 298 | radius1: f64, 299 | radius2: f64, 300 | angle: f64, 301 | ) -> Result<(), Box> { 302 | context.move_to( 303 | gauge_center.0 + radius1 * angle.sin(), 304 | gauge_center.1 - radius1 * angle.cos(), 305 | ); 306 | context.rel_line_to( 307 | (radius2 - radius1) * angle.sin(), 308 | (radius1 - radius2) * angle.cos(), 309 | ); 310 | context.stroke()?; 311 | Ok(()) 312 | } 313 | 314 | fn hex_to_rgb(&self, hex_string: &str) -> (f64, f64, f64) { 315 | let r = u8::from_str_radix(&hex_string[1..3], 16).unwrap() as f64 / 255.0; 316 | let g = u8::from_str_radix(&hex_string[3..5], 16).unwrap() as f64 / 255.0; 317 | let b = u8::from_str_radix(&hex_string[5..7], 16).unwrap() as f64 / 255.0; 318 | (r, g, b) 319 | } 320 | 321 | fn redraw_bg(&self, context: &cairo::Context) -> Result<(), Box> { 322 | let gauge_gauge_center_y = 0.94; 323 | let gauge_cents_bar_stroke = 0.025; 324 | let gauge_cents_bar_radius = 0.75; 325 | let gauge_cents_bar_major_tic_radius = 0.04; 326 | let gauge_cents_bar_minor_tic_radius = 0.03; 327 | let gauge_cents_bar_major_tic_stroke = 0.03; 328 | let gauge_cents_bar_minor_tic_stroke = 0.01; 329 | let gauge_cents_text_size = 0.09; 330 | let gauge_frequency_bar_stroke = 0.025; 331 | let gauge_frequency_bar_radius = 0.78; 332 | let gauge_frequency_bar_major_tic_radius = 0.04; 333 | let gauge_ok_bar_stroke = 0.07; 334 | let gauge_ok_bar_radius = 0.48; 335 | 336 | let overture_angle = 65.0 * PI / 180.0; 337 | 338 | //colors 339 | 340 | let gauge_frequency_bar_color = "#364da5"; 341 | let gauge_cents_bar_color = "#FFD2C4"; 342 | let gauge_ok_color = "#ABE8FF"; 343 | let gauge_ko_color = "#7486CC"; 344 | 345 | // # gauge_cents_bar_color = "#333355" 346 | // # gauge_frequency_bar_color = "#555533" 347 | // # gauge_ok_color = "#99dd99" 348 | // # gauge_ko_color = "#ddaaaa" 349 | 350 | let width = self.width() as f64; 351 | let height = self.height() as f64; 352 | 353 | let gauge_center = (width / 2.0, height * gauge_gauge_center_y); 354 | 355 | let cents_bar_radius = height * gauge_cents_bar_radius; 356 | let cents_bar_stroke = height * gauge_cents_bar_stroke; 357 | let cents_bar_major_tic_radius = 358 | cents_bar_radius - height * gauge_cents_bar_major_tic_radius; 359 | let cents_bar_minor_tic_radius = 360 | cents_bar_radius - height * gauge_cents_bar_minor_tic_radius; 361 | let cents_bar_major_tic_stroke = height * gauge_cents_bar_major_tic_stroke; 362 | let cents_bar_minor_tic_stroke = height * gauge_cents_bar_minor_tic_stroke; 363 | let cents_text_size = height * gauge_cents_text_size; 364 | let frequency_bar_radius = height * gauge_frequency_bar_radius; 365 | let frequency_bar_major_tic_radius = 366 | frequency_bar_radius + height * gauge_frequency_bar_major_tic_radius; 367 | let frequency_bar_stroke = height * gauge_frequency_bar_stroke; 368 | let ok_bar_radius = height * gauge_ok_bar_radius; 369 | let ok_bar_stroke = height * gauge_ok_bar_stroke; 370 | 371 | context.set_source_rgb(1.0, 1.0, 1.0); 372 | context.save()?; 373 | 374 | // let rect = gdk::Rectangle::new(0, 0, self.width(), self.height()); 375 | // Gdk.cairo_rectangle(context, rect); 376 | context.fill_preserve()?; 377 | context.restore()?; 378 | 379 | context.set_source_rgb(0.0, 0.0, 0.0); 380 | context.stroke()?; 381 | 382 | // #draw ok/ko bar 383 | context.set_line_width(ok_bar_stroke); 384 | context.set_line_cap(cairo::LineCap::Butt); 385 | //context.set_source_rgba(*hex_to_rgba(gauge_ko_color)); 386 | let color = self.hex_to_rgb(gauge_ko_color); 387 | context.set_source_rgb(color.0, color.1, color.2); 388 | context.arc( 389 | gauge_center.0, 390 | gauge_center.1, 391 | ok_bar_radius, 392 | -0.5 * PI - overture_angle, 393 | -0.5 * PI + overture_angle, 394 | ); 395 | context.stroke()?; 396 | //context.set_source_rgba(*hex_to_rgba(gauge_ok_color)); 397 | let color = self.hex_to_rgb(gauge_ok_color); 398 | context.set_source_rgb(color.0, color.1, color.2); 399 | context.arc( 400 | gauge_center.0, 401 | gauge_center.1, 402 | ok_bar_radius, 403 | -0.5 * PI - 0.1 * overture_angle, 404 | -0.5 * PI + 0.1 * overture_angle, 405 | ); 406 | context.stroke()?; 407 | 408 | // #draw cents bar 409 | context.set_line_width(cents_bar_stroke); 410 | //context.set_source_rgba(*hex_to_rgba(gauge_cents_bar_color)); 411 | let color = self.hex_to_rgb(gauge_cents_bar_color); 412 | context.set_source_rgb(color.0, color.1, color.2); 413 | 414 | context.arc( 415 | gauge_center.0, 416 | gauge_center.1, 417 | cents_bar_radius, 418 | -0.5 * PI - 1.05 * overture_angle, 419 | -0.5 * PI + 1.05 * overture_angle, 420 | ); 421 | context.stroke()?; 422 | 423 | // #cent tics 424 | let gauge_range = 100.0; 425 | let max_minor_divisions = 20.0; 426 | let cents_per_minor_division: f64 = gauge_range / max_minor_divisions; 427 | let base = f64::powf(10.0, cents_per_minor_division.log10().floor()); 428 | let mut normalized_cents_per_division = cents_per_minor_division / base; 429 | if normalized_cents_per_division >= 6.0 { 430 | normalized_cents_per_division = 10.0; 431 | } else if normalized_cents_per_division >= 2.5 { 432 | normalized_cents_per_division = 5.0; 433 | } else if normalized_cents_per_division >= 1.2 { 434 | normalized_cents_per_division = 2.0; 435 | } else { 436 | normalized_cents_per_division = 1.0; 437 | } 438 | 439 | let cents_per_minor_division = normalized_cents_per_division * base; 440 | let cents_per_major_division = 5.0 * cents_per_minor_division; 441 | 442 | // #minor tics 443 | context.set_line_width(cents_bar_minor_tic_stroke); 444 | let max_index = (0.5 * gauge_range / cents_per_minor_division).floor() as i32; 445 | let angle_step = 2.0 * overture_angle * cents_per_minor_division / gauge_range; 446 | 447 | for i in -max_index..max_index + 1 { 448 | let angle = i as f64 * angle_step; 449 | self.draw_gauge_tic( 450 | context, 451 | gauge_center, 452 | cents_bar_minor_tic_radius, 453 | cents_bar_radius, 454 | angle, 455 | )?; 456 | } 457 | 458 | // #major tics 459 | let max_index = (0.5 * gauge_range / cents_per_major_division).floor() as i32; 460 | let angle_step = 2.0 * overture_angle * cents_per_major_division / gauge_range; 461 | context.set_line_width(cents_bar_major_tic_stroke); 462 | 463 | for i in -max_index..max_index + 1 { 464 | let angle = i as f64 * angle_step; 465 | self.draw_gauge_tic( 466 | context, 467 | gauge_center, 468 | cents_bar_major_tic_radius, 469 | cents_bar_radius, 470 | angle, 471 | )?; 472 | } 473 | 474 | // #cents text 475 | context.set_line_width(1.0); 476 | let mut old_angle = 0.0; 477 | 478 | context.save()?; 479 | 480 | context.select_font_face( 481 | "Cantarell", 482 | cairo::FontSlant::Normal, 483 | cairo::FontWeight::Normal, 484 | ); 485 | context.set_font_size(cents_text_size); 486 | let te = context.text_extents("cent")?; 487 | context.move_to( 488 | gauge_center.0 - te.width() / 2.0 - te.x_bearing(), 489 | gauge_center.1 - 0.81 * cents_bar_major_tic_radius - te.height() / 2.0 - te.y_bearing(), 490 | ); 491 | context.show_text("cent")?; 492 | 493 | context.translate(gauge_center.0, gauge_center.1); 494 | 495 | for i in -max_index..max_index + 1 { 496 | let angle = i as f64 * angle_step; 497 | context.rotate(angle - old_angle); 498 | let cents = i * cents_per_major_division as i32; 499 | if i > 0 { 500 | let text = &format!("+{}", cents); 501 | let te = context.text_extents(text)?; 502 | context.move_to( 503 | -te.width() / 2.0 - te.x_bearing(), 504 | -0.92 * cents_bar_major_tic_radius - te.height() / 2.0 - te.y_bearing(), 505 | ); 506 | context.show_text(text)?; 507 | old_angle = angle; 508 | } else { 509 | let text = &format!("{}", cents); 510 | let te = context.text_extents(text)?; 511 | context.move_to( 512 | -te.width() / 2.0 - te.x_bearing(), 513 | -0.92 * cents_bar_major_tic_radius - te.height() / 2.0 - te.y_bearing(), 514 | ); 515 | context.show_text(text)?; 516 | old_angle = angle; 517 | } 518 | } 519 | 520 | context.restore()?; 521 | context.stroke()?; 522 | 523 | // #draw frequency bar 524 | context.set_line_width(frequency_bar_stroke); 525 | // context.set_source_rgba(*hex_to_rgba(gauge_frequency_bar_color))' 526 | let color = self.hex_to_rgb(gauge_frequency_bar_color); 527 | context.set_source_rgb(color.0, color.1, color.2); 528 | context.arc( 529 | gauge_center.0, 530 | gauge_center.1, 531 | frequency_bar_radius, 532 | -0.5 * PI - 1.05 * overture_angle, 533 | -0.5 * PI + 1.05 * overture_angle, 534 | ); 535 | context.stroke()?; 536 | 537 | // #frequency tics 538 | self.draw_gauge_tic( 539 | context, 540 | gauge_center, 541 | frequency_bar_major_tic_radius, 542 | frequency_bar_radius, 543 | 0.0, 544 | )?; 545 | 546 | Ok(()) 547 | } 548 | 549 | fn redraw_gauge(&self, context: &cairo::Context) -> Result<(), Box> { 550 | let gauge_size_x = self.width(); 551 | let gauge_size_y = self.height(); 552 | 553 | // #normalized dimensions 554 | let gauge_gauge_center_y = 0.94; 555 | let gauge_gauge_length = 0.85; 556 | let gauge_gauge_length_back = 0.08; 557 | let gauge_gauge_centerradius = 0.045; 558 | let gauge_gaugestroke = 0.012; 559 | let gauge_gauge_shadow_offset_x = 0.015; 560 | let gauge_gauge_shadow_offset_y = 0.01; 561 | 562 | let overture_angle = 65.0 * PI / 180.0; 563 | 564 | // #colors 565 | let gauge_gauge_color = "#FA9457"; 566 | let gauge_gauge_shadow_color = "#7F7F7F"; 567 | 568 | // let gauge_gauge_color = "#aa3333"; 569 | // let gauge_gauge_shadow_color = "#7F7F7F"; 570 | 571 | let width = gauge_size_x as f64; 572 | let height = gauge_size_y as f64; 573 | 574 | // #dimensions applied to the current size 575 | let gauge_center = (width / 2.0, height * gauge_gauge_center_y); 576 | 577 | let gauge_shadow_center = ( 578 | gauge_center.0 + height * gauge_gauge_shadow_offset_x, 579 | gauge_center.1 + height * gauge_gauge_shadow_offset_y, 580 | ); 581 | let gauge_length = height * gauge_gauge_length; 582 | let gauge_length_back = height * gauge_gauge_length_back; 583 | let gauge_centerradius = height * gauge_gauge_centerradius; 584 | let gaugestroke = height * gauge_gaugestroke; 585 | 586 | let normalized_error = self.imp().gauge_pos.get() / self.imp().gauge_range.get(); 587 | let angle = 2.0 * normalized_error * overture_angle; 588 | context.set_line_width(gaugestroke); 589 | context.set_line_cap(cairo::LineCap::Butt); 590 | 591 | //SHADOW GAUGE 592 | let color = self.hex_to_rgb(gauge_gauge_shadow_color); 593 | context.set_source_rgba(color.0, color.1, color.2, 0.25); 594 | 595 | self.draw_gauge_tic( 596 | context, 597 | gauge_shadow_center, 598 | -gauge_length_back, 599 | -0.99 * gauge_centerradius, 600 | angle, 601 | )?; 602 | self.draw_gauge_tic( 603 | context, 604 | gauge_shadow_center, 605 | 0.99 * gauge_centerradius, 606 | gauge_length, 607 | angle, 608 | )?; 609 | context.arc( 610 | gauge_shadow_center.0, 611 | gauge_shadow_center.1, 612 | gauge_centerradius, 613 | 0.0, 614 | 2.0 * PI, 615 | ); 616 | context.fill()?; 617 | 618 | //MAIN GAUGE 619 | let color = self.hex_to_rgb(gauge_gauge_color); 620 | context.set_source_rgb(color.0, color.1, color.2); 621 | 622 | self.draw_gauge_tic( 623 | context, 624 | gauge_center, 625 | -gauge_length_back, 626 | gauge_length, 627 | angle, 628 | )?; 629 | context.arc( 630 | gauge_center.0, 631 | gauge_center.1, 632 | gauge_centerradius, 633 | 0.0, 634 | 2.0 * PI, 635 | ); 636 | context.fill()?; 637 | 638 | Ok(()) 639 | } 640 | } 641 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.20" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.69" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" 19 | 20 | [[package]] 21 | name = "atty" 22 | version = "0.2.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 25 | dependencies = [ 26 | "hermit-abi", 27 | "libc", 28 | "winapi", 29 | ] 30 | 31 | [[package]] 32 | name = "aubio-rs" 33 | version = "0.2.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "9189fd9d8af82083a442e8b73ce12e602a6719f7360cf53f0bb9cb9e8c8d5a87" 36 | dependencies = [ 37 | "aubio-sys", 38 | ] 39 | 40 | [[package]] 41 | name = "aubio-sys" 42 | version = "0.2.1" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "99ef2dfeaceccd0b8a6d72203409acc927d9eebc8180c5756099549c9f8f20a8" 45 | dependencies = [ 46 | "cc", 47 | ] 48 | 49 | [[package]] 50 | name = "autocfg" 51 | version = "1.1.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 54 | 55 | [[package]] 56 | name = "bitflags" 57 | version = "0.7.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 60 | 61 | [[package]] 62 | name = "bitflags" 63 | version = "1.3.2" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 66 | 67 | [[package]] 68 | name = "block" 69 | version = "0.1.6" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 72 | 73 | [[package]] 74 | name = "cairo-rs" 75 | version = "0.16.7" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "f3125b15ec28b84c238f6f476c6034016a5f6cc0221cb514ca46c532139fc97d" 78 | dependencies = [ 79 | "bitflags 1.3.2", 80 | "cairo-sys-rs", 81 | "glib", 82 | "libc", 83 | "once_cell", 84 | "thiserror", 85 | ] 86 | 87 | [[package]] 88 | name = "cairo-sys-rs" 89 | version = "0.16.3" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "7c48f4af05fabdcfa9658178e1326efa061853f040ce7d72e33af6885196f421" 92 | dependencies = [ 93 | "glib-sys", 94 | "libc", 95 | "system-deps", 96 | ] 97 | 98 | [[package]] 99 | name = "cc" 100 | version = "1.0.79" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 103 | dependencies = [ 104 | "jobserver", 105 | ] 106 | 107 | [[package]] 108 | name = "cfg-expr" 109 | version = "0.11.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" 112 | dependencies = [ 113 | "smallvec", 114 | ] 115 | 116 | [[package]] 117 | name = "cfg-if" 118 | version = "1.0.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 121 | 122 | [[package]] 123 | name = "chromatic" 124 | version = "0.1.0" 125 | dependencies = [ 126 | "aubio-rs", 127 | "fuzzy-matcher", 128 | "gettext-rs", 129 | "gtk4", 130 | "libadwaita", 131 | "log", 132 | "once_cell", 133 | "pitch_calc", 134 | "portaudio", 135 | "pretty_env_logger", 136 | "pulsectl-rs", 137 | "regex", 138 | ] 139 | 140 | [[package]] 141 | name = "env_logger" 142 | version = "0.7.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 145 | dependencies = [ 146 | "atty", 147 | "humantime", 148 | "log", 149 | "regex", 150 | "termcolor", 151 | ] 152 | 153 | [[package]] 154 | name = "field-offset" 155 | version = "0.3.4" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" 158 | dependencies = [ 159 | "memoffset", 160 | "rustc_version", 161 | ] 162 | 163 | [[package]] 164 | name = "fuchsia-cprng" 165 | version = "0.1.1" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 168 | 169 | [[package]] 170 | name = "futures-channel" 171 | version = "0.3.26" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" 174 | dependencies = [ 175 | "futures-core", 176 | ] 177 | 178 | [[package]] 179 | name = "futures-core" 180 | version = "0.3.26" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" 183 | 184 | [[package]] 185 | name = "futures-executor" 186 | version = "0.3.26" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" 189 | dependencies = [ 190 | "futures-core", 191 | "futures-task", 192 | "futures-util", 193 | ] 194 | 195 | [[package]] 196 | name = "futures-io" 197 | version = "0.3.26" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" 200 | 201 | [[package]] 202 | name = "futures-macro" 203 | version = "0.3.26" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" 206 | dependencies = [ 207 | "proc-macro2", 208 | "quote", 209 | "syn", 210 | ] 211 | 212 | [[package]] 213 | name = "futures-task" 214 | version = "0.3.26" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" 217 | 218 | [[package]] 219 | name = "futures-util" 220 | version = "0.3.26" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" 223 | dependencies = [ 224 | "futures-core", 225 | "futures-macro", 226 | "futures-task", 227 | "pin-project-lite", 228 | "pin-utils", 229 | "slab", 230 | ] 231 | 232 | [[package]] 233 | name = "fuzzy-matcher" 234 | version = "0.3.7" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" 237 | dependencies = [ 238 | "thread_local", 239 | ] 240 | 241 | [[package]] 242 | name = "gdk-pixbuf" 243 | version = "0.16.7" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "c3578c60dee9d029ad86593ed88cb40f35c1b83360e12498d055022385dd9a05" 246 | dependencies = [ 247 | "bitflags 1.3.2", 248 | "gdk-pixbuf-sys", 249 | "gio", 250 | "glib", 251 | "libc", 252 | ] 253 | 254 | [[package]] 255 | name = "gdk-pixbuf-sys" 256 | version = "0.16.3" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "3092cf797a5f1210479ea38070d9ae8a5b8e9f8f1be9f32f4643c529c7d70016" 259 | dependencies = [ 260 | "gio-sys", 261 | "glib-sys", 262 | "gobject-sys", 263 | "libc", 264 | "system-deps", 265 | ] 266 | 267 | [[package]] 268 | name = "gdk4" 269 | version = "0.5.5" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "bb2181330ebf9d091f8ea7fed6877f7adc92114128592e1fdaeb1da28e0d01e9" 272 | dependencies = [ 273 | "bitflags 1.3.2", 274 | "cairo-rs", 275 | "gdk-pixbuf", 276 | "gdk4-sys", 277 | "gio", 278 | "glib", 279 | "libc", 280 | "pango", 281 | ] 282 | 283 | [[package]] 284 | name = "gdk4-sys" 285 | version = "0.5.5" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "de55cb49432901fe2b3534177fa06844665b9b0911d85d8601a8d8b88b7791db" 288 | dependencies = [ 289 | "cairo-sys-rs", 290 | "gdk-pixbuf-sys", 291 | "gio-sys", 292 | "glib-sys", 293 | "gobject-sys", 294 | "libc", 295 | "pango-sys", 296 | "pkg-config", 297 | "system-deps", 298 | ] 299 | 300 | [[package]] 301 | name = "gettext-rs" 302 | version = "0.7.0" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "e49ea8a8fad198aaa1f9655a2524b64b70eb06b2f3ff37da407566c93054f364" 305 | dependencies = [ 306 | "gettext-sys", 307 | "locale_config", 308 | ] 309 | 310 | [[package]] 311 | name = "gettext-sys" 312 | version = "0.21.3" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "c63ce2e00f56a206778276704bbe38564c8695249fdc8f354b4ef71c57c3839d" 315 | dependencies = [ 316 | "cc", 317 | "temp-dir", 318 | ] 319 | 320 | [[package]] 321 | name = "gio" 322 | version = "0.16.7" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "2a1c84b4534a290a29160ef5c6eff2a9c95833111472e824fc5cb78b513dd092" 325 | dependencies = [ 326 | "bitflags 1.3.2", 327 | "futures-channel", 328 | "futures-core", 329 | "futures-io", 330 | "futures-util", 331 | "gio-sys", 332 | "glib", 333 | "libc", 334 | "once_cell", 335 | "pin-project-lite", 336 | "smallvec", 337 | "thiserror", 338 | ] 339 | 340 | [[package]] 341 | name = "gio-sys" 342 | version = "0.16.3" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229" 345 | dependencies = [ 346 | "glib-sys", 347 | "gobject-sys", 348 | "libc", 349 | "system-deps", 350 | "winapi", 351 | ] 352 | 353 | [[package]] 354 | name = "glib" 355 | version = "0.16.7" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "ddd4df61a866ed7259d6189b8bcb1464989a77f1d85d25d002279bbe9dd38b2f" 358 | dependencies = [ 359 | "bitflags 1.3.2", 360 | "futures-channel", 361 | "futures-core", 362 | "futures-executor", 363 | "futures-task", 364 | "futures-util", 365 | "gio-sys", 366 | "glib-macros", 367 | "glib-sys", 368 | "gobject-sys", 369 | "libc", 370 | "once_cell", 371 | "smallvec", 372 | "thiserror", 373 | ] 374 | 375 | [[package]] 376 | name = "glib-macros" 377 | version = "0.16.3" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "e084807350b01348b6d9dbabb724d1a0bb987f47a2c85de200e98e12e30733bf" 380 | dependencies = [ 381 | "anyhow", 382 | "heck", 383 | "proc-macro-crate", 384 | "proc-macro-error", 385 | "proc-macro2", 386 | "quote", 387 | "syn", 388 | ] 389 | 390 | [[package]] 391 | name = "glib-sys" 392 | version = "0.16.3" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65" 395 | dependencies = [ 396 | "libc", 397 | "system-deps", 398 | ] 399 | 400 | [[package]] 401 | name = "gobject-sys" 402 | version = "0.16.3" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1" 405 | dependencies = [ 406 | "glib-sys", 407 | "libc", 408 | "system-deps", 409 | ] 410 | 411 | [[package]] 412 | name = "graphene-rs" 413 | version = "0.16.3" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "95ecb4d347e6d09820df3bdfd89a74a8eec07753a06bb92a3aac3ad31d04447b" 416 | dependencies = [ 417 | "glib", 418 | "graphene-sys", 419 | "libc", 420 | ] 421 | 422 | [[package]] 423 | name = "graphene-sys" 424 | version = "0.16.3" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "b9aa82337d3972b4eafdea71e607c23f47be6f27f749aab613f1ad8ddbe6dcd6" 427 | dependencies = [ 428 | "glib-sys", 429 | "libc", 430 | "pkg-config", 431 | "system-deps", 432 | ] 433 | 434 | [[package]] 435 | name = "gsk4" 436 | version = "0.5.5" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "591239f5c52ca803b222124ac9c47f230cd180cee9b114c4d672e4a94b74f491" 439 | dependencies = [ 440 | "bitflags 1.3.2", 441 | "cairo-rs", 442 | "gdk4", 443 | "glib", 444 | "graphene-rs", 445 | "gsk4-sys", 446 | "libc", 447 | "pango", 448 | ] 449 | 450 | [[package]] 451 | name = "gsk4-sys" 452 | version = "0.5.5" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "195a63f0be42529f98c3eb3bae0decfd0428ba2cc683b3e20ced88f340904ec5" 455 | dependencies = [ 456 | "cairo-sys-rs", 457 | "gdk4-sys", 458 | "glib-sys", 459 | "gobject-sys", 460 | "graphene-sys", 461 | "libc", 462 | "pango-sys", 463 | "system-deps", 464 | ] 465 | 466 | [[package]] 467 | name = "gtk4" 468 | version = "0.5.5" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "fd89dba65def483a233dc4fdd3f3dab01576e3d83f80f6c9303ebe421661855e" 471 | dependencies = [ 472 | "bitflags 1.3.2", 473 | "cairo-rs", 474 | "field-offset", 475 | "futures-channel", 476 | "gdk-pixbuf", 477 | "gdk4", 478 | "gio", 479 | "glib", 480 | "graphene-rs", 481 | "gsk4", 482 | "gtk4-macros", 483 | "gtk4-sys", 484 | "libc", 485 | "once_cell", 486 | "pango", 487 | ] 488 | 489 | [[package]] 490 | name = "gtk4-macros" 491 | version = "0.5.5" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "832687a415d9d8bc11fe9c17dda1bf13ee262c41b995dd4df1d1cce33cead405" 494 | dependencies = [ 495 | "anyhow", 496 | "proc-macro-crate", 497 | "proc-macro-error", 498 | "proc-macro2", 499 | "quote", 500 | "syn", 501 | ] 502 | 503 | [[package]] 504 | name = "gtk4-sys" 505 | version = "0.5.5" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "e370564e3fdacff7cffc99f7366b6a4689feb44e819d3ccee598a9a215b71605" 508 | dependencies = [ 509 | "cairo-sys-rs", 510 | "gdk-pixbuf-sys", 511 | "gdk4-sys", 512 | "gio-sys", 513 | "glib-sys", 514 | "gobject-sys", 515 | "graphene-sys", 516 | "gsk4-sys", 517 | "libc", 518 | "pango-sys", 519 | "system-deps", 520 | ] 521 | 522 | [[package]] 523 | name = "hashbrown" 524 | version = "0.12.3" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 527 | 528 | [[package]] 529 | name = "heck" 530 | version = "0.4.1" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 533 | 534 | [[package]] 535 | name = "hermit-abi" 536 | version = "0.1.19" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 539 | dependencies = [ 540 | "libc", 541 | ] 542 | 543 | [[package]] 544 | name = "humantime" 545 | version = "1.3.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 548 | dependencies = [ 549 | "quick-error", 550 | ] 551 | 552 | [[package]] 553 | name = "indexmap" 554 | version = "1.9.2" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" 557 | dependencies = [ 558 | "autocfg", 559 | "hashbrown", 560 | ] 561 | 562 | [[package]] 563 | name = "jobserver" 564 | version = "0.1.26" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" 567 | dependencies = [ 568 | "libc", 569 | ] 570 | 571 | [[package]] 572 | name = "lazy_static" 573 | version = "1.4.0" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 576 | 577 | [[package]] 578 | name = "libadwaita" 579 | version = "0.2.1" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "9dfa0722d4f1724f661cbf668c273c5926296ca411ed3814e206f8fd082b6c48" 582 | dependencies = [ 583 | "bitflags 1.3.2", 584 | "futures-channel", 585 | "gdk-pixbuf", 586 | "gdk4", 587 | "gio", 588 | "glib", 589 | "gtk4", 590 | "libadwaita-sys", 591 | "libc", 592 | "once_cell", 593 | "pango", 594 | ] 595 | 596 | [[package]] 597 | name = "libadwaita-sys" 598 | version = "0.2.1" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "de902982372b454a0081d7fd9dd567b37b73ae29c8f6da1820374d345fd95d5b" 601 | dependencies = [ 602 | "gdk4-sys", 603 | "gio-sys", 604 | "glib-sys", 605 | "gobject-sys", 606 | "gtk4-sys", 607 | "libc", 608 | "pango-sys", 609 | "system-deps", 610 | ] 611 | 612 | [[package]] 613 | name = "libc" 614 | version = "0.2.139" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 617 | 618 | [[package]] 619 | name = "libpulse-binding" 620 | version = "2.27.1" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "1745b20bfc194ac12ef828f144f0ec2d4a7fe993281fa3567a0bd4969aee6890" 623 | dependencies = [ 624 | "bitflags 1.3.2", 625 | "libc", 626 | "libpulse-sys", 627 | "num-derive", 628 | "num-traits", 629 | "winapi", 630 | ] 631 | 632 | [[package]] 633 | name = "libpulse-sys" 634 | version = "1.20.1" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "2191e6880818d1df4cf72eac8e91dce7a5a52ba0da4b2a5cdafabc22b937eadb" 637 | dependencies = [ 638 | "libc", 639 | "num-derive", 640 | "num-traits", 641 | "pkg-config", 642 | "winapi", 643 | ] 644 | 645 | [[package]] 646 | name = "locale_config" 647 | version = "0.3.0" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "08d2c35b16f4483f6c26f0e4e9550717a2f6575bcd6f12a53ff0c490a94a6934" 650 | dependencies = [ 651 | "lazy_static", 652 | "objc", 653 | "objc-foundation", 654 | "regex", 655 | "winapi", 656 | ] 657 | 658 | [[package]] 659 | name = "log" 660 | version = "0.4.17" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 663 | dependencies = [ 664 | "cfg-if", 665 | ] 666 | 667 | [[package]] 668 | name = "malloc_buf" 669 | version = "0.0.6" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 672 | dependencies = [ 673 | "libc", 674 | ] 675 | 676 | [[package]] 677 | name = "memchr" 678 | version = "2.5.0" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 681 | 682 | [[package]] 683 | name = "memoffset" 684 | version = "0.6.5" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 687 | dependencies = [ 688 | "autocfg", 689 | ] 690 | 691 | [[package]] 692 | name = "num" 693 | version = "0.1.42" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" 696 | dependencies = [ 697 | "num-bigint", 698 | "num-complex", 699 | "num-integer", 700 | "num-iter", 701 | "num-rational", 702 | "num-traits", 703 | ] 704 | 705 | [[package]] 706 | name = "num-bigint" 707 | version = "0.1.44" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1" 710 | dependencies = [ 711 | "num-integer", 712 | "num-traits", 713 | "rand 0.4.6", 714 | "rustc-serialize", 715 | ] 716 | 717 | [[package]] 718 | name = "num-complex" 719 | version = "0.1.43" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656" 722 | dependencies = [ 723 | "num-traits", 724 | "rustc-serialize", 725 | ] 726 | 727 | [[package]] 728 | name = "num-derive" 729 | version = "0.3.3" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" 732 | dependencies = [ 733 | "proc-macro2", 734 | "quote", 735 | "syn", 736 | ] 737 | 738 | [[package]] 739 | name = "num-integer" 740 | version = "0.1.45" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 743 | dependencies = [ 744 | "autocfg", 745 | "num-traits", 746 | ] 747 | 748 | [[package]] 749 | name = "num-iter" 750 | version = "0.1.43" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" 753 | dependencies = [ 754 | "autocfg", 755 | "num-integer", 756 | "num-traits", 757 | ] 758 | 759 | [[package]] 760 | name = "num-rational" 761 | version = "0.1.42" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" 764 | dependencies = [ 765 | "num-bigint", 766 | "num-integer", 767 | "num-traits", 768 | "rustc-serialize", 769 | ] 770 | 771 | [[package]] 772 | name = "num-traits" 773 | version = "0.2.15" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 776 | dependencies = [ 777 | "autocfg", 778 | ] 779 | 780 | [[package]] 781 | name = "objc" 782 | version = "0.2.7" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 785 | dependencies = [ 786 | "malloc_buf", 787 | ] 788 | 789 | [[package]] 790 | name = "objc-foundation" 791 | version = "0.1.1" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" 794 | dependencies = [ 795 | "block", 796 | "objc", 797 | "objc_id", 798 | ] 799 | 800 | [[package]] 801 | name = "objc_id" 802 | version = "0.1.1" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" 805 | dependencies = [ 806 | "objc", 807 | ] 808 | 809 | [[package]] 810 | name = "once_cell" 811 | version = "1.17.1" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" 814 | 815 | [[package]] 816 | name = "pango" 817 | version = "0.16.5" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94" 820 | dependencies = [ 821 | "bitflags 1.3.2", 822 | "gio", 823 | "glib", 824 | "libc", 825 | "once_cell", 826 | "pango-sys", 827 | ] 828 | 829 | [[package]] 830 | name = "pango-sys" 831 | version = "0.16.3" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "9e134909a9a293e04d2cc31928aa95679c5e4df954d0b85483159bd20d8f047f" 834 | dependencies = [ 835 | "glib-sys", 836 | "gobject-sys", 837 | "libc", 838 | "system-deps", 839 | ] 840 | 841 | [[package]] 842 | name = "pest" 843 | version = "2.5.5" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" 846 | dependencies = [ 847 | "thiserror", 848 | "ucd-trie", 849 | ] 850 | 851 | [[package]] 852 | name = "pin-project-lite" 853 | version = "0.2.9" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 856 | 857 | [[package]] 858 | name = "pin-utils" 859 | version = "0.1.0" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 862 | 863 | [[package]] 864 | name = "pitch_calc" 865 | version = "0.12.0" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "387005d7ff9e9970f954ffd33e258f9b755d5f27f11a4b57df3e5c6eab5a46f8" 868 | dependencies = [ 869 | "num", 870 | "rand 0.3.23", 871 | ] 872 | 873 | [[package]] 874 | name = "pkg-config" 875 | version = "0.3.26" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 878 | 879 | [[package]] 880 | name = "portaudio" 881 | version = "0.7.0" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "0d289315f6155a7608b6d8757786c79ed2243afeab8a5eda8989effda3fdc5c3" 884 | dependencies = [ 885 | "bitflags 0.7.0", 886 | "libc", 887 | "num", 888 | "pkg-config", 889 | ] 890 | 891 | [[package]] 892 | name = "pretty_env_logger" 893 | version = "0.4.0" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" 896 | dependencies = [ 897 | "env_logger", 898 | "log", 899 | ] 900 | 901 | [[package]] 902 | name = "proc-macro-crate" 903 | version = "1.3.1" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" 906 | dependencies = [ 907 | "once_cell", 908 | "toml_edit", 909 | ] 910 | 911 | [[package]] 912 | name = "proc-macro-error" 913 | version = "1.0.4" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 916 | dependencies = [ 917 | "proc-macro-error-attr", 918 | "proc-macro2", 919 | "quote", 920 | "syn", 921 | "version_check", 922 | ] 923 | 924 | [[package]] 925 | name = "proc-macro-error-attr" 926 | version = "1.0.4" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 929 | dependencies = [ 930 | "proc-macro2", 931 | "quote", 932 | "version_check", 933 | ] 934 | 935 | [[package]] 936 | name = "proc-macro2" 937 | version = "1.0.51" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" 940 | dependencies = [ 941 | "unicode-ident", 942 | ] 943 | 944 | [[package]] 945 | name = "pulsectl-rs" 946 | version = "0.3.2" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "06a988bceed1981b2c5fc4a3da0e4e073fdaff8e6bd022b089f54bc573dc3cfc" 949 | dependencies = [ 950 | "libpulse-binding", 951 | ] 952 | 953 | [[package]] 954 | name = "quick-error" 955 | version = "1.2.3" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 958 | 959 | [[package]] 960 | name = "quote" 961 | version = "1.0.23" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 964 | dependencies = [ 965 | "proc-macro2", 966 | ] 967 | 968 | [[package]] 969 | name = "rand" 970 | version = "0.3.23" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" 973 | dependencies = [ 974 | "libc", 975 | "rand 0.4.6", 976 | ] 977 | 978 | [[package]] 979 | name = "rand" 980 | version = "0.4.6" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 983 | dependencies = [ 984 | "fuchsia-cprng", 985 | "libc", 986 | "rand_core 0.3.1", 987 | "rdrand", 988 | "winapi", 989 | ] 990 | 991 | [[package]] 992 | name = "rand_core" 993 | version = "0.3.1" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 996 | dependencies = [ 997 | "rand_core 0.4.2", 998 | ] 999 | 1000 | [[package]] 1001 | name = "rand_core" 1002 | version = "0.4.2" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 1005 | 1006 | [[package]] 1007 | name = "rdrand" 1008 | version = "0.4.0" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 1011 | dependencies = [ 1012 | "rand_core 0.3.1", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "regex" 1017 | version = "1.7.1" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" 1020 | dependencies = [ 1021 | "aho-corasick", 1022 | "memchr", 1023 | "regex-syntax", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "regex-syntax" 1028 | version = "0.6.28" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 1031 | 1032 | [[package]] 1033 | name = "rustc-serialize" 1034 | version = "0.3.24" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 1037 | 1038 | [[package]] 1039 | name = "rustc_version" 1040 | version = "0.3.3" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" 1043 | dependencies = [ 1044 | "semver", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "semver" 1049 | version = "0.11.0" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" 1052 | dependencies = [ 1053 | "semver-parser", 1054 | ] 1055 | 1056 | [[package]] 1057 | name = "semver-parser" 1058 | version = "0.10.2" 1059 | source = "registry+https://github.com/rust-lang/crates.io-index" 1060 | checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" 1061 | dependencies = [ 1062 | "pest", 1063 | ] 1064 | 1065 | [[package]] 1066 | name = "serde" 1067 | version = "1.0.152" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 1070 | 1071 | [[package]] 1072 | name = "slab" 1073 | version = "0.4.8" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" 1076 | dependencies = [ 1077 | "autocfg", 1078 | ] 1079 | 1080 | [[package]] 1081 | name = "smallvec" 1082 | version = "1.10.0" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 1085 | 1086 | [[package]] 1087 | name = "syn" 1088 | version = "1.0.109" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1091 | dependencies = [ 1092 | "proc-macro2", 1093 | "quote", 1094 | "unicode-ident", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "system-deps" 1099 | version = "6.0.3" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" 1102 | dependencies = [ 1103 | "cfg-expr", 1104 | "heck", 1105 | "pkg-config", 1106 | "toml", 1107 | "version-compare", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "temp-dir" 1112 | version = "0.1.11" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "af547b166dd1ea4b472165569fc456cfb6818116f854690b0ff205e636523dab" 1115 | 1116 | [[package]] 1117 | name = "termcolor" 1118 | version = "1.2.0" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 1121 | dependencies = [ 1122 | "winapi-util", 1123 | ] 1124 | 1125 | [[package]] 1126 | name = "thiserror" 1127 | version = "1.0.38" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" 1130 | dependencies = [ 1131 | "thiserror-impl", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "thiserror-impl" 1136 | version = "1.0.38" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" 1139 | dependencies = [ 1140 | "proc-macro2", 1141 | "quote", 1142 | "syn", 1143 | ] 1144 | 1145 | [[package]] 1146 | name = "thread_local" 1147 | version = "1.1.7" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" 1150 | dependencies = [ 1151 | "cfg-if", 1152 | "once_cell", 1153 | ] 1154 | 1155 | [[package]] 1156 | name = "toml" 1157 | version = "0.5.11" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 1160 | dependencies = [ 1161 | "serde", 1162 | ] 1163 | 1164 | [[package]] 1165 | name = "toml_datetime" 1166 | version = "0.6.1" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" 1169 | 1170 | [[package]] 1171 | name = "toml_edit" 1172 | version = "0.19.4" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" 1175 | dependencies = [ 1176 | "indexmap", 1177 | "toml_datetime", 1178 | "winnow", 1179 | ] 1180 | 1181 | [[package]] 1182 | name = "ucd-trie" 1183 | version = "0.1.5" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" 1186 | 1187 | [[package]] 1188 | name = "unicode-ident" 1189 | version = "1.0.6" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 1192 | 1193 | [[package]] 1194 | name = "version-compare" 1195 | version = "0.1.1" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" 1198 | 1199 | [[package]] 1200 | name = "version_check" 1201 | version = "0.9.4" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1204 | 1205 | [[package]] 1206 | name = "winapi" 1207 | version = "0.3.9" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1210 | dependencies = [ 1211 | "winapi-i686-pc-windows-gnu", 1212 | "winapi-x86_64-pc-windows-gnu", 1213 | ] 1214 | 1215 | [[package]] 1216 | name = "winapi-i686-pc-windows-gnu" 1217 | version = "0.4.0" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1220 | 1221 | [[package]] 1222 | name = "winapi-util" 1223 | version = "0.1.5" 1224 | source = "registry+https://github.com/rust-lang/crates.io-index" 1225 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1226 | dependencies = [ 1227 | "winapi", 1228 | ] 1229 | 1230 | [[package]] 1231 | name = "winapi-x86_64-pc-windows-gnu" 1232 | version = "0.4.0" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1235 | 1236 | [[package]] 1237 | name = "winnow" 1238 | version = "0.3.3" 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" 1240 | checksum = "faf09497b8f8b5ac5d3bb4d05c0a99be20f26fd3d5f2db7b0716e946d5103658" 1241 | dependencies = [ 1242 | "memchr", 1243 | ] 1244 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | 676 | --------------------------------------------------------------------------------