@devaloop/devalang 0.0.1-beta.2 → 0.0.1-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +84 -81
- package/README.md +3 -2
- package/docs/CHANGELOG.md +41 -0
- package/docs/ROADMAP.md +3 -3
- package/examples/chain.deva +19 -0
- package/examples/plugin.deva +10 -10
- package/examples/routing.deva +23 -0
- package/out-tsc/bin/project-version.json +6 -0
- package/out-tsc/pkg/devalang_core_bg.wasm.d.ts +8 -8
- package/out-tsc/scripts/version/copy-to-binary.d.ts +1 -0
- package/out-tsc/scripts/version/copy-to-binary.js +79 -0
- package/package.json +23 -10
- package/project-version.json +3 -3
- package/rust/bindings/Cargo.toml +9 -0
- package/rust/bindings/src/lib.rs +86 -0
- package/rust/cli/addon/commands.rs +35 -0
- package/rust/cli/addon/download.rs +234 -0
- package/rust/cli/addon/install.rs +33 -0
- package/rust/cli/addon/list.rs +224 -0
- package/rust/cli/addon/metadata.rs +124 -0
- package/rust/cli/addon/mod.rs +8 -0
- package/rust/cli/addon/remove.rs +271 -0
- package/rust/cli/addon/update.rs +305 -0
- package/rust/cli/{install/addon.rs → addon/utils.rs} +109 -118
- package/rust/cli/build/commands.rs +153 -153
- package/rust/cli/build/process.rs +165 -165
- package/rust/cli/check/mod.rs +208 -208
- package/rust/cli/discover/commands.rs +275 -253
- package/rust/cli/discover/config.rs +109 -111
- package/rust/cli/discover/fs.rs +19 -19
- package/rust/cli/discover/install.rs +214 -103
- package/rust/cli/discover/metadata.rs +48 -48
- package/rust/cli/discover/mod.rs +5 -5
- package/rust/cli/me/commands.rs +52 -0
- package/rust/cli/me/mod.rs +1 -0
- package/rust/cli/mod.rs +12 -12
- package/rust/cli/parser.rs +30 -69
- package/rust/cli/play/commands.rs +375 -375
- package/rust/cli/play/process.rs +159 -159
- package/rust/core/audio/engine/driver.rs +19 -2
- package/rust/core/audio/engine/export.rs +169 -169
- package/rust/core/audio/engine/mod.rs +56 -56
- package/rust/core/audio/engine/notes/dsp.rs +88 -85
- package/rust/core/audio/engine/notes/mod.rs +53 -44
- package/rust/core/audio/engine/notes/params.rs +294 -294
- package/rust/core/audio/engine/sample/insert.rs +148 -47
- package/rust/core/audio/engine/sample/mod.rs +40 -40
- package/rust/core/audio/engine/sample/padding.rs +170 -170
- package/rust/core/audio/evaluator/condition.rs +61 -61
- package/rust/core/audio/evaluator/numeric.rs +152 -152
- package/rust/core/audio/evaluator/rhs.rs +16 -16
- package/rust/core/audio/evaluator/string_expr.rs +94 -94
- package/rust/core/audio/interpreter/driver.rs +574 -574
- package/rust/core/audio/interpreter/mod.rs +2 -2
- package/rust/core/audio/interpreter/statements/arrow_call/interprete.rs +9 -5
- package/rust/core/audio/interpreter/statements/arrow_call/methods/chord.rs +398 -384
- package/rust/core/audio/interpreter/statements/arrow_call/methods/effects.rs +323 -0
- package/rust/core/audio/interpreter/statements/arrow_call/methods/mod.rs +1 -0
- package/rust/core/audio/interpreter/statements/arrow_call/methods/note.rs +66 -11
- package/rust/core/audio/interpreter/statements/arrow_call/mod.rs +3 -3
- package/rust/core/audio/interpreter/statements/arrow_call/types/arp.rs +192 -192
- package/rust/core/audio/interpreter/statements/arrow_call/types/mod.rs +24 -24
- package/rust/core/audio/interpreter/statements/arrow_call/types/pad.rs +116 -116
- package/rust/core/audio/interpreter/statements/arrow_call/types/pluck.rs +97 -97
- package/rust/core/audio/interpreter/statements/arrow_call/types/sub.rs +100 -100
- package/rust/core/audio/interpreter/statements/automate.rs +16 -16
- package/rust/core/audio/interpreter/statements/call.rs +31 -1
- package/rust/core/audio/interpreter/statements/condition.rs +72 -72
- package/rust/core/audio/interpreter/statements/function.rs +24 -24
- package/rust/core/audio/interpreter/statements/let_.rs +36 -36
- package/rust/core/audio/interpreter/statements/load.rs +17 -17
- package/rust/core/audio/interpreter/statements/loop_.rs +115 -115
- package/rust/core/audio/interpreter/statements/spawn.rs +51 -2
- package/rust/core/audio/interpreter/statements/trigger.rs +242 -239
- package/rust/core/audio/loader/trigger.rs +98 -98
- package/rust/core/audio/player.rs +70 -70
- package/rust/core/audio/special/mod.rs +9 -9
- package/rust/core/builder/mod.rs +129 -129
- package/rust/core/debugger/lexer.rs +27 -27
- package/rust/core/debugger/logs.rs +52 -52
- package/rust/core/debugger/preprocessor.rs +27 -27
- package/rust/core/debugger/store.rs +38 -38
- package/rust/core/lexer/driver.rs +59 -59
- package/rust/core/lexer/handler/arrow.rs +82 -82
- package/rust/core/lexer/handler/at.rs +21 -21
- package/rust/core/lexer/handler/brace.rs +41 -41
- package/rust/core/lexer/handler/colon.rs +21 -21
- package/rust/core/lexer/handler/comment.rs +30 -30
- package/rust/core/lexer/handler/dot.rs +21 -21
- package/rust/core/lexer/handler/driver.rs +337 -337
- package/rust/core/lexer/handler/identifier.rs +47 -47
- package/rust/core/lexer/handler/indent.rs +66 -66
- package/rust/core/lexer/handler/mod.rs +15 -15
- package/rust/core/lexer/handler/newline.rs +23 -23
- package/rust/core/lexer/handler/number.rs +31 -31
- package/rust/core/lexer/handler/operator.rs +46 -46
- package/rust/core/lexer/handler/parenthesis.rs +41 -41
- package/rust/core/lexer/handler/slash.rs +21 -21
- package/rust/core/lexer/handler/string.rs +63 -63
- package/rust/core/lexer/mod.rs +3 -3
- package/rust/core/mod.rs +9 -9
- package/rust/core/parser/driver/block.rs +111 -111
- package/rust/core/parser/driver/cursor.rs +82 -82
- package/rust/core/parser/driver/driver_impl.rs +21 -1
- package/rust/core/parser/driver/mod.rs +6 -6
- package/rust/core/parser/driver/parse_array.rs +120 -120
- package/rust/core/parser/driver/parse_map.rs +247 -223
- package/rust/core/parser/driver/parser.rs +160 -160
- package/rust/core/parser/handler/arrow_call.rs +65 -14
- package/rust/core/parser/handler/identifier/synth.rs +171 -135
- package/rust/core/parser/handler/mod.rs +9 -9
- package/rust/core/parser/handler/pattern.rs +24 -1
- package/rust/core/plugin/loader.rs +137 -137
- package/rust/core/plugin/mod.rs +2 -2
- package/rust/core/plugin/runner/non_wasm.rs +481 -297
- package/rust/core/plugin/runner/wasm32.rs +1 -0
- package/rust/core/preprocessor/loader/inject.rs +313 -278
- package/rust/core/preprocessor/loader/loader_helpers.rs +110 -110
- package/rust/core/preprocessor/loader/mod.rs +235 -235
- package/rust/core/preprocessor/module.rs +55 -55
- package/rust/core/preprocessor/processor/handlers.rs +107 -107
- package/rust/core/preprocessor/resolver/bank.rs +49 -49
- package/rust/core/preprocessor/resolver/call.rs +124 -124
- package/rust/core/preprocessor/resolver/condition.rs +95 -95
- package/rust/core/preprocessor/resolver/driver.rs +324 -324
- package/rust/core/preprocessor/resolver/function.rs +69 -69
- package/rust/core/preprocessor/resolver/group.rs +122 -122
- package/rust/core/preprocessor/resolver/let_.rs +32 -32
- package/rust/core/preprocessor/resolver/loop_.rs +318 -318
- package/rust/core/preprocessor/resolver/mod.rs +16 -16
- package/rust/core/preprocessor/resolver/pattern.rs +95 -83
- package/rust/core/preprocessor/resolver/spawn.rs +99 -99
- package/rust/core/preprocessor/resolver/synth.rs +54 -54
- package/rust/core/preprocessor/resolver/tempo.rs +48 -48
- package/rust/core/preprocessor/resolver/trigger.rs +116 -116
- package/rust/core/preprocessor/resolver/value.rs +176 -176
- package/rust/core/store/global.rs +57 -57
- package/rust/lib.rs +323 -323
- package/rust/macros/Cargo.toml +14 -0
- package/rust/macros/src/lib.rs +52 -0
- package/rust/main.rs +311 -142
- package/rust/types/Cargo.toml +1 -1
- package/rust/types/src/addons.rs +3 -1
- package/rust/types/src/config.rs +1 -3
- package/rust/utils/Cargo.toml +5 -2
- package/rust/utils/src/file.rs +397 -14
- package/rust/utils/src/path.rs +31 -2
- package/rust/utils/src/version.rs +38 -7
- package/rust/web/auth.rs +5 -0
- package/rust/web/forge.rs +5 -0
- package/rust/web/mod.rs +5 -3
- package/typescript/scripts/version/copy-to-binary.ts +82 -0
- package/rust/cli/bank/api.rs +0 -122
- package/rust/cli/bank/commands.rs +0 -306
- package/rust/cli/bank/mod.rs +0 -29
- package/rust/cli/install/bank.rs +0 -72
- package/rust/cli/install/commands.rs +0 -35
- package/rust/cli/install/mod.rs +0 -4
- package/rust/cli/install/plugin.rs +0 -80
|
@@ -1,169 +1,169 @@
|
|
|
1
|
-
use crate::core::audio::engine::driver::MidiNoteEvent;
|
|
2
|
-
use std::fs::File;
|
|
3
|
-
|
|
4
|
-
pub fn generate_midi_file_impl(
|
|
5
|
-
midi_events: &Vec<MidiNoteEvent>,
|
|
6
|
-
output_path: &String,
|
|
7
|
-
bpm: Option<f32>,
|
|
8
|
-
tpqn: Option<u16>,
|
|
9
|
-
) -> Result<(), String> {
|
|
10
|
-
use midly::num::{u7, u15, u24};
|
|
11
|
-
use midly::{
|
|
12
|
-
Format, Header, MetaMessage, MidiMessage, Smf, Timing, TrackEvent, TrackEventKind,
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
if midi_events.is_empty() {
|
|
16
|
-
return Ok(());
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
let bpm = bpm.unwrap_or(120.0_f32);
|
|
20
|
-
let tpqn: u16 = tpqn.unwrap_or(480u16);
|
|
21
|
-
let header = Header {
|
|
22
|
-
format: Format::SingleTrack,
|
|
23
|
-
timing: Timing::Metrical(u15::from(tpqn)),
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
#[derive(Clone)]
|
|
27
|
-
struct AbsEvent {
|
|
28
|
-
tick: u64,
|
|
29
|
-
kind: TrackEventKind<'static>,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
let mut abs_events: Vec<AbsEvent> = Vec::new();
|
|
33
|
-
let microsecs_per_quarter = (60_000_000.0 / bpm) as u32;
|
|
34
|
-
abs_events.push(AbsEvent {
|
|
35
|
-
tick: 0,
|
|
36
|
-
kind: TrackEventKind::Meta(MetaMessage::Tempo(u24::from(microsecs_per_quarter))),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
for ev in midi_events {
|
|
40
|
-
let start_secs = (ev.start_ms as f32) / 1000.0;
|
|
41
|
-
let dur_secs = (ev.duration_ms as f32) / 1000.0;
|
|
42
|
-
let start_ticks_f = start_secs * (bpm / 60.0) * (tpqn as f32);
|
|
43
|
-
let dur_ticks_f = dur_secs * (bpm / 60.0) * (tpqn as f32);
|
|
44
|
-
let start_tick = start_ticks_f.max(0.0).round() as u64;
|
|
45
|
-
let off_tick = (start_ticks_f + dur_ticks_f).max(start_tick as f32).round() as u64;
|
|
46
|
-
|
|
47
|
-
let key = u7::from(ev.key.min(127));
|
|
48
|
-
let vel = u7::from(ev.vel.min(127));
|
|
49
|
-
|
|
50
|
-
abs_events.push(AbsEvent {
|
|
51
|
-
tick: start_tick,
|
|
52
|
-
kind: TrackEventKind::Midi {
|
|
53
|
-
channel: (ev.channel as u8).into(),
|
|
54
|
-
message: MidiMessage::NoteOn { key, vel },
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
abs_events.push(AbsEvent {
|
|
59
|
-
tick: off_tick,
|
|
60
|
-
kind: TrackEventKind::Midi {
|
|
61
|
-
channel: (ev.channel as u8).into(),
|
|
62
|
-
message: MidiMessage::NoteOff {
|
|
63
|
-
key,
|
|
64
|
-
vel: u7::from(0),
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
let max_tick = abs_events.iter().map(|e| e.tick).max().unwrap_or(0);
|
|
71
|
-
abs_events.push(AbsEvent {
|
|
72
|
-
tick: max_tick + 0,
|
|
73
|
-
kind: TrackEventKind::Meta(MetaMessage::EndOfTrack),
|
|
74
|
-
});
|
|
75
|
-
abs_events.sort_by_key(|e| e.tick);
|
|
76
|
-
|
|
77
|
-
let mut track: Vec<TrackEvent> = Vec::new();
|
|
78
|
-
let mut last_tick: u64 = 0;
|
|
79
|
-
for e in abs_events {
|
|
80
|
-
let delta = (e.tick - last_tick) as u32;
|
|
81
|
-
track.push(TrackEvent {
|
|
82
|
-
delta: delta.into(),
|
|
83
|
-
kind: e.kind,
|
|
84
|
-
});
|
|
85
|
-
last_tick = e.tick;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
let smf = Smf {
|
|
89
|
-
header,
|
|
90
|
-
tracks: vec![track],
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
if let Ok(mut file) = File::create(output_path) {
|
|
94
|
-
if let Err(e) = smf.write_std(&mut file) {
|
|
95
|
-
return Err(format!("Error writing MIDI file: {}", e));
|
|
96
|
-
}
|
|
97
|
-
} else {
|
|
98
|
-
return Err(format!("Cannot create MIDI file at {}", output_path));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
Ok(())
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
pub fn generate_wav_file_impl(
|
|
105
|
-
buffer: &mut Vec<i16>,
|
|
106
|
-
output_dir: &String,
|
|
107
|
-
audio_format: Option<String>,
|
|
108
|
-
sample_rate: Option<u32>,
|
|
109
|
-
) -> Result<(), String> {
|
|
110
|
-
if buffer.len() % (crate::core::audio::engine::CHANNELS as usize) != 0 {
|
|
111
|
-
buffer.push(0);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
let sr = sample_rate.unwrap_or(crate::core::audio::engine::SAMPLE_RATE);
|
|
115
|
-
let format_str = audio_format.unwrap_or_else(|| "Wav16".to_string());
|
|
116
|
-
let fmt_low = format_str.to_lowercase();
|
|
117
|
-
|
|
118
|
-
match fmt_low.as_str() {
|
|
119
|
-
"wav16" => {
|
|
120
|
-
let spec = hound::WavSpec {
|
|
121
|
-
channels: crate::core::audio::engine::CHANNELS,
|
|
122
|
-
sample_rate: sr,
|
|
123
|
-
bits_per_sample: 16,
|
|
124
|
-
sample_format: hound::SampleFormat::Int,
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
let mut writer = hound::WavWriter::create(output_dir, spec)
|
|
128
|
-
.map_err(|e| format!("Error creating WAV file: {}", e))?;
|
|
129
|
-
|
|
130
|
-
for sample in buffer.iter() {
|
|
131
|
-
writer
|
|
132
|
-
.write_sample(*sample)
|
|
133
|
-
.map_err(|e| format!("Error writing sample: {:?}", e))?;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
writer
|
|
137
|
-
.finalize()
|
|
138
|
-
.map_err(|e| format!("Error finalizing WAV: {:?}", e))?;
|
|
139
|
-
}
|
|
140
|
-
"wav24" | "wav32" => {
|
|
141
|
-
let bits = if fmt_low.contains("24") { 24 } else { 32 };
|
|
142
|
-
let spec = hound::WavSpec {
|
|
143
|
-
channels: crate::core::audio::engine::CHANNELS,
|
|
144
|
-
sample_rate: sr,
|
|
145
|
-
bits_per_sample: bits,
|
|
146
|
-
sample_format: hound::SampleFormat::Int,
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
let mut writer = hound::WavWriter::create(output_dir, spec)
|
|
150
|
-
.map_err(|e| format!("Error creating WAV file: {}", e))?;
|
|
151
|
-
|
|
152
|
-
for &s in buffer.iter() {
|
|
153
|
-
let v32 = (s as i32) << (bits - 16);
|
|
154
|
-
writer
|
|
155
|
-
.write_sample(v32)
|
|
156
|
-
.map_err(|e| format!("Error writing sample: {:?}", e))?;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
writer
|
|
160
|
-
.finalize()
|
|
161
|
-
.map_err(|e| format!("Error finalizing WAV: {:?}", e))?;
|
|
162
|
-
}
|
|
163
|
-
_ => {
|
|
164
|
-
return Err(format!("Unsupported audio format: {}", format_str));
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
Ok(())
|
|
169
|
-
}
|
|
1
|
+
use crate::core::audio::engine::driver::MidiNoteEvent;
|
|
2
|
+
use std::fs::File;
|
|
3
|
+
|
|
4
|
+
pub fn generate_midi_file_impl(
|
|
5
|
+
midi_events: &Vec<MidiNoteEvent>,
|
|
6
|
+
output_path: &String,
|
|
7
|
+
bpm: Option<f32>,
|
|
8
|
+
tpqn: Option<u16>,
|
|
9
|
+
) -> Result<(), String> {
|
|
10
|
+
use midly::num::{u7, u15, u24};
|
|
11
|
+
use midly::{
|
|
12
|
+
Format, Header, MetaMessage, MidiMessage, Smf, Timing, TrackEvent, TrackEventKind,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
if midi_events.is_empty() {
|
|
16
|
+
return Ok(());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let bpm = bpm.unwrap_or(120.0_f32);
|
|
20
|
+
let tpqn: u16 = tpqn.unwrap_or(480u16);
|
|
21
|
+
let header = Header {
|
|
22
|
+
format: Format::SingleTrack,
|
|
23
|
+
timing: Timing::Metrical(u15::from(tpqn)),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
#[derive(Clone)]
|
|
27
|
+
struct AbsEvent {
|
|
28
|
+
tick: u64,
|
|
29
|
+
kind: TrackEventKind<'static>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let mut abs_events: Vec<AbsEvent> = Vec::new();
|
|
33
|
+
let microsecs_per_quarter = (60_000_000.0 / bpm) as u32;
|
|
34
|
+
abs_events.push(AbsEvent {
|
|
35
|
+
tick: 0,
|
|
36
|
+
kind: TrackEventKind::Meta(MetaMessage::Tempo(u24::from(microsecs_per_quarter))),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
for ev in midi_events {
|
|
40
|
+
let start_secs = (ev.start_ms as f32) / 1000.0;
|
|
41
|
+
let dur_secs = (ev.duration_ms as f32) / 1000.0;
|
|
42
|
+
let start_ticks_f = start_secs * (bpm / 60.0) * (tpqn as f32);
|
|
43
|
+
let dur_ticks_f = dur_secs * (bpm / 60.0) * (tpqn as f32);
|
|
44
|
+
let start_tick = start_ticks_f.max(0.0).round() as u64;
|
|
45
|
+
let off_tick = (start_ticks_f + dur_ticks_f).max(start_tick as f32).round() as u64;
|
|
46
|
+
|
|
47
|
+
let key = u7::from(ev.key.min(127));
|
|
48
|
+
let vel = u7::from(ev.vel.min(127));
|
|
49
|
+
|
|
50
|
+
abs_events.push(AbsEvent {
|
|
51
|
+
tick: start_tick,
|
|
52
|
+
kind: TrackEventKind::Midi {
|
|
53
|
+
channel: (ev.channel as u8).into(),
|
|
54
|
+
message: MidiMessage::NoteOn { key, vel },
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
abs_events.push(AbsEvent {
|
|
59
|
+
tick: off_tick,
|
|
60
|
+
kind: TrackEventKind::Midi {
|
|
61
|
+
channel: (ev.channel as u8).into(),
|
|
62
|
+
message: MidiMessage::NoteOff {
|
|
63
|
+
key,
|
|
64
|
+
vel: u7::from(0),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let max_tick = abs_events.iter().map(|e| e.tick).max().unwrap_or(0);
|
|
71
|
+
abs_events.push(AbsEvent {
|
|
72
|
+
tick: max_tick + 0,
|
|
73
|
+
kind: TrackEventKind::Meta(MetaMessage::EndOfTrack),
|
|
74
|
+
});
|
|
75
|
+
abs_events.sort_by_key(|e| e.tick);
|
|
76
|
+
|
|
77
|
+
let mut track: Vec<TrackEvent> = Vec::new();
|
|
78
|
+
let mut last_tick: u64 = 0;
|
|
79
|
+
for e in abs_events {
|
|
80
|
+
let delta = (e.tick - last_tick) as u32;
|
|
81
|
+
track.push(TrackEvent {
|
|
82
|
+
delta: delta.into(),
|
|
83
|
+
kind: e.kind,
|
|
84
|
+
});
|
|
85
|
+
last_tick = e.tick;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let smf = Smf {
|
|
89
|
+
header,
|
|
90
|
+
tracks: vec![track],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if let Ok(mut file) = File::create(output_path) {
|
|
94
|
+
if let Err(e) = smf.write_std(&mut file) {
|
|
95
|
+
return Err(format!("Error writing MIDI file: {}", e));
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
return Err(format!("Cannot create MIDI file at {}", output_path));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Ok(())
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
pub fn generate_wav_file_impl(
|
|
105
|
+
buffer: &mut Vec<i16>,
|
|
106
|
+
output_dir: &String,
|
|
107
|
+
audio_format: Option<String>,
|
|
108
|
+
sample_rate: Option<u32>,
|
|
109
|
+
) -> Result<(), String> {
|
|
110
|
+
if buffer.len() % (crate::core::audio::engine::CHANNELS as usize) != 0 {
|
|
111
|
+
buffer.push(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let sr = sample_rate.unwrap_or(crate::core::audio::engine::SAMPLE_RATE);
|
|
115
|
+
let format_str = audio_format.unwrap_or_else(|| "Wav16".to_string());
|
|
116
|
+
let fmt_low = format_str.to_lowercase();
|
|
117
|
+
|
|
118
|
+
match fmt_low.as_str() {
|
|
119
|
+
"wav16" => {
|
|
120
|
+
let spec = hound::WavSpec {
|
|
121
|
+
channels: crate::core::audio::engine::CHANNELS,
|
|
122
|
+
sample_rate: sr,
|
|
123
|
+
bits_per_sample: 16,
|
|
124
|
+
sample_format: hound::SampleFormat::Int,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
let mut writer = hound::WavWriter::create(output_dir, spec)
|
|
128
|
+
.map_err(|e| format!("Error creating WAV file: {}", e))?;
|
|
129
|
+
|
|
130
|
+
for sample in buffer.iter() {
|
|
131
|
+
writer
|
|
132
|
+
.write_sample(*sample)
|
|
133
|
+
.map_err(|e| format!("Error writing sample: {:?}", e))?;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
writer
|
|
137
|
+
.finalize()
|
|
138
|
+
.map_err(|e| format!("Error finalizing WAV: {:?}", e))?;
|
|
139
|
+
}
|
|
140
|
+
"wav24" | "wav32" => {
|
|
141
|
+
let bits = if fmt_low.contains("24") { 24 } else { 32 };
|
|
142
|
+
let spec = hound::WavSpec {
|
|
143
|
+
channels: crate::core::audio::engine::CHANNELS,
|
|
144
|
+
sample_rate: sr,
|
|
145
|
+
bits_per_sample: bits,
|
|
146
|
+
sample_format: hound::SampleFormat::Int,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
let mut writer = hound::WavWriter::create(output_dir, spec)
|
|
150
|
+
.map_err(|e| format!("Error creating WAV file: {}", e))?;
|
|
151
|
+
|
|
152
|
+
for &s in buffer.iter() {
|
|
153
|
+
let v32 = (s as i32) << (bits - 16);
|
|
154
|
+
writer
|
|
155
|
+
.write_sample(v32)
|
|
156
|
+
.map_err(|e| format!("Error writing sample: {:?}", e))?;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
writer
|
|
160
|
+
.finalize()
|
|
161
|
+
.map_err(|e| format!("Error finalizing WAV: {:?}", e))?;
|
|
162
|
+
}
|
|
163
|
+
_ => {
|
|
164
|
+
return Err(format!("Unsupported audio format: {}", format_str));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
Ok(())
|
|
169
|
+
}
|
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
pub mod driver;
|
|
2
|
-
pub mod export;
|
|
3
|
-
pub mod helpers;
|
|
4
|
-
pub mod notes;
|
|
5
|
-
pub mod sample;
|
|
6
|
-
|
|
7
|
-
pub use driver::AudioEngine;
|
|
8
|
-
|
|
9
|
-
pub use driver::CHANNELS;
|
|
10
|
-
pub use driver::MidiNoteEvent;
|
|
11
|
-
pub use driver::SAMPLE_RATE;
|
|
12
|
-
pub use helpers::*;
|
|
13
|
-
|
|
14
|
-
use crate::core::audio::interpreter::driver::run_audio_program;
|
|
15
|
-
use crate::core::parser::statement::Statement;
|
|
16
|
-
use crate::core::store::global::GlobalStore;
|
|
17
|
-
use devalang_types::{FunctionTable, VariableTable};
|
|
18
|
-
use std::collections::HashMap;
|
|
19
|
-
|
|
20
|
-
/// Render audio for a set of parsed modules.
|
|
21
|
-
///
|
|
22
|
-
/// For each entry in `modules` (module path -> statements), create an
|
|
23
|
-
/// AudioEngine, setup empty module-local VariableTable and FunctionTable,
|
|
24
|
-
/// invoke the interpreter driver to schedule audio into the engine, and
|
|
25
|
-
/// return a map of module name -> AudioEngine.
|
|
26
|
-
pub fn render_audio_with_modules(
|
|
27
|
-
modules: HashMap<String, Vec<Statement>>,
|
|
28
|
-
_output_dir: &str,
|
|
29
|
-
global_store: &mut GlobalStore,
|
|
30
|
-
) -> HashMap<String, AudioEngine> {
|
|
31
|
-
let mut result: HashMap<String, AudioEngine> = HashMap::new();
|
|
32
|
-
|
|
33
|
-
for (module_name, statements) in modules.into_iter() {
|
|
34
|
-
// Create engine named after module (used for diagnostics)
|
|
35
|
-
let mut engine = AudioEngine::new(module_name.clone());
|
|
36
|
-
|
|
37
|
-
// Create empty module-local tables; interpreter expects these as starting context
|
|
38
|
-
let module_vars = VariableTable::new();
|
|
39
|
-
let module_funcs = FunctionTable::new();
|
|
40
|
-
|
|
41
|
-
// Run interpreter which will populate the engine.buffer and midi events
|
|
42
|
-
let (_max_end_time, _cursor_time) = run_audio_program(
|
|
43
|
-
&statements,
|
|
44
|
-
&mut engine,
|
|
45
|
-
module_name.clone(),
|
|
46
|
-
module_name.clone(),
|
|
47
|
-
module_vars,
|
|
48
|
-
module_funcs,
|
|
49
|
-
global_store,
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
result.insert(module_name, engine);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
result
|
|
56
|
-
}
|
|
1
|
+
pub mod driver;
|
|
2
|
+
pub mod export;
|
|
3
|
+
pub mod helpers;
|
|
4
|
+
pub mod notes;
|
|
5
|
+
pub mod sample;
|
|
6
|
+
|
|
7
|
+
pub use driver::AudioEngine;
|
|
8
|
+
|
|
9
|
+
pub use driver::CHANNELS;
|
|
10
|
+
pub use driver::MidiNoteEvent;
|
|
11
|
+
pub use driver::SAMPLE_RATE;
|
|
12
|
+
pub use helpers::*;
|
|
13
|
+
|
|
14
|
+
use crate::core::audio::interpreter::driver::run_audio_program;
|
|
15
|
+
use crate::core::parser::statement::Statement;
|
|
16
|
+
use crate::core::store::global::GlobalStore;
|
|
17
|
+
use devalang_types::{FunctionTable, VariableTable};
|
|
18
|
+
use std::collections::HashMap;
|
|
19
|
+
|
|
20
|
+
/// Render audio for a set of parsed modules.
|
|
21
|
+
///
|
|
22
|
+
/// For each entry in `modules` (module path -> statements), create an
|
|
23
|
+
/// AudioEngine, setup empty module-local VariableTable and FunctionTable,
|
|
24
|
+
/// invoke the interpreter driver to schedule audio into the engine, and
|
|
25
|
+
/// return a map of module name -> AudioEngine.
|
|
26
|
+
pub fn render_audio_with_modules(
|
|
27
|
+
modules: HashMap<String, Vec<Statement>>,
|
|
28
|
+
_output_dir: &str,
|
|
29
|
+
global_store: &mut GlobalStore,
|
|
30
|
+
) -> HashMap<String, AudioEngine> {
|
|
31
|
+
let mut result: HashMap<String, AudioEngine> = HashMap::new();
|
|
32
|
+
|
|
33
|
+
for (module_name, statements) in modules.into_iter() {
|
|
34
|
+
// Create engine named after module (used for diagnostics)
|
|
35
|
+
let mut engine = AudioEngine::new(module_name.clone());
|
|
36
|
+
|
|
37
|
+
// Create empty module-local tables; interpreter expects these as starting context
|
|
38
|
+
let module_vars = VariableTable::new();
|
|
39
|
+
let module_funcs = FunctionTable::new();
|
|
40
|
+
|
|
41
|
+
// Run interpreter which will populate the engine.buffer and midi events
|
|
42
|
+
let (_max_end_time, _cursor_time) = run_audio_program(
|
|
43
|
+
&statements,
|
|
44
|
+
&mut engine,
|
|
45
|
+
module_name.clone(),
|
|
46
|
+
module_name.clone(),
|
|
47
|
+
module_vars,
|
|
48
|
+
module_funcs,
|
|
49
|
+
global_store,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
result.insert(module_name, engine);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
result
|
|
56
|
+
}
|
|
@@ -1,85 +1,88 @@
|
|
|
1
|
-
use crate::core::audio::engine::notes::params::NoteSetup;
|
|
2
|
-
use devalang_types::Value;
|
|
3
|
-
use std::collections::HashMap;
|
|
4
|
-
|
|
5
|
-
pub fn render_notes_into_buffer(
|
|
6
|
-
engine: &mut crate::core::audio::engine::AudioEngine,
|
|
7
|
-
_waveform: &str,
|
|
8
|
-
_freq: f32,
|
|
9
|
-
_amp: f32,
|
|
10
|
-
_start_time_ms: f32,
|
|
11
|
-
_duration_ms: f32,
|
|
12
|
-
_synth_params: HashMap<String, Value>,
|
|
13
|
-
_note_params: HashMap<String, Value>,
|
|
14
|
-
_automation: Option<HashMap<String, Value>>,
|
|
15
|
-
setup: NoteSetup,
|
|
16
|
-
) {
|
|
17
|
-
use crate::core::audio::engine::helpers;
|
|
18
|
-
|
|
19
|
-
let sample_rate = setup.sample_rate;
|
|
20
|
-
let channels = setup.channels;
|
|
21
|
-
let total_samples = setup.total_samples;
|
|
22
|
-
let start_sample = setup.start_sample;
|
|
23
|
-
|
|
24
|
-
let mut stereo_samples: Vec<i16> = Vec::with_capacity(total_samples * channels);
|
|
25
|
-
let fade_len = (sample_rate * 0.01) as usize; // 10ms fade
|
|
26
|
-
|
|
27
|
-
for i in 0..total_samples {
|
|
28
|
-
let t = ((start_sample + i) as f32) / sample_rate;
|
|
29
|
-
|
|
30
|
-
// simplified voice/unison and oscillator sampling
|
|
31
|
-
let mut value = helpers::oscillator_sample(_waveform, _freq, t);
|
|
32
|
-
|
|
33
|
-
// apply ADSR envelope
|
|
34
|
-
let envelope = helpers::adsr_envelope_value(
|
|
35
|
-
i,
|
|
36
|
-
setup.attack_samples,
|
|
37
|
-
setup.decay_samples,
|
|
38
|
-
if total_samples > setup.attack_samples + setup.decay_samples + setup.release_samples {
|
|
39
|
-
total_samples - setup.attack_samples - setup.decay_samples - setup.release_samples
|
|
40
|
-
} else {
|
|
41
|
-
0
|
|
42
|
-
},
|
|
43
|
-
setup.release_samples,
|
|
44
|
-
setup.sustain_level,
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
value *= envelope * (i16::MAX as f32) * _amp;
|
|
48
|
-
|
|
49
|
-
if fade_len > 0 && i < fade_len {
|
|
50
|
-
if fade_len == 1 {
|
|
51
|
-
value *= 0.0;
|
|
52
|
-
} else {
|
|
53
|
-
value *= (i as f32) / (fade_len as f32);
|
|
54
|
-
}
|
|
55
|
-
} else if fade_len > 0 && i >= total_samples.saturating_sub(fade_len) {
|
|
56
|
-
if fade_len == 1 {
|
|
57
|
-
value *= 0.0;
|
|
58
|
-
} else {
|
|
59
|
-
value *= ((total_samples - 1 - i) as f32) / ((fade_len - 1) as f32);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
let (left_gain, right_gain) = helpers::pan_gains(0.0);
|
|
64
|
-
let left = (value * left_gain)
|
|
65
|
-
.round()
|
|
66
|
-
.clamp(i16::MIN as f32, i16::MAX as f32) as i16;
|
|
67
|
-
let right = (value * right_gain)
|
|
68
|
-
.round()
|
|
69
|
-
.clamp(i16::MIN as f32, i16::MAX as f32) as i16;
|
|
70
|
-
|
|
71
|
-
// push samples interleaved for channels=2, fallback zeros for other channel counts
|
|
72
|
-
if channels >= 2 {
|
|
73
|
-
stereo_samples.push(left);
|
|
74
|
-
stereo_samples.push(right);
|
|
75
|
-
} else if channels == 1 {
|
|
76
|
-
stereo_samples.push(((left as i32 + right as i32) / 2) as i16);
|
|
77
|
-
} else {
|
|
78
|
-
stereo_samples.push(left);
|
|
79
|
-
stereo_samples.push(right);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
engine.note_count = engine.note_count.saturating_add(1);
|
|
84
|
-
helpers::mix_stereo_samples_into_buffer(engine, start_sample, channels, &stereo_samples);
|
|
85
|
-
|
|
1
|
+
use crate::core::audio::engine::notes::params::NoteSetup;
|
|
2
|
+
use devalang_types::Value;
|
|
3
|
+
use std::collections::HashMap;
|
|
4
|
+
|
|
5
|
+
pub fn render_notes_into_buffer(
|
|
6
|
+
engine: &mut crate::core::audio::engine::AudioEngine,
|
|
7
|
+
_waveform: &str,
|
|
8
|
+
_freq: f32,
|
|
9
|
+
_amp: f32,
|
|
10
|
+
_start_time_ms: f32,
|
|
11
|
+
_duration_ms: f32,
|
|
12
|
+
_synth_params: HashMap<String, Value>,
|
|
13
|
+
_note_params: HashMap<String, Value>,
|
|
14
|
+
_automation: Option<HashMap<String, Value>>,
|
|
15
|
+
setup: NoteSetup,
|
|
16
|
+
) -> Vec<(usize, usize)> {
|
|
17
|
+
use crate::core::audio::engine::helpers;
|
|
18
|
+
|
|
19
|
+
let sample_rate = setup.sample_rate;
|
|
20
|
+
let channels = setup.channels;
|
|
21
|
+
let total_samples = setup.total_samples;
|
|
22
|
+
let start_sample = setup.start_sample;
|
|
23
|
+
|
|
24
|
+
let mut stereo_samples: Vec<i16> = Vec::with_capacity(total_samples * channels);
|
|
25
|
+
let fade_len = (sample_rate * 0.01) as usize; // 10ms fade
|
|
26
|
+
|
|
27
|
+
for i in 0..total_samples {
|
|
28
|
+
let t = ((start_sample + i) as f32) / sample_rate;
|
|
29
|
+
|
|
30
|
+
// simplified voice/unison and oscillator sampling
|
|
31
|
+
let mut value = helpers::oscillator_sample(_waveform, _freq, t);
|
|
32
|
+
|
|
33
|
+
// apply ADSR envelope
|
|
34
|
+
let envelope = helpers::adsr_envelope_value(
|
|
35
|
+
i,
|
|
36
|
+
setup.attack_samples,
|
|
37
|
+
setup.decay_samples,
|
|
38
|
+
if total_samples > setup.attack_samples + setup.decay_samples + setup.release_samples {
|
|
39
|
+
total_samples - setup.attack_samples - setup.decay_samples - setup.release_samples
|
|
40
|
+
} else {
|
|
41
|
+
0
|
|
42
|
+
},
|
|
43
|
+
setup.release_samples,
|
|
44
|
+
setup.sustain_level,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
value *= envelope * (i16::MAX as f32) * _amp;
|
|
48
|
+
|
|
49
|
+
if fade_len > 0 && i < fade_len {
|
|
50
|
+
if fade_len == 1 {
|
|
51
|
+
value *= 0.0;
|
|
52
|
+
} else {
|
|
53
|
+
value *= (i as f32) / (fade_len as f32);
|
|
54
|
+
}
|
|
55
|
+
} else if fade_len > 0 && i >= total_samples.saturating_sub(fade_len) {
|
|
56
|
+
if fade_len == 1 {
|
|
57
|
+
value *= 0.0;
|
|
58
|
+
} else {
|
|
59
|
+
value *= ((total_samples - 1 - i) as f32) / ((fade_len - 1) as f32);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let (left_gain, right_gain) = helpers::pan_gains(0.0);
|
|
64
|
+
let left = (value * left_gain)
|
|
65
|
+
.round()
|
|
66
|
+
.clamp(i16::MIN as f32, i16::MAX as f32) as i16;
|
|
67
|
+
let right = (value * right_gain)
|
|
68
|
+
.round()
|
|
69
|
+
.clamp(i16::MIN as f32, i16::MAX as f32) as i16;
|
|
70
|
+
|
|
71
|
+
// push samples interleaved for channels=2, fallback zeros for other channel counts
|
|
72
|
+
if channels >= 2 {
|
|
73
|
+
stereo_samples.push(left);
|
|
74
|
+
stereo_samples.push(right);
|
|
75
|
+
} else if channels == 1 {
|
|
76
|
+
stereo_samples.push(((left as i32 + right as i32) / 2) as i16);
|
|
77
|
+
} else {
|
|
78
|
+
stereo_samples.push(left);
|
|
79
|
+
stereo_samples.push(right);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
engine.note_count = engine.note_count.saturating_add(1);
|
|
84
|
+
helpers::mix_stereo_samples_into_buffer(engine, start_sample, channels, &stereo_samples);
|
|
85
|
+
|
|
86
|
+
// Return the inserted sample range for this note (start_sample, total_samples)
|
|
87
|
+
vec![(start_sample, stereo_samples.len())]
|
|
88
|
+
}
|