#![windows_subsystem = "windows"]
use eframe::egui;
use rodio::{OutputStream, OutputStreamHandle, Source, buffer::SamplesBuffer};
use std::f32::consts::PI;
// Настройки из ASM
const DISCR: u32 = 48000;
// Для f32 амплитуда должна быть в пределах 0.0...1.0
const AMPLITUDE: f32 = 0.3;
const HZS: [f32; 67] = [
131.0, 139.0, 147.0, 156.0, 165.0, 175.0, 185.0, 196.0, 208.0, 220.0, 233.0, 247.0,
262.0, 277.0, 294.0, 311.0, 330.0, 349.0, 370.0, 392.0, 415.0, 440.0, 466.0, 494.0,
523.0, 554.0, 587.0, 622.0, 659.0, 698.0, 740.0, 784.0, 831.0, 880.0, 932.0, 988.0,
1047.0, 1109.0, 1175.0, 1245.0, 1319.0, 1397.0, 1480.0, 1568.0, 1661.0, 1760.0, 1865.0, 1976.0,
2093.0, 2217.0, 2349.0, 2489.0, 2637.0, 2794.0, 2960.0, 3136.0, 3322.0, 3520.0, 3729.0, 3951.0,
4186.0, 4435.0, 4699.0, 4978.0, 5274.0, 5588.0, 5920.0,
];
// Вставьте здесь ваши байты мелодий
const MELODY1: &[u8] = &[79,80,82,82,82,82,82,84,82,79,80,82,82,82,82,82,84,82,87,86,87,86,84,77,77,80,82,84,84,84,84,84,86,84,80,82,84,84,84,84,84,86,84,89,87,86,84,81,82,79,79,80,82,82,82,82,82,84,82,79,80,82,82,82,82,82,84,82,91,89,87,86,83,84,84,92,89,84,89,84,89,84,89,91,87,83,84,83,84,83,84,87,84,80,79,77,82,79,82,87,87,87,87,87,87,87,77,82,87,87,87,87,87,87,87,89,91,92,84,86,91,87];
const MELODY2: &[u8] = &[76,75,76,77,76,72,72,71,69,72,76,76,81,76,79,78,77,74,83,81,80,77,76,74,76,74,72,69,71,72,71,69,71,74,77,76,74,72,71,72,69,72,76,81,84,81,83,84,83,81,83,86,89,88,86,84,83,81,80,81,83,80,83,81,68,71,76,74,72,77,76,74,72,71,69,68,64,68,71,76,74,72,69,77,76,74,72,71,69,68,52,59,57,56,59,62,60,59,62,65,64,62,68,71,69,68,71,74,72,71,74,77,76,76,76,76,76,76,76,76,76,76,75,76,77,76,72,72,71,69,72,76,76,81,76,79,78,77,74,83,81,80,77,76,74,76,74,72,69,71,72,71,69,71,74,77,76,74,72,71,72,69,72,76,81,84,81,83,84,83,81,83,86,89,88,86,84,83,81,80,81,83,80,83,81,69,64,66,68,69,71,72,69,71,64,68,69,71,72,74,71,72,67,69,71,72,74,76,72,74,67,71,72,74,76,77,74,76,75,76,77,76,74,72,72,71,69,72,71,71,76,64,68,69,68,69,71,72,71,72,74,76,72,71,71,76,64,68,69,68,72,71,71,69];
const MELODY3: &[u8] = &[52,60,59,57,56,57,52,64,62,60,59,60,57,65,64,62,60,59,60,62,65,64,62,60,62,59,52,60,59,57,56,57,52,64,62,60,59,60,57,65,64,62,60,59,60,62,65,64,62,60,62,59,64,64,55,55,55,55,53,52,53,53,62,62,53,53,53,53,52,50,52,52,47,50,53,57,62,60,59,57,57,56,47,50,53,57,62,60,59,57,57,56,57];
const MELODY4: &[u8] = &[63,64,62,60,59,57,68,69,67,65,65,61,62,60,59,57,55,66,67,65,64,64,63,64,62,60,59,57,68,69,67,65,65,64,62,64,69,57,59,64,52,57];
const MELODY5: &[u8] = &[72,69,64,64,69,72,71,69,71,68,64,69,70,69,67,64,62,61,61,62,64,67,65,64,65,77,74,70,70,74,77,76,74,76,72,69,72,69,75,72,72,71,69,71,76,77,76,77,76,77,76,77,52,57,59,60,59,57,52,52,57,59,60,64,67,65,59,59,60,62,60,62,64,62,64,65,64,62,60,59,59,60,55,60,62,60,59,57,56];
const MELODY6: &[u8] = &[74,67,69,71,72,74,67,67,76,72,74,76,78,79,67,67,72,74,72,71,69,71,72,71,69,67,66,67,69,71,67,71,69,74,67,69,71,72,74,67,67,76,72,74,76,78,79,67,67,72,74,72,71,69,71,72,71,69,67,69,71,69,67,66,67];
const MELODY7: &[u8] = &[74,72,70,69,67,67,66,75,74,72,70,69,69,67,79,77,79,75,77,74,75,77,75,77,74,75,72,74,75,74,75,72,74,70,72,74,79,81,82,81,79,78,79,77,62,63,65,63,62,60,72,75,79,74,70,74,79,72,68,70,72,70,68,67,67,69,70,69,67,66,66,67,69,67,66,67];
const MELODY8: &[u8] = &[62,63,64,72,64,72,64,72,72,74,75,76,72,74,76,71,74,72,62,63,64,72,64,72,64,72,69,67,66,69,72,76,74,72,69,74,62,63,64,72,64,72,64,72,72,74,75,76,72,74,76,71,74,72,72,74,76,72,74,76,72,74,72,76,72,74,76,72,74,72,76,72,74,76,71,74,72];
const MELODY9: &[u8] = &[60,69,69,67,69,65,60,60,60,69,69,70,67,72,72,62,62,70,70,69,67,65,60,69,69,67,69,65,72,62,62,70,70,69,67,65,60,69,69,67,69,65];
const MELODY10: &[u8] = &[81,80,81,83,81,69,73,76,76,74,74,74,73,74,76,74,64,71,74,74,73,73,81,78,76,75,75,75,81,78,76,75,75,75,81,78,80,76,73,81,80,78,76,78,76,81,80,81,83,81,69,73,76,76,74,74,74,73,74,76,74,64,71,74,74,73,73,81,78,76,75,75,75,81,78,76,75,75,75,81,78,80,76,73,81,80,78,76,78,76,84,76,83,76,76,76,81,76,80,76,76,76,84,76,83,76,76,76,81,76,80,76,76,76,81,80,81,83,81,69,73,76,76,74,74,74,73,74,76,74,64,71,74,74,73,73,74,71,69,68,68,68,74,71,69,68,68,68,74,71,73,69,66,74,73,71,69,71,69,84,76,83,76,76,76,81,76,80,76,76,76,84,76,83,76,76,76,81,76,80,76,76,76,81,80,81,83,81,69,73,76,76,74,74,74,73,74,76,74,64,71,74,74,73,73,74,71,69,68,68,68,74,71,69,68,68,68,74,71,73,69,66,74,73,71,69,71,69];
const MELODY11: &[u8] = &[74,74,71,71,74,74,69,69,71,72,74,76,78,74,74,74,71,71,74,74,69,69,81,80,81,83,76,81,74,83,83,81,79,79,78,78,79,81,78,76,74,79,79,79,76,76,79,79,74,74,74,76,79,74,81,79];
const MELODY12: &[u8] = &[83,83,83,83,83,83,83,86,79,81,83,84,84,84,84,84,83,83,83,83,81,81,83,81,86,83,83,83,83,83,83,83,86,79,81,83,84,84,84,84,84,83,83,83,86,86,84,81,79];
const MELODY13: &[u8] = &[65,77,75,75,73,73,72,72,70,70,72,65,65,77,75,75,73,73,72,72,70,70,72,72,65,70,82,80,80,78,78,77,77,68,80,78,78,77,77,75,73,75,72,69,70,77,89,87,87,85,85,84,84,82,82,84,77,77,89,87,87,85,85,84,84,82,82,84,84,77,82,94,92,92,90,90,89,89,80,92,90,90,89,89,87,85,87,84,81,82,49,53,58,60,61,61,49,53,58,60,61,61,54,54,58,63,65,66,66,54,58,63,65,66,66,68,65,77,75,75,73,73,72,72,70,70,72,65,65,77,75,75,73,73,72,72,70,70,72,72,65,70,82,80,80,78,78,77,77,68,80,78,78,77,77,75,73,75,72,69,70,77,89,87,87,85,85,84,84,82,82,84,77,77,89,87,87,85,85,84,84,82,82,84,84,77,82,94,92,92,90,90,89,89,80,92,90,90,89,89,87,85,87,84,81,82];
const MELODY14: &[u8] = &[71,69,68,69,72,74,72,71,72,76,77,76,75,76,83,81,80,81,83,81,80,81,84,81,84,83,81,79,81,83,81,79,81,83,81,79,78,76,71,69,68,69,72,74,72,71,72,76,77,76,75,76,83,81,80,81,83,81,80,81,84,81,84,83,81,79,81,83,81,79,81,83,81,79,78,76,76,77,79,79,81,79,77,76,74,67,76,77,79,79,81,79,77,76,74,72,74,76,76,77,76,74,72,71,64,72,74,76,76,77,76,74,72,71,71,69,68,69,72,74,72,71,72,76,77,76,75,76,83,81,80,81,83,81,80,81,84,81,83,84,83,81,80,81,76,77,74,72,71,69,69,71,73,69,71,73,71,69,68,66,68,69,71,68,64,69,71,73,69,71,73,71,69,68,66,71,68,64,69,85,86,85,83,81,83,81,80,78,81,80,78,77,78,80,77,73,75,77,73,78,77,78,80,81,80,81,83,85,84,85,84,85,86,85,83,81,83,81,80,78,81,80,78,76,78,80,76,73,75,76,73,75,76,78,75,72,73,75,72,73,85,86,85,83,81,83,81,80,78,81,80,78,77,78,80,77,73,75,77,73,78,77,78,80,81,80,81,83,85,84,85,84,85,86,85,83,81,83,81,80,78,81,80,78,76,78,80,76,73,75,76,73,75,76,78,75,72,73,75,72,73,76,74,73,71,69,71,73,74,76,78,80,81,81,80,78,76,76,74,73,71,69,71,73,74,76,78,80,81,82,83,76,74,73,71,69,71,73,74,76,78,80,81,81,80,78,76,76,74,73,71,73,76,69,73,71,74,68,71,69];
const MELODY15: &[u8] = &[62,62,66,63,62,66,66,69,67,66,67,67,70,69,67,66,63,66,63,66,62,62,66,63,62,66,66,69,67,66,67,67,70,69,67,66,63,66,63,62,66,66,63,62,62,62,63,63,62,60,60,60,60,63,62,60,60,67,66,63,66,63,62,66,66,63,62,62,62,63,63,62,60,60,60,60,63,62,60,60,67,66,63,66,63,62,67,67,67,67,67,67,67,67,70,69,67,70,69,67,67,67,70,69,67,70,69,67,69,69,72,70,69,72,70,69,69,69,72,70,69,72,70,69,69,69,74,69,69,74,62,62,74,72,70,69,67];
const MELODY16: &[u8] = &[72,64,64,71,69,71,63,63,71,62,62,69,68,69,61,61,69,60,60,69,68,69,59,59,59,62,64,65,65,64,62,62,60,60,64,62,60,59,57,60,59,72,64,64,71,69,71,63,63,71,62,62,69,68,69,61,61,69,60,60,69,68,69,59,59,59,62,64,65,64,62,64,65,69,68,69,71,64,72];
const MELODY17: &[u8] = &[69,74,76,77,79,81,81,82,81,82,86,81,79,79,76,77,77,76,74,76,77,74,69,74,76,77,79,81,81,82,81,82,86,81,79,79,76,77,77,76,74,76,77,74,74,86,86,86,84,86,84,82,81,74,86,86,86,84,86,84,82,81,79,79,76,77,77,79,81,82,84,81,79,77,79,79,77,77,77,76,74,76,77,74,79,79,76,77,77,79,81,82,84,81,79,77,79,79,77,77,77,76,74,76,77,74];
const MELODY18: &[u8] = &[69,70,69,67,69,72,70,69,67,67,65,69,67,65,65,67,69,65,67,69,70,69,67,69,74,72,74,72,72,70,69,70,70,72,70,69,70,74,72,70,69,69,72,70,66,67,67,69,70,67,74,72,74,72,70,70,69,68,68,69,69,74,76,74,73,76,70,68,69,77,76,74,76,74,73,76,70,72,72,74,72,74,72,71,72,71,72,74,76,77,79,81,81,81,81,82,81,79,77,79,77,76,74,76,74,72,70,72,70,69,67,74,72,71,72,76,77,72,69,69,67,66,67,71,72,70,67,65];
struct Piano {
melodies: Vec<(&'static str, &'static [u8])>,
precomputed_sounds: Vec<Vec<f32>>,
selected_idx: usize,
current_note_idx: usize,
audio_handle: OutputStreamHandle,
_stream: OutputStream,
}
impl Piano {
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
let (stream, handle) = OutputStream::try_default().expect("Ошибка аудио");
// Предварительно вычисляем все ноты в f32 для мгновенного доступа
let mut precomputed = Vec::with_capacity(60);
for midi_note in 40..100 {
precomputed.push(Self::generate_note_data(midi_note));
}
Self {
melodies: vec![
("Вальс-шутка, Дмитрий Шостакович", MELODY1),
("Полонез 13, Михаил Огинский", MELODY2),
("Город Золотой, Владимир Вавилов", MELODY3),
("Шербурские зонтики, Мишеля Леграна", MELODY4),
("Бабье лето, Джо Дассен", MELODY5),
("Менуэт, Иоганн Себастьян Бах", MELODY6),
("Адажио, Ремо Джадзотто", MELODY7),
("Артист эстрады, Скотт Джоплин", MELODY8),
("В лесу родилась елочка, Леонид Бекман", MELODY9),
("Менуэт, Луиджи Боккерини", MELODY10),
("Америка прекрасна, Сэмюэл Уорд", MELODY11),
("Джингл Белс, Джеймс Лорд Пирпонт", MELODY12),
("Тот самый Мюнхгаузен, Рыбников", MELODY13),
("Турецкий марш, Моцарта", MELODY14),
("Хава нагила, еврейская песня", MELODY15),
("Где-то далеко, Микаэл Таривердиев", MELODY16),
("Атиква, Джузеппе Ченчи", MELODY17),
("Каста Дива, Винченцо Беллини", MELODY18),
],
precomputed_sounds: precomputed,
selected_idx: 0,
current_note_idx: 0,
audio_handle: handle,
_stream: stream,
}
}
// Чистый синтез (выполняется только при старте)
fn generate_note_data(midi_note: u8) -> Vec<f32> {
let mut samples = Vec::with_capacity(48000);
let base_idx = midi_note.saturating_sub(40) as usize;
let f1 = *HZS.get(base_idx).unwrap_or(&440.0);
let f2 = *HZS.get(base_idx + 12).unwrap_or(&(f1 * 2.0));
let f3 = *HZS.get(base_idx + 28).unwrap_or(&(f2 * 2.0));
for n in 0..24000 {
let angle = 2.0 * PI * (n as f32) / DISCR as f32;
let envelope = (angle.cos() + 1.0) / 2.0;
let s = ((angle * f1).sin() + (angle * f2).sin() + (angle * f3).sin())
* envelope * AMPLITUDE;
samples.push(s); // Left
samples.push(s); // Right
}
samples
}
fn play_next(&mut self) {
let melody = self.melodies[self.selected_idx].1;
if let Some(¬e) = melody.get(self.current_note_idx) {
let sound_idx = note.saturating_sub(40) as usize;
if let Some(data) = self.precomputed_sounds.get(sound_idx) {
// Используем SamplesBuffer<f32> для исключения конвертации
let buffer = SamplesBuffer::new(2, DISCR, data.clone());
// play_raw - минимальная задержка
let _ = self.audio_handle.play_raw(buffer);
self.current_note_idx += 1;
}
} else {
self.current_note_idx = 0;
}
}
}
impl eframe::App for Piano {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Ловим нажатие пробела (быстрее, чем клик мышкой)
if ctx.input(|i| i.key_pressed(egui::Key::Space)) {
self.play_next();
}
egui::CentralPanel::default().show(ctx, |ui| {
ui.vertical_centered(|ui| {
ui.add_space(10.0);
// БОЛЬШОЙ ЗАГОЛОВОК
ui.label(
egui::RichText::new("Piano Rust (Ultra Fast)")
.size(46.0)
.strong()
.color(egui::Color32::RED)
);
ui.add_space(20.0);
ui.label(egui::RichText::new("Выберите мелодию:").size(30.0).strong());
ui.add_space(5.0);
// 1. Увеличиваем внутренние отступы кнопки ComboBox,
// чтобы текст не прилипал к краям и казался центрированным
ui.spacing_mut().button_padding = egui::vec2(100.0, 10.0);
let combo = egui::ComboBox::from_id_source("melody_selector")
.width(20.0)
.height(600.0)
.selected_text(egui::RichText::new(self.melodies[self.selected_idx].0).size(25.0));
combo.show_ui(ui, |ui: &mut egui::Ui| {
for i in 0..self.melodies.len() {
let item_text = egui::RichText::new(self.melodies[i].0).size(24.0);
if ui.selectable_value(&mut self.selected_idx, i, item_text).clicked() {
self.current_note_idx = 0;
}
}
});
ui.add_space(20.0);
// ОГРОМНАЯ КНОПКА
let btn_text = egui::RichText::new("ИГРАТЬ НОТУ").size(30.0).strong();
let btn = ui.add_sized([500.0, 150.0], egui::Button::new(btn_text));
if btn.clicked() {
self.play_next();
}
ui.add_space(20.0);
if ui.button(egui::RichText::new("Сброс").size(28.0)).clicked() {
self.current_note_idx = 0;
}
ui.add_space(40.0);
ui.label(egui::RichText::new("Подсказка: жмите ПРОБЕЛ").size(30.0).italics());
});
});
// Требуем перерисовки для плавности
ctx.request_repaint();
}
}
fn main() -> eframe::Result<()> {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size([700.0, 800.0])
.with_resizable(false),
..Default::default()
};
eframe::run_native(
"Piano",
options,
Box::new(|cc| Box::new(Piano::new(cc))),
)
}