first
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/rossnome.iml" filepath="$PROJECT_DIR$/.idea/rossnome.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+11
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="EMPTY_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+4162
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "rossnome"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eframe = "0.32.3"
|
||||||
|
tokio = { version = "1.47.1", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
use eframe::egui;
|
||||||
|
use eframe::egui::{Color32, Context, Id, Margin, PointerButton, Pos2, Rangef, Response, Stroke, TopBottomPanel, Ui, Vec2, Widget};
|
||||||
|
use crate::nome::LOOP_LENGTH;
|
||||||
|
|
||||||
|
pub enum NoteType {
|
||||||
|
Sine,
|
||||||
|
Triangle,
|
||||||
|
Sawtooth,
|
||||||
|
Square,
|
||||||
|
Noise
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoteType {
|
||||||
|
fn color(&self) -> Color32 {
|
||||||
|
match self {
|
||||||
|
NoteType::Sine => Color32::from_rgb(3, 211, 252),
|
||||||
|
NoteType::Triangle => Color32::from_rgb(252, 202, 3),
|
||||||
|
NoteType::Sawtooth => Color32::from_rgb(252, 18, 61),
|
||||||
|
NoteType::Square => Color32::from_rgb(4, 219, 51),
|
||||||
|
NoteType::Noise => Color32::from_rgb(120, 120, 120),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Grid {
|
||||||
|
note_type: NoteType,
|
||||||
|
volume: f32,
|
||||||
|
notes: Vec<bool>,
|
||||||
|
pub id: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grid {
|
||||||
|
pub fn for_note_type(note_type: NoteType, counter: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
note_type,
|
||||||
|
volume: 1.0,
|
||||||
|
notes: vec![false; (LOOP_LENGTH * LOOP_LENGTH) as usize],
|
||||||
|
id: format!("Track {}", counter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(&mut self, ctx: &Context, cursor: f32) {
|
||||||
|
let id = Id::from(self.id.clone());
|
||||||
|
let win = egui::Window::new(&self.id).resizable(false).id(id).scroll([false, false]);
|
||||||
|
win.show(ctx, |ui| {
|
||||||
|
self.frame(ui, cursor)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(&mut self, ui: &mut Ui, cursor: f32) {
|
||||||
|
egui::Frame::new().inner_margin(3).show(ui, |ui| {
|
||||||
|
let dim = 20.0 * LOOP_LENGTH as f32;
|
||||||
|
ui.set_width(dim);
|
||||||
|
ui.set_height(dim);
|
||||||
|
let rect = ui.clip_rect().translate(Vec2::new(6.0, 6.0));
|
||||||
|
//ui.painter().debug_rect(rect, Color32::from_rgb(0, 255, 0), "blah");
|
||||||
|
for (i, lit) in self.notes.iter().enumerate() {
|
||||||
|
let (x, y) = (i as u32 % LOOP_LENGTH, i as u32 / LOOP_LENGTH);
|
||||||
|
let center = Pos2::new(
|
||||||
|
(x * 20 + 10) as f32 + rect.left(),
|
||||||
|
(y * 20 + 10) as f32 + rect.top());
|
||||||
|
if *lit {
|
||||||
|
ui.painter().circle_filled(center, 10.0, self.note_type.color());
|
||||||
|
} else {
|
||||||
|
ui.painter().circle_stroke(center, 10.0, (1.0, Color32::from_gray(0x88)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.painter().vline(
|
||||||
|
cursor * dim + rect.left(),
|
||||||
|
Rangef::new(rect.top(), rect.top() + dim),
|
||||||
|
(1.0, self.note_type.color())
|
||||||
|
);
|
||||||
|
|
||||||
|
if ui.response().contains_pointer() {
|
||||||
|
ui.input(|input| {
|
||||||
|
if input.pointer.button_clicked(PointerButton::Primary) {
|
||||||
|
if let Some(pos) = input.pointer.latest_pos() {
|
||||||
|
println!("window: {} pos: {} {}", self.id, pos.x - rect.left(), pos.y - rect.top());
|
||||||
|
let x = ((pos.x - rect.left()) / 20.0).floor() as usize;
|
||||||
|
let y = ((pos.y - rect.top()) / 20.0).floor() as usize;
|
||||||
|
let n = x + y * LOOP_LENGTH as usize;
|
||||||
|
self.notes[n] = !self.notes[n]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn notes(&self, beat: u32) -> Vec<u32> {
|
||||||
|
let mut notes = vec![];
|
||||||
|
for y in 0..LOOP_LENGTH {
|
||||||
|
if self.notes[(y * LOOP_LENGTH + beat) as usize] {
|
||||||
|
notes.push(y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notes
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
use std::ops::RangeInclusive;
|
||||||
|
use eframe::egui;
|
||||||
|
use eframe::egui::{Context, TopBottomPanel};
|
||||||
|
use crate::grid::{Grid, NoteType};
|
||||||
|
use crate::Nome;
|
||||||
|
|
||||||
|
impl Nome {
|
||||||
|
pub fn menu(&mut self, ctx: &Context) {
|
||||||
|
TopBottomPanel::top("menu_panel").show(ctx, |ui| {
|
||||||
|
egui::MenuBar::new().ui(ui, |ui| {
|
||||||
|
if ui.button("Sine").clicked() {
|
||||||
|
self.add_window(NoteType::Sine)
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||||
|
ui.add(egui::Slider::new(&mut self.tempo, RangeInclusive::new(20, 180)));
|
||||||
|
if self.playing {
|
||||||
|
if ui.button("||").clicked() { self.playing = false }
|
||||||
|
} else {
|
||||||
|
if ui.button(">").clicked() { self.playing = true }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
mod gui;
|
||||||
|
mod nome;
|
||||||
|
mod grid;
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
use eframe::{App, Frame};
|
||||||
|
use eframe::egui::Context;
|
||||||
|
use crate::nome::Nome;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let native_options = eframe::NativeOptions::default();
|
||||||
|
eframe::run_native("Fleen", native_options, Box::new(|_cc| {
|
||||||
|
Ok(Box::new(Nome::default()))
|
||||||
|
})).expect("Error running application");
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App for Nome {
|
||||||
|
fn update(&mut self, ctx: &Context, frame: &mut Frame) {
|
||||||
|
let play = self.tick();
|
||||||
|
let cursor = self.ratio();
|
||||||
|
self.menu(&ctx);
|
||||||
|
for g in self.grids.iter_mut() {
|
||||||
|
g.show(ctx, cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if play {
|
||||||
|
for (track, row) in self.notes_for_beat() {
|
||||||
|
println!("{}: {}", track, row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.request_repaint_after(Duration::from_millis(17))
|
||||||
|
}
|
||||||
|
}
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
use std::time::Instant;
|
||||||
|
use crate::grid::{Grid, NoteType};
|
||||||
|
|
||||||
|
pub const LOOP_LENGTH: u32 = 16;
|
||||||
|
|
||||||
|
pub struct Nome {
|
||||||
|
/// Tempo in beats per minute
|
||||||
|
pub tempo: u32,
|
||||||
|
|
||||||
|
// A count _in beats_ of where we are in the loop
|
||||||
|
timer: f32,
|
||||||
|
|
||||||
|
/// Whether or not we're playing; false == paused
|
||||||
|
pub playing: bool,
|
||||||
|
|
||||||
|
// The instant of the last time we called tick()
|
||||||
|
last_tick: Option<Instant>,
|
||||||
|
|
||||||
|
/// The grids that we currently have going
|
||||||
|
pub grids: Vec<Grid>,
|
||||||
|
|
||||||
|
// Running count of windows created (for ids)
|
||||||
|
window_counter: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Nome {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
tempo: 90,
|
||||||
|
timer: 0.0,
|
||||||
|
playing: true,
|
||||||
|
last_tick: None,
|
||||||
|
grids: vec![],
|
||||||
|
window_counter: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Nome {
|
||||||
|
/// Call this every frame to update the timer / last tick based on the current instant
|
||||||
|
/// and the tempo
|
||||||
|
pub fn tick(&mut self) -> bool {
|
||||||
|
let now = Instant::now();
|
||||||
|
let old_beat = self.beat();
|
||||||
|
if let Some(last) = self.last_tick && self.playing {
|
||||||
|
let dt = (now - last).as_secs_f32();
|
||||||
|
let bps = (self.tempo as f32) / 60.0;
|
||||||
|
// Timer is an amount of time _in beats_ and some of those beats might have been for a
|
||||||
|
// different tempo.
|
||||||
|
// We know now a time delta in seconds and a conversion factor to turn that to beats, so:
|
||||||
|
self.timer += dt * bps;
|
||||||
|
// 16 beats in the loop, so timer should never be over 16:
|
||||||
|
while self.timer > LOOP_LENGTH as f32 { self.timer -= LOOP_LENGTH as f32 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Playing or not, update last_tick so that the next frame adds the correct duration to timer
|
||||||
|
self.last_tick = Some(now);
|
||||||
|
|
||||||
|
// Did we enter a new beat?
|
||||||
|
self.beat() != old_beat
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which beat (0..loop_length) we're on
|
||||||
|
pub fn beat(&self) -> u32 {
|
||||||
|
self.timer.floor() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What fraction we are (0.0..1.0) through the loop
|
||||||
|
/// (multiply by window width to find the x coord to draw the cursor line)
|
||||||
|
pub fn ratio(&self) -> f32 {
|
||||||
|
self.timer / LOOP_LENGTH as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_window(&mut self, note_type: NoteType) {
|
||||||
|
self.window_counter += 1;
|
||||||
|
self.grids.push(Grid::for_note_type(note_type, self.window_counter));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn notes_for_beat(&self) -> Vec<(String, u32)> {
|
||||||
|
let mut notes = vec![];
|
||||||
|
let beat = self.beat();
|
||||||
|
|
||||||
|
for grid in self.grids.iter() {
|
||||||
|
for note in grid.notes(beat).into_iter() {
|
||||||
|
notes.push((grid.id.clone(), note))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notes
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user