Skip to content

Instantly share code, notes, and snippets.

@airstrike
Created June 30, 2025 01:43
Show Gist options
  • Select an option

  • Save airstrike/b2e958164380e0e6d03b2cd26a295a2d to your computer and use it in GitHub Desktop.

Select an option

Save airstrike/b2e958164380e0e6d03b2cd26a295a2d to your computer and use it in GitHub Desktop.
iced • now playing: chill tracks only
use rand::Rng;
use iced::time::{Duration, Instant};
use iced::widget::canvas::{Frame, Geometry, Path, Program};
use iced::widget::{button, canvas, center, column, text};
use iced::{Element, Point, Rectangle, Renderer, Size, Theme};
use iced::{Fill, Subscription, Task};
const NUM_BARS: usize = 20;
const MAX_BAR_HEIGHT: f32 = 0.7;
const BAR_SPACING: f32 = 2.0;
fn main() -> iced::Result {
iced::application(
NowPlayingApp::new,
NowPlayingApp::update,
NowPlayingApp::view,
)
.subscription(NowPlayingApp::subscription)
.title("iced • Now Playing")
.window_size([400.0, 200.0])
.theme(|_| iced::Theme::GruvboxDark)
.centered()
.run()
}
#[derive(Debug, Clone)]
pub enum Message {
TogglePlay,
Tick,
Pulse,
}
#[derive(Debug)]
struct NowPlaying {
is_playing: bool,
bars: [f32; NUM_BARS],
target_bars: [f32; NUM_BARS],
peaks: [f32; NUM_BARS],
peak_hold_timers: [u8; NUM_BARS],
}
impl Default for NowPlaying {
fn default() -> Self {
// Start with all bars and targets at zero (empty)
Self {
is_playing: false,
bars: [0.0; NUM_BARS],
target_bars: [0.0; NUM_BARS],
peaks: [0.0; NUM_BARS],
peak_hold_timers: [0; NUM_BARS],
}
}
}
impl NowPlaying {
fn create_wave(&mut self, center_index: usize, intensity: f32) {
for i in 0..NUM_BARS {
let distance = (i as isize - center_index as isize).abs() as f32;
let wave_intensity = intensity * (-distance * 0.25).exp();
if wave_intensity > 0.1 {
let random_factor = 0.85 + rand::rng().random_range(0.0..0.35);
self.target_bars[i] = self.target_bars[i].max(wave_intensity * random_factor);
}
}
}
}
struct NowPlayingApp {
state: NowPlaying,
last_pulse_time: Instant,
next_pulse_interval: Duration,
}
impl NowPlayingApp {
fn new() -> (Self, Task<Message>) {
// Create an initial wave using create_wave
let mut state = NowPlaying::default();
state.create_wave(NUM_BARS / 2, 0.8);
(
Self {
state,
last_pulse_time: Instant::now(),
next_pulse_interval: Duration::from_millis(200),
},
Task::none(),
)
}
fn subscription(&self) -> Subscription<Message> {
iced::time::every(Duration::from_millis(16)).map(|_| Message::Tick)
}
fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::TogglePlay => {
self.state.is_playing = !self.state.is_playing;
}
Message::Tick => {
// --- Pulse scheduling logic (matches JS) ---
if self.state.is_playing {
let now = Instant::now();
if now.duration_since(self.last_pulse_time) > self.next_pulse_interval {
self.last_pulse_time = now;
// Randomize next interval between 150-350ms
let next_ms = 150 + (rand::rng().random_range(0.0..200.0)) as u64;
self.next_pulse_interval = Duration::from_millis(next_ms);
// Create 1-3 wave centers
let num_waves = 1 + (rand::rng().random_range(0.0..3.0)) as usize;
for _ in 0..num_waves {
let center = rand::rng().random_range(0..NUM_BARS);
let intensity = 0.6 + rand::rng().random_range(0.0..0.4);
self.state.create_wave(center, intensity);
}
}
}
// Always update bars and peaks (decay/animation)
for i in 0..NUM_BARS {
// Smoothly move bars toward target
let diff = self.state.target_bars[i] - self.state.bars[i];
self.state.bars[i] += diff * 0.25;
// Peak logic (ghost bar)
if self.state.bars[i] > self.state.peaks[i] {
self.state.peaks[i] = self.state.bars[i];
self.state.peak_hold_timers[i] = 30; // Hold for ~0.5s at 60fps
} else if self.state.peak_hold_timers[i] > 0 {
self.state.peak_hold_timers[i] -= 1;
} else {
self.state.peaks[i] *= 0.95;
}
// Decay the target
self.state.target_bars[i] *= 0.92;
// Additional decay to bars
self.state.bars[i] *= 0.96;
// Clamp values
self.state.bars[i] = self.state.bars[i].max(0.0).min(1.0);
self.state.target_bars[i] = self.state.target_bars[i].max(0.0).min(1.0);
self.state.peaks[i] = self.state.peaks[i].max(0.0).min(1.0);
}
}
_ => {}
}
Task::none()
}
fn view(&self) -> Element<Message> {
let content = column![
button(
text(
if self.state.is_playing {
"Pause"
} else {
"Play"
}
.to_uppercase()
)
.size(12)
)
.on_press(Message::TogglePlay),
canvas(NowPlayingViewer {
bars: self.state.bars,
peaks: self.state.peaks,
})
.width(Fill)
.height(Fill)
]
.spacing(10)
.padding(10);
center(content).into()
}
}
struct NowPlayingViewer {
bars: [f32; NUM_BARS],
peaks: [f32; NUM_BARS],
}
impl<Message> Program<Message> for NowPlayingViewer {
type State = ();
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
theme: &Theme,
bounds: Rectangle,
_cursor: iced::mouse::Cursor,
) -> Vec<Geometry> {
let mut frame = Frame::new(renderer, bounds.size());
let bar_width = bounds.width / NUM_BARS as f32;
let actual_bar_width = bar_width - BAR_SPACING;
let palette = theme.extended_palette();
let bar_color = palette.primary.base.color;
let peak_color = palette.primary.strong.color;
for (i, &height) in self.bars.iter().enumerate() {
let scaled_height = height * MAX_BAR_HEIGHT * bounds.height;
let x = i as f32 * bar_width + BAR_SPACING / 2.0;
let y = bounds.height - scaled_height;
// Draw main bar
let bar = Path::rectangle(Point::new(x, y), Size::new(actual_bar_width, scaled_height));
// Simple solid color for bars
frame.fill(&bar, bar_color);
// Draw peak indicator
if self.peaks[i] > 0.01 {
let peak_y = bounds.height - (self.peaks[i] * MAX_BAR_HEIGHT * bounds.height);
let peak = Path::rectangle(
Point::new(x, peak_y - 2.0),
Size::new(actual_bar_width, 2.0),
);
frame.fill(&peak, peak_color);
}
}
vec![frame.into_geometry()]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment