Skip to content

Commit

Permalink
Implement font selection
Browse files Browse the repository at this point in the history
  • Loading branch information
PolyMeilex committed Dec 2, 2020
1 parent 8db880e commit 0c2ddaa
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 64 deletions.
File renamed without changes.
35 changes: 23 additions & 12 deletions src/output_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ use synth_backend::SynthBackend;

use std::{
fmt::{self, Display, Formatter},
path::Path,
path::{Path, PathBuf},
};

#[derive(Debug, Clone, PartialEq)]
pub enum OutputDescriptor {
Synth,
Synth(Option<PathBuf>),
MidiOut(MidiPortInfo),
DummyOutput,
}

impl Display for OutputDescriptor {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
OutputDescriptor::Synth => write!(f, "Buildin Synth"),
OutputDescriptor::Synth(_) => write!(f, "Buildin Synth"),
OutputDescriptor::MidiOut(info) => write!(f, "{}", info),
OutputDescriptor::DummyOutput => write!(f, "No Output"),
}
Expand All @@ -45,11 +45,12 @@ pub struct OutputManager {

impl OutputManager {
pub fn new() -> Self {
let synth_backend = if Path::new("./font.sf2").exists() {
Some(SynthBackend::new())
} else {
log::info!("./font.sf2 not found");
None
let synth_backend = match SynthBackend::new() {
Ok(synth_backend) => Some(synth_backend),
Err(err) => {
log::error!("{:?}", err);
None
}
};

let midi_backend = match MidiBackend::new() {
Expand All @@ -72,24 +73,34 @@ impl OutputManager {
pub fn get_outputs(&self) -> Vec<OutputDescriptor> {
let mut outs = Vec::new();

outs.push(OutputDescriptor::DummyOutput);

if let Some(synth) = &self.synth_backend {
outs.append(&mut synth.get_outputs());
}
if let Some(midi) = &self.midi_backend {
outs.append(&mut midi.get_outputs());
}

outs.push(OutputDescriptor::DummyOutput);

outs
}

pub fn connect(&mut self, desc: OutputDescriptor) {
if desc != self.output_connection.0 {
match desc {
OutputDescriptor::Synth => {
OutputDescriptor::Synth(ref font) => {
if let Some(ref mut synth) = self.synth_backend {
self.output_connection = (desc, Box::new(synth.new_output_connection()));
if let Some(font) = font.clone() {
self.output_connection =
(desc, Box::new(synth.new_output_connection(font)));
} else {
if Path::new("./default.sf2").exists() {
self.output_connection = (
desc,
Box::new(synth.new_output_connection("./default.sf2".into())),
);
}
}
}
}
OutputDescriptor::MidiOut(ref info) => {
Expand Down
26 changes: 14 additions & 12 deletions src/output_manager/synth_backend.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
extern crate fluidlite_lib;

use std::{error::Error, path::PathBuf, sync::mpsc::Receiver};

use crate::output_manager::{OutputConnection, OutputDescriptor};

use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
Expand All @@ -21,29 +23,29 @@ pub struct SynthBackend {
}

impl SynthBackend {
pub fn new() -> Self {
pub fn new() -> Result<Self, Box<dyn Error>> {
let host = cpal::default_host();

let device = host
.default_output_device()
.expect("failed to find a default output device");
.ok_or("failed to find a default output device")?;

let config = device.default_output_config().unwrap();
let config = device.default_output_config()?;
let sample_format = config.sample_format();

let mut stream_config: cpal::StreamConfig = config.into();
stream_config.sample_rate.0 = 44100;

Self {
Ok(Self {
_host: host,
device,

stream_config,
sample_format,
}
})
}

fn run<T: cpal::Sample>(&self, rx: std::sync::mpsc::Receiver<MidiEvent>) -> cpal::Stream {
fn run<T: cpal::Sample>(&self, rx: Receiver<MidiEvent>, path: PathBuf) -> cpal::Stream {
let mut buff: [f32; SAMPLES_SIZE] = [0.0f32; SAMPLES_SIZE];

let synth = {
Expand All @@ -55,7 +57,7 @@ impl SynthBackend {
rate.set((sample_rate / 2) as f64);

let synth = fluidlite::Synth::new(settings).unwrap();
synth.sfload("font.sf2", true).unwrap();
synth.sfload(path, true).unwrap();
synth.set_sample_rate(sample_rate as f32);
synth.set_gain(1.0);

Expand Down Expand Up @@ -112,19 +114,19 @@ impl SynthBackend {
stream
}

pub fn new_output_connection(&mut self) -> SynthOutputConnection {
pub fn new_output_connection(&mut self, path: PathBuf) -> SynthOutputConnection {
let (tx, rx) = std::sync::mpsc::channel::<MidiEvent>();
let _stream = match self.sample_format {
cpal::SampleFormat::F32 => self.run::<f32>(rx),
cpal::SampleFormat::I16 => self.run::<i16>(rx),
cpal::SampleFormat::U16 => self.run::<u16>(rx),
cpal::SampleFormat::F32 => self.run::<f32>(rx, path),
cpal::SampleFormat::I16 => self.run::<i16>(rx, path),
cpal::SampleFormat::U16 => self.run::<u16>(rx, path),
};

SynthOutputConnection { _stream, tx }
}

pub fn get_outputs(&self) -> Vec<OutputDescriptor> {
vec![OutputDescriptor::Synth]
vec![OutputDescriptor::Synth(None)]
}
}

Expand Down
122 changes: 84 additions & 38 deletions src/scene/menu_scene/iced_menu.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::path::PathBuf;

use iced_native::{
image, Align, Color, Column, Command, Container, Element, HorizontalAlignment, Image, Length,
Program, Row, Text, VerticalAlignment,
Expand All @@ -8,10 +10,12 @@ use crate::output_manager::OutputDescriptor;

pub struct IcedMenu {
midi_file: Option<lib_midi::Midi>,
pub font_path: Option<PathBuf>,

carousel: Carousel,
pub carousel: Carousel,

file_select_button: neo_btn::State,
synth_button: neo_btn::State,
prev_button: neo_btn::State,
next_button: neo_btn::State,
play_button: neo_btn::State,
Expand All @@ -20,14 +24,15 @@ pub struct IcedMenu {
#[derive(Debug, Clone)]
pub enum Message {
FileSelectPressed,
FontSelectPressed,

PrevPressed,
NextPressed,
PlayPressed,

OutputsUpdated(Vec<OutputDescriptor>),

MainMenuDone(lib_midi::Midi, usize, OutputDescriptor),
MainMenuDone(lib_midi::Midi, OutputDescriptor),
}

impl IcedMenu {
Expand All @@ -45,10 +50,12 @@ impl IcedMenu {

Self {
midi_file,
font_path: None,

carousel,

file_select_button: neo_btn::State::new(),
synth_button: neo_btn::State::new(),
prev_button: neo_btn::State::new(),
next_button: neo_btn::State::new(),
play_button: neo_btn::State::new(),
Expand Down Expand Up @@ -90,6 +97,24 @@ impl Program for IcedMenu {
}
}

Message::FontSelectPressed => {
use nfd2::Response;

match nfd2::DialogBuilder::single()
.filter("sf2")
.open()
.expect("Font Dialog Error")
{
Response::Okay(path) => {
log::info!("Font path = {:?}", path);
self.font_path = Some(path);
}
_ => {
log::error!("User canceled dialog");
}
}
}

Message::NextPressed => {
self.carousel.next();
}
Expand All @@ -99,18 +124,23 @@ impl Program for IcedMenu {

Message::PlayPressed => {
if self.midi_file.is_some() {
async fn play(
midi: lib_midi::Midi,
id: usize,
out: OutputDescriptor,
) -> Message {
Message::MainMenuDone(midi, id, out)
async fn play(m: Message) -> Message {
m
}

if self.midi_file.is_some() {
if let Some(midi) = std::mem::replace(&mut self.midi_file, None) {
if let Some(port) = self.carousel.get_item() {
return Command::from(play(midi, self.carousel.id, port.clone()));
let port = if let OutputDescriptor::Synth(_) = port {
OutputDescriptor::Synth(std::mem::replace(
&mut self.font_path,
None,
))
} else {
port.clone()
};
let event = Message::MainMenuDone(midi, port);
return Command::from(play(event));
}
}
}
Expand All @@ -121,7 +151,7 @@ impl Program for IcedMenu {
self.carousel.update(outs);
}

Message::MainMenuDone(_, _, _) => {}
Message::MainMenuDone(_, _) => {}
}

Command::none()
Expand All @@ -142,52 +172,65 @@ impl Program for IcedMenu {
.on_press(Message::FileSelectPressed),
);

let item = self
.carousel
.get_item()
let item = self.carousel.get_item();

let label = item
.map(|o| o.to_string())
.unwrap_or("Disconected".to_string());

let text = Text::new(item)
let output = Text::new(label)
.color(Color::WHITE)
// .height(Length::Units(100))
.size(30)
.horizontal_alignment(HorizontalAlignment::Center)
.vertical_alignment(VerticalAlignment::Center);

let select_row = Row::new()
.height(Length::Units(50))
.push(
NeoBtn::new(
&mut self.prev_button,
Text::new("<")
.size(40)
.horizontal_alignment(HorizontalAlignment::Center)
.vertical_alignment(VerticalAlignment::Center),
)
.width(Length::Fill)
.disabled(!self.carousel.check_prev())
.on_press(Message::PrevPressed),
let mut select_row = Row::new().height(Length::Units(50)).push(
NeoBtn::new(
&mut self.prev_button,
Text::new("<")
.size(40)
.horizontal_alignment(HorizontalAlignment::Center)
.vertical_alignment(VerticalAlignment::Center),
)
.push(
.width(Length::Fill)
.disabled(!self.carousel.check_prev())
.on_press(Message::PrevPressed),
);

if let Some(OutputDescriptor::Synth(_)) = item {
select_row = select_row.push(
NeoBtn::new(
&mut self.next_button,
Text::new(">")
.size(40)
&mut self.synth_button,
Text::new("Soundfont")
.size(20)
.horizontal_alignment(HorizontalAlignment::Center)
.vertical_alignment(VerticalAlignment::Center),
)
.width(Length::Fill)
.disabled(!self.carousel.check_next())
.on_press(Message::NextPressed),
.width(Length::Units(100))
.height(Length::Fill)
.on_press(Message::FontSelectPressed),
);
}

select_row = select_row.push(
NeoBtn::new(
&mut self.next_button,
Text::new(">")
.size(40)
.horizontal_alignment(HorizontalAlignment::Center)
.vertical_alignment(VerticalAlignment::Center),
)
.width(Length::Fill)
.disabled(!self.carousel.check_next())
.on_press(Message::NextPressed),
);

let controls = Column::new()
.align_items(Align::Center)
.width(Length::Units(500))
.spacing(30)
.push(file_select_button)
.push(text)
.push(output)
.push(select_row);

let controls = Container::new(controls).width(Length::Fill).center_x();
Expand Down Expand Up @@ -238,7 +281,6 @@ impl Program for IcedMenu {
};

let footer = Container::new(content)
.padding(10)
.width(Length::Fill)
.height(Length::Units(70))
.align_x(Align::End)
Expand All @@ -250,7 +292,7 @@ impl Program for IcedMenu {
}
}

struct Carousel {
pub struct Carousel {
outputs: Vec<OutputDescriptor>,
id: usize,
}
Expand All @@ -263,6 +305,10 @@ impl Carousel {
}
}

pub fn id(&self) -> usize {
self.id
}

fn update(&mut self, outs: Vec<OutputDescriptor>) {
self.outputs = outs;
}
Expand Down
Loading

0 comments on commit 0c2ddaa

Please sign in to comment.