Skip to content

Commit

Permalink
Use phase modulation for continous curves
Browse files Browse the repository at this point in the history
  • Loading branch information
jbakker-atmind committed Nov 26, 2023
1 parent 6f58a83 commit 4f77a99
Show file tree
Hide file tree
Showing 26 changed files with 424 additions and 101 deletions.
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"fract"
]
}
17 changes: 13 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

[workspace]
members = [
"audio-engine-common",
"audio-engine-notes",
"audio-engine-fm"
]

Expand Down
10 changes: 10 additions & 0 deletions audio-engine-common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "audio-engine-common"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
name="audio_engine_common"
path="src/lib.rs"
1 change: 1 addition & 0 deletions audio-engine-common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod phase_time;
20 changes: 20 additions & 0 deletions audio-engine-common/src/phase_time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use std::ops::AddAssign;

#[derive(Default)]
pub struct PhaseTime {
pub time: f32,
}

impl PhaseTime {
pub fn delta_phase_time(frequency: f32, sample_rate: f32) -> PhaseTime {
PhaseTime {
time: (frequency / sample_rate).fract(),
}
}
}

impl AddAssign for PhaseTime {
fn add_assign(&mut self, other: Self) {
self.time = (self.time + 1.0 + other.time).fract()
}
}
2 changes: 2 additions & 0 deletions audio-engine-fm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ path="src/lib.rs"
[dependencies]
cpal = "*"

audio-engine-common = {path="../audio-engine-common"}

[[examples]]
name="experiment"
42 changes: 18 additions & 24 deletions audio-engine-fm/examples/experiment/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use audio_engine_fm::{
algorithm::Algorithm,
envelope::Envelope,
instrument::Instrument,
instrument::{Instrument, InstrumentNoteState},
operator::{Operator, Operators},
sample::{SampleGenerator, NO_USER_DATA},
waveform::Waveform,
Time,
};
Expand All @@ -12,9 +11,19 @@ use cpal::{
Sample,
};

fn sample_tone(sample_time: f32) -> f32 {
let frequency = 437.0;
fn main() -> Result<(), ()> {
let host = cpal::default_host();
let device = host.default_output_device().unwrap();
let config = device.default_output_config().unwrap();

play_tone(&device, &config.into())
}

fn play_tone(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), ()> {
let sample_rate = config.sample_rate.0 as f32;
let channels = config.channels as usize;

let mut sample_num = 0_u64;
let instrument = Instrument {
operators: Operators {
a: Operator {
Expand All @@ -32,37 +41,22 @@ fn sample_tone(sample_time: f32) -> f32 {
},
b: Operator {
waveform: Waveform::Sine,
rate: 1.0,
level: 1.0,
rate: 0.02,
level: 127.0,
envelope: Envelope::default(),
},
c: Operator::default(),
d: Operator::default(),
},
algorithm: Algorithm::BModulatesA,
};
let note_off = if sample_time > 6.0 { Some(6.0) } else { None };

instrument.sample(sample_time, note_off, frequency, &NO_USER_DATA)
}

fn main() -> Result<(), ()> {
let host = cpal::default_host();
let device = host.default_output_device().unwrap();
let config = device.default_output_config().unwrap();

play_tone(&device, &config.into())
}

fn play_tone(device: &cpal::Device, config: &cpal::StreamConfig) -> Result<(), ()> {
let sample_rate = config.sample_rate.0 as f32;
let channels = config.channels as usize;
let mut instrument_state = InstrumentNoteState::default();
let frequency = 437.0;

let mut sample_num = 0_u64;
let mut next_value = move || {
sample_num += 1;
let sample_time = sample_num as Time / sample_rate;
sample_tone(sample_time)
instrument.sample(sample_time, None, frequency, &mut instrument_state)
};

let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
Expand Down
35 changes: 19 additions & 16 deletions audio-engine-fm/src/algorithm.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use crate::{
operator::Operators,
sample::{SampleGenerator, NO_USER_DATA},
operator::{Operators, OperatorsNoteState},
Time,
};

Expand All @@ -15,33 +14,37 @@ pub enum Algorithm {
BModulatesA,
}

impl SampleGenerator for Algorithm {
type U = Operators;
fn sample(
impl Algorithm {
pub fn sample(
&self,
note_time: Time,
note_off: Option<Time>,
frequency: f32,
user_data: &Self::U,
operators: &Operators,
operator_states: &mut OperatorsNoteState,
) -> f32 {
match self {
Algorithm::A => user_data
.a
.sample(note_time, note_off, frequency, &NO_USER_DATA),
Algorithm::A => {
operators
.a
.sample(note_time, note_off, frequency, &mut operator_states.a)
}
Algorithm::AB => {
(user_data
(operators
.a
.sample(note_time, note_off, frequency, &NO_USER_DATA)
+ user_data
.sample(note_time, note_off, frequency, &mut operator_states.a)
+ operators
.b
.sample(note_time, note_off, frequency, &NO_USER_DATA))
.sample(note_time, note_off, frequency, &mut operator_states.b))
/ 2.0
}
Algorithm::BModulatesA => user_data.a.sample(
Algorithm::BModulatesA => operators.a.sample(
note_time,
note_off,
user_data.b.modulate(note_time, note_off, frequency),
&NO_USER_DATA,
operators
.b
.modulate(note_time, note_off, frequency, &mut operator_states.b),
&mut operator_states.a,
),
}
}
Expand Down
24 changes: 16 additions & 8 deletions audio-engine-fm/src/instrument.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
use crate::{
algorithm::Algorithm,
operator::Operators,
sample::{NoUserData, SampleGenerator},
operator::{Operators, OperatorsNoteState},
};

pub struct Instrument {
pub operators: Operators,
pub algorithm: Algorithm,
}

impl SampleGenerator for Instrument {
type U = NoUserData;
fn sample(
impl Instrument {
pub fn sample(
&self,
note_time: crate::Time,
note_off: Option<crate::Time>,
frequency: f32,
_user_data: &Self::U,
state: &mut InstrumentNoteState,
) -> f32 {
self.algorithm
.sample(note_time, note_off, frequency, &self.operators)
self.algorithm.sample(
note_time,
note_off,
frequency,
&self.operators,
&mut state.operators,
)
}
}

#[derive(Default)]
pub struct InstrumentNoteState {
pub operators: OperatorsNoteState,
}
1 change: 0 additions & 1 deletion audio-engine-fm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ pub mod algorithm;
pub mod envelope;
pub mod instrument;
pub mod operator;
pub mod sample;
pub mod waveform;

pub type Time = f32;
Expand Down
41 changes: 26 additions & 15 deletions audio-engine-fm/src/operator.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
use crate::{
envelope::Envelope,
sample::{NoUserData, SampleGenerator, NO_USER_DATA},
waveform::Waveform,
Level, Time,
};
use crate::{envelope::Envelope, waveform::Waveform, Level, Time};
use audio_engine_common::phase_time::PhaseTime;

pub struct Operator {
pub waveform: Waveform,
Expand All @@ -24,23 +20,25 @@ impl Default for Operator {
}

impl Operator {
pub fn modulate(&self, note_time: Time, note_off: Option<Time>, frequency: f32) -> f32 {
frequency + self.sample(note_time, note_off, frequency, &NO_USER_DATA)
pub fn modulate(
&self,
note_time: Time,
note_off: Option<Time>,
frequency: f32,
state: &mut OperatorNoteState,
) -> f32 {
frequency + self.sample(note_time, note_off, frequency, state)
}
}

impl SampleGenerator for Operator {
type U = NoUserData;

fn sample(
pub fn sample(
&self,
note_time: Time,
note_off: Option<Time>,
frequency: f32,
user_data: &Self::U,
state: &mut OperatorNoteState,
) -> f32 {
self.waveform
.sample(note_time, note_off, frequency * self.rate, user_data)
.sample_and_advance(&mut state.phase_time, frequency * self.rate, 44100.0)
* self.envelope.level(note_time, note_off)
* self.level
}
Expand All @@ -52,3 +50,16 @@ pub struct Operators {
pub c: Operator,
pub d: Operator,
}

#[derive(Default)]
pub struct OperatorNoteState {
pub phase_time: PhaseTime,
}

#[derive(Default)]
pub struct OperatorsNoteState {
pub a: OperatorNoteState,
pub b: OperatorNoteState,
pub c: OperatorNoteState,
pub d: OperatorNoteState,
}
15 changes: 0 additions & 15 deletions audio-engine-fm/src/sample.rs

This file was deleted.

Loading

0 comments on commit 4f77a99

Please sign in to comment.