Skip to content

Commit

Permalink
soundfont
Browse files Browse the repository at this point in the history
  • Loading branch information
dansgithubuser committed Aug 30, 2024
1 parent 0be59d3 commit 1471a6e
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 14 deletions.
Binary file added assets/soundfont/32MbGMStereo.sf2
Binary file not shown.
2 changes: 1 addition & 1 deletion components/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ members = [
"reverb",
"rhymel",
"sinbank",
"sonic",
"sonic", "soundfont",
"stft",
"strummer",
"tape",
Expand Down
47 changes: 44 additions & 3 deletions components/liner/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,29 @@ impl Line {
}
json!(notes)
}

fn duration(&self, sample_rate: u32, run_size: usize) -> f32 {
let mut line = Line {
deltamsgs: self.deltamsgs.clone(),
ticks_per_quarter: self.ticks_per_quarter,
..Default::default()
};
line.reset();
let mut samples: u32 = 0;
loop {
samples += run_size as u32;
if line.advance(
samples,
run_size,
sample_rate,
None,
None,
) {
break;
}
}
samples as f32 / sample_rate as f32
}
}

struct Queue {
Expand Down Expand Up @@ -261,6 +284,7 @@ component!(
"advance": {"args": ["seconds"]},
"skip_line": {"args": [{"desc": "number of lines", "default": 1}]},
"multiply_deltas": {"args": ["multiplier"]},
"duration": {},
},
);

Expand Down Expand Up @@ -296,6 +320,9 @@ impl ComponentTrait for Component {
while let Ok(mut line) = self.queue.recv.try_recv() {
let line_index = (*line).index;
(*line).index = (*self.lines[line_index]).index;
while line_index >= self.lines.len() {
self.lines.push(Box::new(Line::default()));
}
self.lines[line_index] = line;
}
self.samples += self.run_size as u32;
Expand Down Expand Up @@ -368,11 +395,11 @@ impl Component {
let line_index: usize = body.arg(0)?;
let ticks_per_quarter: u32 = body.arg(1)?;
let deltamsgs = body.arg(2)?;
while line_index >= self.lines.len() {
self.lines.push(Box::new(Line::default()));
}
if let Ok(immediate) = body.kwarg("immediate") {
if immediate {
while line_index >= self.lines.len() {
self.lines.push(Box::new(Line::default()));
}
self.lines[line_index] = Box::new(Line::new(
&deltamsgs,
ticks_per_quarter,
Expand Down Expand Up @@ -441,4 +468,18 @@ impl Component {
}
Ok(None)
}

fn duration_cmd(&mut self, _body: serde_json::Value) -> CmdResult {
if self.run_size == 0 {
return Err(err!("run size is 0").into());
}
if self.sample_rate == 0 {
return Err(err!("sample rate is 0").into());
}
let mut duration: f32 = 0.0;
for line in &self.lines {
duration = line.duration(self.sample_rate, self.run_size).max(duration);
}
Ok(Some(json!(duration)))
}
}
11 changes: 11 additions & 0 deletions components/soundfont/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "soundfont"
version = "1.0.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
dlal-component-base = { path = "../base" }
rustysynth = "1.3.1"
92 changes: 92 additions & 0 deletions components/soundfont/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use dlal_component_base::{component, serde_json, Body, CmdResult};

use rustysynth;

use std::fs::File;
use std::sync::Arc;

component!(
{"in": ["midi"], "out": ["audio"]},
[
"run_size",
"sample_rate",
"uni",
"check_audio",
{"name": "field_helpers", "fields": ["soundfont_path"], "kinds": ["r", "json"]},
],
{
soundfont_path: String,
synth: Option<rustysynth::Synthesizer>,
buffer: Vec<f32>,
},
{
"soundfont_load": {
"args": ["path"],
},
},
);

impl ComponentTrait for Component {
fn run(&mut self) {
let synth = match self.synth.as_mut() {
Some(synth) => synth,
_ => return,
};
let audio = match &self.output {
Some(output) => output.audio(self.run_size).unwrap(),
None => return,
};
synth.render(audio, &mut self.buffer);
}

fn midi(&mut self, msg: &[u8]) {
let synth = match self.synth.as_mut() {
Some(synth) => synth,
_ => return,
};
if msg.len() < 3 {
return;
}
synth.process_midi_message(
(msg[0] & 0x0f) as i32,
(msg[0] & 0xf0) as i32,
msg[1] as i32,
msg[2] as i32,
);
}

fn join(&mut self, _body: serde_json::Value) -> CmdResult {
self.buffer.resize(self.run_size, 0.0);
if !self.soundfont_path.is_empty() {
self.soundfont_load()
} else {
Ok(None)
}
}

fn from_json_cmd(&mut self, body: serde_json::Value) -> CmdResult {
field_helper_from_json!(self, body);
if !self.soundfont_path.is_empty() {
self.soundfont_load()
} else {
Ok(None)
}
}
}

impl Component {
fn soundfont_load_cmd(&mut self, body: serde_json::Value) -> CmdResult {
self.soundfont_path = body.arg(0)?;
self.soundfont_load()?;
Ok(None)
}

fn soundfont_load(&mut self) -> CmdResult {
let mut file = File::open(&self.soundfont_path)?;
let soundfont = Arc::new(rustysynth::SoundFont::new(&mut file)?);
let mut settings = rustysynth::SynthesizerSettings::new(self.sample_rate as i32);
settings.block_size = self.run_size;
self.synth = Some(rustysynth::Synthesizer::new(&soundfont, &settings)?);
Ok(None)
}
}
16 changes: 10 additions & 6 deletions skeleton/dlal/_component.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from . import _logging
from ._utils import DIR
from ._utils import DIR, JsonEncoder

import obvious

Expand Down Expand Up @@ -104,11 +104,15 @@ def command_detach(self, name, args=[], kwargs={}):

def command_immediate(self, name, args=[], kwargs={}):
log('debug', f'{self.name} {name} {args} {kwargs}')
result = self._lib.command(self._raw, json.dumps({
'name': name,
'args': args,
'kwargs': kwargs,
}).encode('utf-8'))
body = json.dumps(
{
'name': name,
'args': args,
'kwargs': kwargs,
},
cls=JsonEncoder,
)
result = self._lib.command(self._raw, body.encode('utf-8'))
if not result: return
result = json.loads(result.decode('utf-8'))
if type(result) == dict and 'error' in result:
Expand Down
15 changes: 11 additions & 4 deletions skeleton/dlal/_utils.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
from collections.abc import Iterable
import json
import os
from pathlib import Path
import random
import re
import socket

DIR = os.path.dirname(os.path.realpath(__file__))
REPO_DIR = os.path.dirname(os.path.dirname(DIR))
ASSETS_DIR = os.path.join(REPO_DIR, 'assets')
DEPS_DIR = os.path.join(REPO_DIR, 'deps')
DIR = Path(__file__).resolve().parent
REPO_DIR = DIR.parent.parent
ASSETS_DIR = REPO_DIR / 'assets'
DEPS_DIR = REPO_DIR / 'deps'

class NoContext:
def __enter__(*args, **kwargs): pass
def __exit__(*args, **kwargs): pass

class JsonEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, Path):
return str(o)

def snake_to_upper_camel_case(s):
return ''.join(i.capitalize() for i in s.split('_'))

Expand Down
13 changes: 13 additions & 0 deletions skeleton/dlal/soundfont.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from ._component import Component

from ._utils import ASSETS_DIR

import math
import os

class Soundfont(Component):
def __init__(self, **kwargs):
Component.__init__(self, 'soundfont', **kwargs)
from ._skeleton import Immediate
with Immediate():
self.soundfont_load(ASSETS_DIR / 'soundfont/32MbGMStereo.sf2')
27 changes: 27 additions & 0 deletions systems/soundfont.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import dlal

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--midi-path', '-m')
args = parser.parse_args()

audio = dlal.Audio(driver=True)
comm = dlal.Comm()
liner = dlal.Liner()
soundfont = dlal.Soundfont()
tape = dlal.Tape()

for i in range(16):
liner.connect(soundfont)
dlal.connect(
soundfont,
[audio, tape],
)

duration = None
if args.midi_path:
liner.load(args.midi_path, immediate=True)
duration = liner.duration()

dlal.typical_setup(duration=duration)
4 changes: 4 additions & 0 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

.component-sinbank,
.component-sonic,
.component-soundfont,
.component-train,
.component-osc
{
Expand Down Expand Up @@ -181,6 +182,9 @@
await contextOptionPage('sonic', 'sonic.html');
await contextOptionPage('webboard', 'webboard.html');
break;
case 'soundfont':
await contextOptionPage('webboard', 'webboard.html');
break;
case 'tape':
contextOption(dropdown, 'play', play, name);
break;
Expand Down

0 comments on commit 1471a6e

Please sign in to comment.