very wip
This commit is contained in:
678
notes
678
notes
@@ -7,7 +7,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"active": true,
|
||||
"script": "< < c5 c1 > m3 M3 m6 > note\n\"sine\" sound\n0.2 decay @",
|
||||
"script": "0.2 0.9 rand gain 0.2 decay\n0.1 dur 1 4 rand fm 0.5 fmh\n\"tri\" < << c3 c2 >> 4 ! g3 > note sound @",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
@@ -25,6 +25,340 @@
|
||||
"script": "",
|
||||
"source": 0
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "0.2 0.9 rand gain 0.2 decay\n0.1 dur 1 4 rand fm 0.5 fmh\n0.5 0.9 rand verb \n< << c3 f3 >> 4 ! eb3 M3 > note \"tri\" sound @",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": 0
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": 0
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": 0
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
}
|
||||
],
|
||||
"length": 8,
|
||||
"speed": "Normal",
|
||||
"name": null
|
||||
},
|
||||
{
|
||||
"steps": [
|
||||
{
|
||||
"active": true,
|
||||
"script": "\"ikick\" sound 2 gain @",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
}
|
||||
],
|
||||
"length": 4,
|
||||
"speed": "Normal",
|
||||
"name": null
|
||||
},
|
||||
{
|
||||
"steps": [
|
||||
{
|
||||
"active": true,
|
||||
"script": "\"hh\" sound 0.5 comb 400 combfreq @\n4 scale!\n{\n\"noise\" sound 0.05 dur 0.0 1.0 rand gain 0.05 decay 1 100 rand @\n} 2 div for",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": 0
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "<< \"hh\" 4 ! \"crbongo\" >> sound @\n{ \"snare\" sound 0.2 dur 0.1 decay 200 freq @ } 2 every ?",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": 0
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
@@ -174,7 +508,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"script": " {\n {\n | eb3 g3 bb3 c4 | note 0.1 decay 0.1 dur \"tri\" sound\n 0.5 fmh 0.5 delay 4.5 delaytime 0.75 delayfeedback\n 2 fm 0.9 verb 4 orbit @\n } 4 every ? \n} 4 stack for",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
@@ -333,341 +667,7 @@
|
||||
"source": null
|
||||
}
|
||||
],
|
||||
"length": 16,
|
||||
"speed": "Normal",
|
||||
"name": null
|
||||
},
|
||||
{
|
||||
"steps": [
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
}
|
||||
],
|
||||
"length": 16,
|
||||
"speed": "Normal",
|
||||
"name": null
|
||||
},
|
||||
{
|
||||
"steps": [
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"script": "",
|
||||
"source": null
|
||||
}
|
||||
],
|
||||
"length": 16,
|
||||
"length": 4,
|
||||
"speed": "Normal",
|
||||
"name": null
|
||||
},
|
||||
@@ -42834,6 +42834,8 @@
|
||||
"name": null
|
||||
}
|
||||
],
|
||||
"sample_paths": [],
|
||||
"sample_paths": [
|
||||
"/Users/bubo/Documents/samples"
|
||||
],
|
||||
"tempo": 110.0
|
||||
}
|
||||
@@ -31,7 +31,6 @@ pub struct App {
|
||||
pub patterns_nav: PatternsNav,
|
||||
|
||||
pub metrics: Metrics,
|
||||
pub sample_pool_mb: f32,
|
||||
pub script_engine: ScriptEngine,
|
||||
pub variables: Variables,
|
||||
pub rng: Rng,
|
||||
@@ -61,7 +60,6 @@ impl App {
|
||||
patterns_nav: PatternsNav::default(),
|
||||
|
||||
metrics: Metrics::default(),
|
||||
sample_pool_mb: 0.0,
|
||||
variables,
|
||||
rng,
|
||||
live_keys,
|
||||
@@ -280,7 +278,6 @@ impl App {
|
||||
let ctx = StepContext {
|
||||
step: step_idx,
|
||||
beat: link.beat(),
|
||||
bank,
|
||||
pattern,
|
||||
tempo: link.tempo(),
|
||||
phase: link.phase(),
|
||||
@@ -355,7 +352,6 @@ impl App {
|
||||
let ctx = StepContext {
|
||||
step: step_idx,
|
||||
beat: 0.0,
|
||||
bank,
|
||||
pattern,
|
||||
tempo: link.tempo(),
|
||||
phase: 0.0,
|
||||
|
||||
@@ -87,13 +87,13 @@ pub struct ActivePatternState {
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SharedSequencerState {
|
||||
pub active_patterns: Vec<ActivePatternState>,
|
||||
pub pattern_traces: HashMap<PatternId, Vec<SourceSpan>>,
|
||||
pub step_traces: HashMap<(usize, usize, usize), Vec<SourceSpan>>,
|
||||
pub event_count: usize,
|
||||
}
|
||||
|
||||
pub struct SequencerSnapshot {
|
||||
pub active_patterns: Vec<ActivePatternState>,
|
||||
pub pattern_traces: HashMap<PatternId, Vec<SourceSpan>>,
|
||||
pub step_traces: HashMap<(usize, usize, usize), Vec<SourceSpan>>,
|
||||
pub event_count: usize,
|
||||
}
|
||||
|
||||
@@ -118,8 +118,8 @@ impl SequencerSnapshot {
|
||||
.map(|p| p.iter)
|
||||
}
|
||||
|
||||
pub fn get_trace(&self, bank: usize, pattern: usize) -> Option<&Vec<SourceSpan>> {
|
||||
self.pattern_traces.get(&PatternId { bank, pattern })
|
||||
pub fn get_trace(&self, bank: usize, pattern: usize, step: usize) -> Option<&Vec<SourceSpan>> {
|
||||
self.step_traces.get(&(bank, pattern, step))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ impl SequencerHandle {
|
||||
let state = self.shared_state.lock().unwrap();
|
||||
SequencerSnapshot {
|
||||
active_patterns: state.active_patterns.clone(),
|
||||
pattern_traces: state.pattern_traces.clone(),
|
||||
step_traces: state.step_traces.clone(),
|
||||
event_count: state.event_count,
|
||||
}
|
||||
}
|
||||
@@ -284,6 +284,7 @@ impl RunsCounter {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn sequencer_loop(
|
||||
cmd_rx: Receiver<SeqCommand>,
|
||||
audio_tx: Sender<AudioCommand>,
|
||||
@@ -301,7 +302,7 @@ fn sequencer_loop(
|
||||
let mut audio_state = AudioState::new();
|
||||
let mut pattern_cache = PatternCache::new();
|
||||
let mut runs_counter = RunsCounter::new();
|
||||
let mut pattern_traces: HashMap<PatternId, Vec<SourceSpan>> = HashMap::new();
|
||||
let mut step_traces: HashMap<(usize, usize, usize), Vec<SourceSpan>> = HashMap::new();
|
||||
let mut event_count: usize = 0;
|
||||
|
||||
loop {
|
||||
@@ -360,13 +361,15 @@ fn sequencer_loop(
|
||||
}
|
||||
for id in audio_state.pending_stops.drain(..) {
|
||||
audio_state.active_patterns.remove(&id);
|
||||
pattern_traces.remove(&id);
|
||||
step_traces.retain(|&(bank, pattern, _), _| {
|
||||
bank != id.bank || pattern != id.pattern
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let prev_beat = audio_state.prev_beat;
|
||||
|
||||
for (id, active) in audio_state.active_patterns.iter_mut() {
|
||||
for (_id, active) in audio_state.active_patterns.iter_mut() {
|
||||
let Some(pattern) = pattern_cache.get(active.bank, active.pattern) else {
|
||||
continue;
|
||||
};
|
||||
@@ -391,7 +394,6 @@ fn sequencer_loop(
|
||||
let ctx = StepContext {
|
||||
step: step_idx,
|
||||
beat,
|
||||
bank: active.bank,
|
||||
pattern: active.pattern,
|
||||
tempo,
|
||||
phase: beat % quantum,
|
||||
@@ -406,8 +408,10 @@ fn sequencer_loop(
|
||||
if let Ok(cmds) =
|
||||
script_engine.evaluate_with_trace(script, &ctx, &mut trace)
|
||||
{
|
||||
pattern_traces
|
||||
.insert(*id, std::mem::take(&mut trace.selected_spans));
|
||||
step_traces.insert(
|
||||
(active.bank, active.pattern, step_idx),
|
||||
std::mem::take(&mut trace.selected_spans),
|
||||
);
|
||||
for cmd in cmds {
|
||||
match audio_tx.try_send(AudioCommand::Evaluate(cmd)) {
|
||||
Ok(()) => {
|
||||
@@ -450,7 +454,7 @@ fn sequencer_loop(
|
||||
iter: a.iter,
|
||||
})
|
||||
.collect();
|
||||
state.pattern_traces = pattern_traces.clone();
|
||||
state.step_traces = step_traces.clone();
|
||||
state.event_count = event_count;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ pub struct ExecutionTrace {
|
||||
pub struct StepContext {
|
||||
pub step: usize,
|
||||
pub beat: f64,
|
||||
pub bank: usize,
|
||||
pub pattern: usize,
|
||||
pub tempo: f64,
|
||||
pub phase: f64,
|
||||
@@ -1751,7 +1750,7 @@ fn parse_interval(name: &str) -> Option<i64> {
|
||||
Some(simple)
|
||||
}
|
||||
|
||||
fn compile_word(name: &str, ops: &mut Vec<Op>) -> bool {
|
||||
fn compile_word(name: &str, span: Option<SourceSpan>, ops: &mut Vec<Op>) -> bool {
|
||||
for word in WORDS {
|
||||
if word.name == name {
|
||||
match &word.compile {
|
||||
@@ -1762,7 +1761,7 @@ fn compile_word(name: &str, ops: &mut Vec<Op>) -> bool {
|
||||
}
|
||||
Context(ctx) => ops.push(Op::GetContext((*ctx).into())),
|
||||
Param => ops.push(Op::SetParam(name.into())),
|
||||
Alias(target) => return compile_word(target, ops),
|
||||
Alias(target) => return compile_word(target, span, ops),
|
||||
Probability(p) => {
|
||||
ops.push(Op::PushFloat(*p, None));
|
||||
ops.push(Op::ChanceExec);
|
||||
@@ -1775,7 +1774,7 @@ fn compile_word(name: &str, ops: &mut Vec<Op>) -> bool {
|
||||
// @varname - fetch variable
|
||||
if let Some(var_name) = name.strip_prefix('@') {
|
||||
if !var_name.is_empty() {
|
||||
ops.push(Op::PushStr(var_name.to_string(), None));
|
||||
ops.push(Op::PushStr(var_name.to_string(), span));
|
||||
ops.push(Op::Get);
|
||||
return true;
|
||||
}
|
||||
@@ -1784,7 +1783,7 @@ fn compile_word(name: &str, ops: &mut Vec<Op>) -> bool {
|
||||
// !varname - store into variable
|
||||
if let Some(var_name) = name.strip_prefix('!') {
|
||||
if !var_name.is_empty() {
|
||||
ops.push(Op::PushStr(var_name.to_string(), None));
|
||||
ops.push(Op::PushStr(var_name.to_string(), span));
|
||||
ops.push(Op::Set);
|
||||
return true;
|
||||
}
|
||||
@@ -1792,14 +1791,14 @@ fn compile_word(name: &str, ops: &mut Vec<Op>) -> bool {
|
||||
|
||||
// Note names: c4, c#4, cs4, eb4, etc. -> MIDI number
|
||||
if let Some(midi) = parse_note_name(name) {
|
||||
ops.push(Op::PushInt(midi, None));
|
||||
ops.push(Op::PushInt(midi, span));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Intervals: m3, M3, P5, etc. -> dup top, add semitones (for chord building)
|
||||
if let Some(semitones) = parse_interval(name) {
|
||||
ops.push(Op::Dup);
|
||||
ops.push(Op::PushInt(semitones, None));
|
||||
ops.push(Op::PushInt(semitones, span));
|
||||
ops.push(Op::Add);
|
||||
return true;
|
||||
}
|
||||
@@ -1827,8 +1826,8 @@ enum Token {
|
||||
Float(f64, SourceSpan),
|
||||
Str(String, SourceSpan),
|
||||
Word(String, SourceSpan),
|
||||
QuoteStart(SourceSpan),
|
||||
QuoteEnd(SourceSpan),
|
||||
QuoteStart,
|
||||
QuoteEnd,
|
||||
}
|
||||
|
||||
fn tokenize(input: &str) -> Vec<Token> {
|
||||
@@ -1869,22 +1868,14 @@ fn tokenize(input: &str) -> Vec<Token> {
|
||||
}
|
||||
|
||||
if c == '{' {
|
||||
let start = pos;
|
||||
chars.next();
|
||||
tokens.push(Token::QuoteStart(SourceSpan {
|
||||
start,
|
||||
end: start + 1,
|
||||
}));
|
||||
tokens.push(Token::QuoteStart);
|
||||
continue;
|
||||
}
|
||||
|
||||
if c == '}' {
|
||||
let start = pos;
|
||||
chars.next();
|
||||
tokens.push(Token::QuoteEnd(SourceSpan {
|
||||
start,
|
||||
end: start + 1,
|
||||
}));
|
||||
tokens.push(Token::QuoteEnd);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1923,15 +1914,15 @@ fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
|
||||
Token::Int(n, span) => ops.push(Op::PushInt(*n, Some(*span))),
|
||||
Token::Float(f, span) => ops.push(Op::PushFloat(*f, Some(*span))),
|
||||
Token::Str(s, span) => ops.push(Op::PushStr(s.clone(), Some(*span))),
|
||||
Token::QuoteStart(_) => {
|
||||
Token::QuoteStart => {
|
||||
let (quote_ops, consumed) = compile_quotation(&tokens[i + 1..])?;
|
||||
i += consumed;
|
||||
ops.push(Op::Quotation(quote_ops));
|
||||
}
|
||||
Token::QuoteEnd(_) => {
|
||||
Token::QuoteEnd => {
|
||||
return Err("unexpected }".into());
|
||||
}
|
||||
Token::Word(w, _) => {
|
||||
Token::Word(w, span) => {
|
||||
let word = w.as_str();
|
||||
if word == "|" {
|
||||
if pipe_parity {
|
||||
@@ -1952,7 +1943,7 @@ fn compile(tokens: &[Token]) -> Result<Vec<Op>, String> {
|
||||
ops.push(Op::Branch(else_ops.len()));
|
||||
ops.extend(else_ops);
|
||||
}
|
||||
} else if !compile_word(word, &mut ops) {
|
||||
} else if !compile_word(word, Some(*span), &mut ops) {
|
||||
return Err(format!("unknown word: {word}"));
|
||||
}
|
||||
}
|
||||
@@ -1969,8 +1960,8 @@ fn compile_quotation(tokens: &[Token]) -> Result<(Vec<Op>, usize), String> {
|
||||
|
||||
for (i, tok) in tokens.iter().enumerate() {
|
||||
match tok {
|
||||
Token::QuoteStart(_) => depth += 1,
|
||||
Token::QuoteEnd(_) => {
|
||||
Token::QuoteStart => depth += 1,
|
||||
Token::QuoteEnd => {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
end_pos = Some(i);
|
||||
@@ -2039,10 +2030,12 @@ impl Forth {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn stack(&self) -> Vec<Value> {
|
||||
self.stack.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn clear_stack(&self) {
|
||||
self.stack.lock().unwrap().clear();
|
||||
}
|
||||
@@ -2104,6 +2097,7 @@ impl Forth {
|
||||
Ok(outputs)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn execute_ops(
|
||||
&self,
|
||||
ops: &[Op],
|
||||
|
||||
@@ -13,6 +13,9 @@ pub enum TokenKind {
|
||||
Sound,
|
||||
Param,
|
||||
Context,
|
||||
Note,
|
||||
Interval,
|
||||
Variable,
|
||||
Default,
|
||||
}
|
||||
|
||||
@@ -28,6 +31,9 @@ impl TokenKind {
|
||||
TokenKind::Sound => Style::default().fg(Color::Rgb(100, 220, 200)),
|
||||
TokenKind::Param => Style::default().fg(Color::Rgb(180, 150, 220)),
|
||||
TokenKind::Context => Style::default().fg(Color::Rgb(220, 180, 120)),
|
||||
TokenKind::Note => Style::default().fg(Color::Rgb(120, 200, 160)),
|
||||
TokenKind::Interval => Style::default().fg(Color::Rgb(160, 200, 120)),
|
||||
TokenKind::Variable => Style::default().fg(Color::Rgb(200, 140, 180)),
|
||||
TokenKind::Default => Style::default().fg(Color::Rgb(200, 200, 200)),
|
||||
}
|
||||
}
|
||||
@@ -39,18 +45,21 @@ pub struct Token {
|
||||
pub kind: TokenKind,
|
||||
}
|
||||
|
||||
const STACK_OPS: &[&str] = &["dup", "drop", "swap", "over", "rot", "nip", "tuck"];
|
||||
const STACK_OPS: &[&str] = &["dup", "dupn", "drop", "swap", "over", "rot", "nip", "tuck"];
|
||||
const OPERATORS: &[&str] = &[
|
||||
"+", "-", "*", "/", "mod", "neg", "abs", "min", "max", "=", "<>", "<", ">", "<=", ">=", "and",
|
||||
"or", "not",
|
||||
"or", "not", "ceil", "floor", "round", "mtof", "ftom",
|
||||
];
|
||||
const KEYWORDS: &[&str] = &[
|
||||
"if", "else", "then", "emit", "get", "set", "rand", "rrand", "seed", "cycle", "choose",
|
||||
"chance", "[", "]",
|
||||
"if", "else", "then", "emit", "rand", "rrand", "seed", "cycle", "choose", "chance", "[", "]",
|
||||
"zoom", "scale!", "stack", "echo", "necho", "for", "div", "each", "at", "pop", "adsr", "ad",
|
||||
"?", "!?", "<<", ">>", "|", "@", "!", "pcycle", "tempo!", "prob", "sometimes", "often",
|
||||
"rarely", "almostAlways", "almostNever", "always", "never", "coin", "fill", "iter", "every",
|
||||
"gt", "lt",
|
||||
];
|
||||
const SOUND: &[&str] = &["sound", "s"];
|
||||
const CONTEXT: &[&str] = &[
|
||||
"step", "beat", "bank", "pattern", "tempo", "phase", "slot", "runs",
|
||||
"step", "beat", "bank", "pattern", "tempo", "phase", "slot", "runs", "stepdur",
|
||||
];
|
||||
const PARAMS: &[&str] = &[
|
||||
"time",
|
||||
@@ -160,6 +169,28 @@ const PARAMS: &[&str] = &[
|
||||
"cut",
|
||||
"reset",
|
||||
];
|
||||
const INTERVALS: &[&str] = &[
|
||||
"P1", "unison", "m2", "M2", "m3", "M3", "P4", "aug4", "dim5", "tritone", "P5", "m6", "M6",
|
||||
"m7", "M7", "P8", "oct", "m9", "M9", "m10", "M10", "P11", "aug11", "P12", "m13", "M13", "m14",
|
||||
"M14", "P15",
|
||||
];
|
||||
|
||||
fn is_note(word: &str) -> bool {
|
||||
let bytes = word.as_bytes();
|
||||
if bytes.len() < 2 {
|
||||
return false;
|
||||
}
|
||||
if !matches!(bytes[0], b'a'..=b'g' | b'A'..=b'G') {
|
||||
return false;
|
||||
}
|
||||
let rest = &bytes[1..];
|
||||
let digits_start = if rest.first().is_some_and(|&b| b == b'#' || b == b's' || b == b'b') {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
rest[digits_start..].iter().all(|&b| b.is_ascii_digit()) && digits_start < rest.len()
|
||||
}
|
||||
|
||||
pub fn tokenize_line(line: &str) -> Vec<Token> {
|
||||
let mut tokens = Vec::new();
|
||||
@@ -252,6 +283,18 @@ fn classify_word(word: &str) -> TokenKind {
|
||||
return TokenKind::Param;
|
||||
}
|
||||
|
||||
if INTERVALS.contains(&word) {
|
||||
return TokenKind::Interval;
|
||||
}
|
||||
|
||||
if is_note(word) {
|
||||
return TokenKind::Note;
|
||||
}
|
||||
|
||||
if word.len() > 1 && (word.starts_with('@') || word.starts_with('!')) {
|
||||
return TokenKind::Variable;
|
||||
}
|
||||
|
||||
TokenKind::Default
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ fn render_step_preview(frame: &mut Frame, app: &App, snapshot: &SequencerSnapsho
|
||||
}
|
||||
|
||||
let runtime_spans = if app.ui.runtime_highlight && app.playback.playing {
|
||||
snapshot.get_trace(app.editor_ctx.bank, app.editor_ctx.pattern)
|
||||
snapshot.get_trace(app.editor_ctx.bank, app.editor_ctx.pattern, step_idx)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -362,7 +362,7 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
let (cursor_row, cursor_col) = app.editor_ctx.text.cursor();
|
||||
|
||||
let runtime_spans = if app.ui.runtime_highlight && app.playback.playing {
|
||||
snapshot.get_trace(app.editor_ctx.bank, app.editor_ctx.pattern)
|
||||
snapshot.get_trace(app.editor_ctx.bank, app.editor_ctx.pattern, app.editor_ctx.step)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ pub fn default_ctx() -> StepContext {
|
||||
StepContext {
|
||||
step: 0,
|
||||
beat: 0.0,
|
||||
bank: 0,
|
||||
pattern: 0,
|
||||
tempo: 120.0,
|
||||
phase: 0.0,
|
||||
@@ -127,6 +126,7 @@ pub fn expect_outputs(script: &str, count: usize) -> Vec<String> {
|
||||
outputs
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn expect_output_contains(script: &str, substr: &str) {
|
||||
let outputs = expect_outputs(script, 1);
|
||||
assert!(
|
||||
|
||||
@@ -46,7 +46,7 @@ fn multiple_emits() {
|
||||
|
||||
#[test]
|
||||
fn subdivide_each() {
|
||||
let outputs = expect_outputs(r#""kick" s 4 div each"#, 4);
|
||||
let _outputs = expect_outputs(r#""kick" s 4 div each"#, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user