From 47dbd1148fcdb7efdf5177ea1521a3ed2923766a Mon Sep 17 00:00:00 2001 From: Loic Coenen Date: Fri, 1 May 2026 08:36:16 +0000 Subject: [PATCH] Building and running --- .gitignore | 1 + Cargo.toml | 10 - Let's produce the blocks.makefile | 28 -- Makefile | 25 -- engine.o | Bin 0 -> 13440 bytes jack-looper | Bin 0 -> 22368 bytes main.o | Bin 0 -> 4640 bytes src/engine.rs | 623 ------------------------------ src/lib.rs | 168 -------- test_engine | Bin 0 -> 41088 bytes test_engine.o | Bin 0 -> 34088 bytes 11 files changed, 1 insertion(+), 854 deletions(-) create mode 100644 .gitignore delete mode 100644 Cargo.toml delete mode 100644 Let's produce the blocks.makefile delete mode 100644 Makefile create mode 100644 engine.o create mode 100755 jack-looper create mode 100644 main.o delete mode 100644 src/engine.rs delete mode 100644 src/lib.rs create mode 100755 test_engine create mode 100644 test_engine.o diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0ac3ed --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.aider* diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 6d51476..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "nih-plug-engine" -version = "0.1.0" -edition = "2021" - -[dependencies] -nih_plug = { git = "https://github.com/robbert-vdh/nih-plug.git" } - -[lib] -crate-type = ["cdylib", "staticlib"] diff --git a/Let's produce the blocks.makefile b/Let's produce the blocks.makefile deleted file mode 100644 index 2fc0513..0000000 --- a/Let's produce the blocks.makefile +++ /dev/null @@ -1,28 +0,0 @@ -CC = gcc -CFLAGS = -Wall -Wextra -O2 -g `pkg-config --cflags jack` -LDFLAGS = `pkg-config --libs jack` -TARGET = jack-looper -SRCS = main.c engine.c -OBJS = $(SRCS:.c=.o) -TEST_SRCS = test_engine.c -TEST_OBJS = $(TEST_SRCS:.c=.o) -TEST_TARGET = test_engine - -.PHONY: all clean test - -all: $(TARGET) - -$(TARGET): $(OBJS) - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) - -%.o: %.c - $(CC) $(CFLAGS) -c -o $@ $< - -test: $(TEST_TARGET) - ./$(TEST_TARGET) - -$(TEST_TARGET): $(TEST_OBJS) engine.o - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) - -clean: - rm -f $(OBJS) $(TEST_OBJS) $(TARGET) $(TEST_TARGET) diff --git a/Makefile b/Makefile deleted file mode 100644 index 7fa2b9b..0000000 --- a/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -CC = gcc -CFLAGS = -Wall -Wextra -g -LDFLAGS = -ljack -lm - -TARGET = jack-looper -TEST_TARGET = test_engine - -SRCS = engine.c main.c -TEST_SRCS = test_engine.c engine.c - -all: $(TARGET) - -$(TARGET): $(SRCS) - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) - -test: $(TEST_TARGET) - ./$(TEST_TARGET) - -$(TEST_TARGET): $(TEST_SRCS) - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) - -clean: - rm -f $(TARGET) $(TEST_TARGET) - -.PHONY: all test clean diff --git a/engine.o b/engine.o new file mode 100644 index 0000000000000000000000000000000000000000..a35406bda65d8ec480b55b62808989f2cf98ac97 GIT binary patch literal 13440 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%0DagCVdV?&YN0b^E{8%Xs$lwKe zP)dDuatN-ghOWZkC?A+vsp%fZS-k8cv-r%Oipe;H?-%0>5iustZOJKsODiorDv$@MDL}sgg5mcmHG;* z{U4=8veq;Q2wv0C@|q`FpHWg!E`Q8?)w)ASG3LSz?FlJ;wQr%l8AE$KN?+~kD7Z}OskkXQ1#beM_KXy+_Kg%&%Ot!1(k!dT z4cS)uYTru1J}Id3x0j!uSBSSn%Bg)Y1=YS8$xzJf`puwGOw_)exn7d%Cbd5Z5uTJE z3cn3ER((zCDahZUDq%p$HsN-gzIskoaICEXDt%@0+ctgmys4nt|5WmJ|L>LhD*o!Z zPQkf01r+E-BUkP?*8W{&N)>9g5VP{IMzF+sQI{|k`uiqdR<_Hz&$r8x!c z`uoAKjvw}OB3(*RLc9LI+w|4GnSz>4-(G&&55m(l@53aj_NCSNiuU{zuHbvnaa#1% zzI8heFrcL51eU=S{bMkw?aN`t+~-l}bE=L>{g;x`Q|)j7G;05*{%VrKe)J*%rwCwP zmA--wC|mW_zIRgUCrwRpqx2Lkvgxb+sBV{#SI>pYjM7thG4zS1{8#659d`){&DMc5 zK{l@?GXFA@B5stvef*%ZQnEr@6Bh!D1t^vdPXJ2qE|mXj{HOg|(|4i5uf;0_(x@s< Y@>IS`l(v-z({H|7NStp|u%Ya~0ccd64FCWD literal 0 HcmV?d00001 diff --git a/main.o b/main.o new file mode 100644 index 0000000000000000000000000000000000000000..744b776940c89c5355309e1b3209ad9d164135f9 GIT binary patch literal 4640 zcmbtYU1%It6uz6(wr>AoZB^O|_eD*uS(2104K<`)w;i=V(i#=ovQBmQ6%^4o^CDVWFiIaH6yiB|?%CYl%?RRwoqO+h zzH{z5=l;wj$1=Njwj>gaB*A*w{D@G--dj1}?~~m=wvw%2{Z{SQ+StuYw~>1C@I}O| z@#}3?ZEV)6XJ)PAYf)>}rmWgeXR-Uc93PiPR_)S%A5LDTS$J*ix>c)ATjVV>dnq%6 z{;$3dkMXe^zF33LV=u!qC)UEVD4xCMTXi*kX7<`kjX@MkEi*GdHp7M&!*kUuhe&(; zQ5kpbi_h+b&N-};4E-$ju6_qkNO#(*L#mMeTm4*E5jHx5~Db zxdEZht(w?6H#gT*Mmg`q1@T~WY#SDcZ8OYN&&<@Kl&Mrx=UXU+M){`BuWG`gWMHgX`mR2rhMkN1%#@)5{}09`=Oh2Wv*sDyvDOsKxwNYw=JWHz}OPvNnJ{0>3u+nbHfuLU!MD9E+$DL>30Ng?eUYu=#TE4jS*0pM>4Cu|W(Ngki?Yi?k#~ZC4IT|# z;jnDBQV!kWY{7wpDAYt*ydFRmj)X5}qj^XpZvYzPJjeE{6&89B7=d`AAss2Vs}_$2 z?%r8=xuHCu;Y*-@GlJ)E|DfQ$Ou~Nd7YD6Mv7wiI4jjr9Wz# z_;iMekNXKFA2m(1{+kB>12z6!!&g9`;{Q4Z|6bu(AN)ikzI^q;7~{b+ zPWT_tN1-@RMnjfR7bW_^b4mPl8jfp6IDSx|AYaEb3McZp)A9~gP8fQRya2Lyz4YRex=jAn>0T9U#jNYtnu~p z)obuKDE?lJubdZ0H&6!!CntN zT$jEE5_KwgIe=8;UsQ1JppUi+X7`{Hl<3p6Z&v_)$B_?C z9o2S0i}M?4GO~ZwNSNX$H~4LhluiOv`>_g?-lL=@chv3x1@(!8?Ub3QQkvZTfa5tb z!`(e=OV%p;_L~5T-g8nqVoI$Lpk?W7|L9(ayUubSLp?%Y* q0>kSJZB2XhX|6p$>=YIS, - /// Number of channels (1 for mono, 2 for stereo) - pub num_channels: usize, - /// Sample rate - pub sample_rate: f32, - /// Current state - pub state: ClipState, - /// Current write position during recording - pub write_position: usize, - /// Current read position during playback - pub read_position: usize, -} - -impl Clip { - pub fn new(num_channels: usize, sample_rate: f32) -> Self { - Self { - samples: Vec::new(), - num_channels, - sample_rate, - state: ClipState::Empty, - write_position: 0, - read_position: 0, - } - } - - /// Record a block of audio into this clip - pub fn record(&mut self, input: &[f32], num_samples: usize) { - if self.state != ClipState::Recording { - return; - } - // Extend the clip buffer if needed - let needed = self.write_position + num_samples * self.num_channels; - if self.samples.len() < needed { - self.samples.resize(needed, 0.0); - } - // Copy input samples - for (i, sample) in input.iter().enumerate().take(num_samples * self.num_channels) { - if self.write_position + i < self.samples.len() { - self.samples[self.write_position + i] = *sample; - } - } - self.write_position += num_samples * self.num_channels; - } - - /// Play back a block of audio from this clip at the given position - /// Returns the number of samples actually read (may be less than requested at end) - pub fn play(&mut self, output: &mut [f32], num_samples: usize) -> usize { - if self.state != ClipState::Looping || self.samples.is_empty() { - return 0; - } - - let channels = self.num_channels; - let total_frames = self.samples.len() / channels; - let mut samples_read = 0; - - for frame in 0..num_samples { - if self.read_position >= total_frames { - // Loop back to start - self.read_position = 0; - } - - let frame_start = self.read_position * channels; - for ch in 0..channels { - if frame_start + ch < self.samples.len() { - output[frame * channels + ch] = self.samples[frame_start + ch]; - } - } - - self.read_position += 1; - samples_read += 1; - } - - samples_read * channels - } - - /// Handle a trigger event for this clip - /// Returns the new state - pub fn trigger(&mut self) -> ClipState { - match self.state { - ClipState::Empty => { - // Start recording - self.state = ClipState::Recording; - self.samples.clear(); - self.write_position = 0; - self.read_position = 0; - } - ClipState::Recording => { - // Stop recording, start looping - self.state = ClipState::Looping; - self.read_position = 0; - } - ClipState::Looping => { - // Stop - self.state = ClipState::Stopped; - } - ClipState::Stopped => { - // Start looping again - self.state = ClipState::Looping; - self.read_position = 0; - } - } - self.state - } - - /// Get the velocity value representing the current state (0-127) - pub fn state_velocity(&self) -> u8 { - match self.state { - ClipState::Empty => 0, - ClipState::Recording => 64, - ClipState::Looping => 127, - ClipState::Stopped => 32, - } - } -} - -/// The engine that manages clips and audio/MIDI routing -pub struct Engine { - /// Clips indexed by note number (0-127) - pub clips: HashMap, - /// Number of audio channels - pub num_channels: usize, - /// Sample rate - pub sample_rate: f32, -} - -impl Engine { - pub fn new(num_channels: usize, sample_rate: f32) -> Self { - Self { - clips: HashMap::new(), - num_channels, - sample_rate, - } - } - - /// Get or create a clip for the given note - pub fn get_clip(&mut self, note: u8) -> &mut Clip { - self.clips.entry(note).or_insert_with(|| { - Clip::new(self.num_channels, self.sample_rate) - }) - } - - /// Process a MIDI note on/off event - /// Returns the output velocity to send - pub fn process_midi_note(&mut self, note: u8, velocity: u8, is_note_on: bool) -> u8 { - if !is_note_on { - // Note off - pass through with original velocity - return velocity; - } - - // Note on - trigger the clip - let clip = self.get_clip(note); - clip.trigger(); - clip.state_velocity() - } - - /// Process audio: record into recording clips, play back looping clips - pub fn process_audio(&mut self, input: &[f32], output: &mut [f32], num_samples: usize) { - // Clear output first - for sample in output.iter_mut() { - *sample = 0.0; - } - - // Record into any clips that are recording - for clip in self.clips.values_mut() { - if clip.state == ClipState::Recording { - clip.record(input, num_samples); - } - } - - // Play back any clips that are looping - for clip in self.clips.values_mut() { - if clip.state == ClipState::Looping { - clip.play(output, num_samples); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - // Helper to create a test engine - fn test_engine() -> Engine { - Engine::new(2, 44100.0) - } - - // Helper to create a test clip with some samples - fn test_clip_with_samples() -> Clip { - let mut clip = Clip::new(2, 44100.0); - clip.samples = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6]; // 3 stereo frames - clip.state = ClipState::Looping; - clip - } - - // ===== ClipState tests ===== - - #[test] - fn test_clip_initial_state_is_empty() { - let clip = Clip::new(2, 44100.0); - assert_eq!(clip.state, ClipState::Empty); - } - - #[test] - fn test_clip_state_velocity_values() { - let mut clip = Clip::new(2, 44100.0); - - clip.state = ClipState::Empty; - assert_eq!(clip.state_velocity(), 0); - - clip.state = ClipState::Recording; - assert_eq!(clip.state_velocity(), 64); - - clip.state = ClipState::Looping; - assert_eq!(clip.state_velocity(), 127); - - clip.state = ClipState::Stopped; - assert_eq!(clip.state_velocity(), 32); - } - - // ===== Trigger state machine tests ===== - - #[test] - fn test_trigger_empty_starts_recording() { - let mut clip = Clip::new(2, 44100.0); - assert_eq!(clip.state, ClipState::Empty); - - let new_state = clip.trigger(); - assert_eq!(new_state, ClipState::Recording); - assert_eq!(clip.state, ClipState::Recording); - assert!(clip.samples.is_empty()); - assert_eq!(clip.write_position, 0); - assert_eq!(clip.read_position, 0); - } - - #[test] - fn test_trigger_recording_starts_looping() { - let mut clip = Clip::new(2, 44100.0); - clip.state = ClipState::Recording; - clip.samples = vec![0.1, 0.2, 0.3, 0.4]; - clip.write_position = 4; - - let new_state = clip.trigger(); - assert_eq!(new_state, ClipState::Looping); - assert_eq!(clip.state, ClipState::Looping); - assert_eq!(clip.read_position, 0); - // Samples should be preserved - assert_eq!(clip.samples.len(), 4); - } - - #[test] - fn test_trigger_looping_stops() { - let mut clip = test_clip_with_samples(); - assert_eq!(clip.state, ClipState::Looping); - - let new_state = clip.trigger(); - assert_eq!(new_state, ClipState::Stopped); - assert_eq!(clip.state, ClipState::Stopped); - // Samples should be preserved - assert!(!clip.samples.is_empty()); - } - - #[test] - fn test_trigger_stopped_starts_looping() { - let mut clip = test_clip_with_samples(); - clip.state = ClipState::Stopped; - - let new_state = clip.trigger(); - assert_eq!(new_state, ClipState::Looping); - assert_eq!(clip.state, ClipState::Looping); - assert_eq!(clip.read_position, 0); - } - - #[test] - fn test_trigger_cycle_empty_recording_looping_stopped_looping() { - let mut clip = Clip::new(2, 44100.0); - - // Empty -> Recording - assert_eq!(clip.trigger(), ClipState::Recording); - - // Recording -> Looping - assert_eq!(clip.trigger(), ClipState::Looping); - - // Looping -> Stopped - assert_eq!(clip.trigger(), ClipState::Stopped); - - // Stopped -> Looping - assert_eq!(clip.trigger(), ClipState::Looping); - - // Looping -> Stopped again - assert_eq!(clip.trigger(), ClipState::Stopped); - } - - // ===== Recording tests ===== - - #[test] - fn test_record_when_not_recording_does_nothing() { - let mut clip = Clip::new(2, 44100.0); - clip.state = ClipState::Empty; - - let input = vec![0.5, 0.6, 0.7, 0.8]; - clip.record(&input, 2); - - assert!(clip.samples.is_empty()); - assert_eq!(clip.write_position, 0); - } - - #[test] - fn test_record_appends_samples() { - let mut clip = Clip::new(2, 44100.0); - clip.state = ClipState::Recording; - - let input = vec![0.1, 0.2, 0.3, 0.4]; - clip.record(&input, 2); - - assert_eq!(clip.samples, vec![0.1, 0.2, 0.3, 0.4]); - assert_eq!(clip.write_position, 4); - } - - #[test] - fn test_record_multiple_blocks() { - let mut clip = Clip::new(2, 44100.0); - clip.state = ClipState::Recording; - - let input1 = vec![0.1, 0.2, 0.3, 0.4]; - clip.record(&input1, 2); - - let input2 = vec![0.5, 0.6, 0.7, 0.8]; - clip.record(&input2, 2); - - assert_eq!(clip.samples, vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]); - assert_eq!(clip.write_position, 8); - } - - #[test] - fn test_record_clears_on_new_recording() { - let mut clip = Clip::new(2, 44100.0); - clip.state = ClipState::Recording; - - // Record some samples - let input1 = vec![0.1, 0.2, 0.3, 0.4]; - clip.record(&input1, 2); - - // Trigger to stop recording - clip.trigger(); // Now Looping - - // Trigger again to start new recording - clip.trigger(); // Now Stopped - clip.trigger(); // Now Looping - clip.trigger(); // Now Stopped - - // Start new recording - clip.state = ClipState::Recording; - clip.samples.clear(); - clip.write_position = 0; - - let input2 = vec![0.9, 0.8, 0.7, 0.6]; - clip.record(&input2, 2); - - assert_eq!(clip.samples, vec![0.9, 0.8, 0.7, 0.6]); - assert_eq!(clip.write_position, 4); - } - - // ===== Playback tests ===== - - #[test] - fn test_play_when_not_looping_returns_zero() { - let mut clip = Clip::new(2, 44100.0); - clip.state = ClipState::Stopped; - clip.samples = vec![0.1, 0.2, 0.3, 0.4]; - - let mut output = vec![0.0; 4]; - let samples_read = clip.play(&mut output, 2); - - assert_eq!(samples_read, 0); - assert_eq!(output, vec![0.0, 0.0, 0.0, 0.0]); - } - - #[test] - fn test_play_empty_clip_returns_zero() { - let mut clip = Clip::new(2, 44100.0); - clip.state = ClipState::Looping; - - let mut output = vec![0.0; 4]; - let samples_read = clip.play(&mut output, 2); - - assert_eq!(samples_read, 0); - } - - #[test] - fn test_play_outputs_samples() { - let mut clip = test_clip_with_samples(); - - let mut output = vec![0.0; 6]; // 3 stereo frames - let samples_read = clip.play(&mut output, 3); - - assert_eq!(samples_read, 6); - assert_eq!(output, vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6]); - assert_eq!(clip.read_position, 3); - } - - #[test] - fn test_play_loops_when_reaching_end() { - let mut clip = test_clip_with_samples(); // 3 stereo frames - - // Read all 3 frames - let mut output1 = vec![0.0; 6]; - clip.play(&mut output1, 3); - assert_eq!(clip.read_position, 3); // At end - - // Read more - should loop back - let mut output2 = vec![0.0; 4]; // 2 stereo frames - let samples_read = clip.play(&mut output2, 2); - - assert_eq!(samples_read, 4); - assert_eq!(output2, vec![0.1, 0.2, 0.3, 0.4]); - assert_eq!(clip.read_position, 2); - } - - #[test] - fn test_play_multiple_loops() { - let mut clip = test_clip_with_samples(); // 3 stereo frames - - // Read 6 frames (2 full loops) - let mut output = vec![0.0; 12]; - let samples_read = clip.play(&mut output, 6); - - assert_eq!(samples_read, 12); - // First loop - assert_eq!(output[0..6], vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6]); - // Second loop - assert_eq!(output[6..12], vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6]); - assert_eq!(clip.read_position, 0); // Back to start - } - - // ===== Engine tests ===== - - #[test] - fn test_engine_initial_state() { - let engine = test_engine(); - assert!(engine.clips.is_empty()); - assert_eq!(engine.num_channels, 2); - assert_eq!(engine.sample_rate, 44100.0); - } - - #[test] - fn test_engine_get_clip_creates_new_clip() { - let mut engine = test_engine(); - - let clip = engine.get_clip(60); - assert_eq!(clip.state, ClipState::Empty); - assert_eq!(clip.num_channels, 2); - assert_eq!(clip.sample_rate, 44100.0); - assert_eq!(engine.clips.len(), 1); - } - - #[test] - fn test_engine_get_clip_reuses_existing() { - let mut engine = test_engine(); - - // Get the clip once - engine.get_clip(60); - - // Get it again - should reuse the same clip - let clip = engine.get_clip(60); - - assert_eq!(clip.state, ClipState::Empty); - assert_eq!(engine.clips.len(), 1); - } - - #[test] - fn test_engine_get_clip_different_notes() { - let mut engine = test_engine(); - - engine.get_clip(60); - engine.get_clip(61); - engine.get_clip(62); - - assert_eq!(engine.clips.len(), 3); - } - - #[test] - fn test_engine_process_midi_note_on_triggers_clip() { - let mut engine = test_engine(); - - // First note on should start recording - let velocity = engine.process_midi_note(60, 100, true); - assert_eq!(velocity, 0); // Empty state velocity - - let clip = engine.get_clip(60); - assert_eq!(clip.state, ClipState::Recording); - } - - #[test] - fn test_engine_process_midi_note_off_passes_through() { - let mut engine = test_engine(); - - let velocity = engine.process_midi_note(60, 100, false); - assert_eq!(velocity, 100); // Original velocity passed through - } - - #[test] - fn test_engine_process_midi_note_cycle() { - let mut engine = test_engine(); - - // Note on 1: Empty -> Recording (velocity 0) - let v1 = engine.process_midi_note(60, 100, true); - assert_eq!(v1, 0); - assert_eq!(engine.get_clip(60).state, ClipState::Recording); - - // Note on 2: Recording -> Looping (velocity 127) - let v2 = engine.process_midi_note(60, 100, true); - assert_eq!(v2, 127); - assert_eq!(engine.get_clip(60).state, ClipState::Looping); - - // Note on 3: Looping -> Stopped (velocity 32) - let v3 = engine.process_midi_note(60, 100, true); - assert_eq!(v3, 32); - assert_eq!(engine.get_clip(60).state, ClipState::Stopped); - - // Note on 4: Stopped -> Looping (velocity 127) - let v4 = engine.process_midi_note(60, 100, true); - assert_eq!(v4, 127); - assert_eq!(engine.get_clip(60).state, ClipState::Looping); - } - - #[test] - fn test_engine_process_audio_records_and_plays() { - let mut engine = test_engine(); - - // Start recording on note 60 - engine.process_midi_note(60, 100, true); - - // Process audio - should record input - let input = vec![0.1, 0.2, 0.3, 0.4]; - let mut output = vec![0.0; 4]; - engine.process_audio(&input, &mut output, 2); - - // Output should be silence (nothing playing yet) - assert_eq!(output, vec![0.0, 0.0, 0.0, 0.0]); - - // Clip should have recorded the input - let clip = engine.get_clip(60); - assert_eq!(clip.samples, vec![0.1, 0.2, 0.3, 0.4]); - - // Stop recording and start looping - engine.process_midi_note(60, 100, true); - - // Process audio - should play back the recorded clip - let input2 = vec![0.0; 4]; - let mut output2 = vec![0.0; 4]; - engine.process_audio(&input2, &mut output2, 2); - - assert_eq!(output2, vec![0.1, 0.2, 0.3, 0.4]); - } - - #[test] - fn test_engine_multiple_clips_independent() { - let mut engine = test_engine(); - - // Trigger note 60 to record - engine.process_midi_note(60, 100, true); - assert_eq!(engine.get_clip(60).state, ClipState::Recording); - - // Trigger note 61 to record - engine.process_midi_note(61, 100, true); - assert_eq!(engine.get_clip(61).state, ClipState::Recording); - - // Trigger note 60 again to loop - engine.process_midi_note(60, 100, true); - assert_eq!(engine.get_clip(60).state, ClipState::Looping); - - // Note 61 should still be recording - assert_eq!(engine.get_clip(61).state, ClipState::Recording); - } - - #[test] - fn test_engine_process_audio_multiple_looping_clips() { - let mut engine = test_engine(); - - // Create and record clip 60 - engine.process_midi_note(60, 100, true); - let input1 = vec![0.1, 0.2, 0.3, 0.4]; - engine.process_audio(&input1, &mut vec![0.0; 4], 2); - engine.process_midi_note(60, 100, true); // Now looping - - // Create and record clip 61 - engine.process_midi_note(61, 100, true); - let input2 = vec![0.5, 0.6, 0.7, 0.8]; - engine.process_audio(&input2, &mut vec![0.0; 4], 2); - engine.process_midi_note(61, 100, true); // Now looping - - // Both should be looping - output should be sum of both clips - let mut output = vec![0.0; 4]; - engine.process_audio(&vec![0.0; 4], &mut output, 2); - - // Clip 60 plays [0.1, 0.2, 0.3, 0.4] - // Clip 61 plays [0.5, 0.6, 0.7, 0.8] - // Output is sum: [0.6, 0.8, 1.0, 1.2] - assert!((output[0] - 0.6).abs() < 1e-6); - assert!((output[1] - 0.8).abs() < 1e-6); - assert!((output[2] - 1.0).abs() < 1e-6); - assert!((output[3] - 1.2).abs() < 1e-6); - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index f6c344c..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,168 +0,0 @@ -use std::sync::Arc; -use nih_plug::prelude::*; -use nih_plug::plugin::vst3::Vst3Plugin; -use nih_plug::plugin::clap::ClapPlugin; -use nih_plug::wrapper::clap::features::ClapFeature; -use nih_plug::wrapper::vst3::subcategories::Vst3SubCategory; - -mod engine; -use engine::Engine; - -/// The main plugin struct -pub struct ClipLauncher { - /// The engine managing clips - engine: Engine, - /// Pending MIDI output events - pending_midi: Vec>, -} - -impl Default for ClipLauncher { - fn default() -> Self { - Self { - engine: Engine::new(2, 44100.0), - pending_midi: Vec::new(), - } - } -} - -impl Plugin for ClipLauncher { - const NAME: &'static str = "Clip Launcher"; - const VENDOR: &'static str = "Your Company"; - const URL: &'static str = ""; - const EMAIL: &'static str = ""; - const VERSION: &'static str = "0.1.0"; - - const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[ - AudioIOLayout { - main_input_channels: NonZeroU32::new(2), - main_output_channels: NonZeroU32::new(2), - ..AudioIOLayout::const_default() - }, - ]; - - const MIDI_INPUT: MidiConfig = MidiConfig::Basic; - const MIDI_OUTPUT: MidiConfig = MidiConfig::Basic; - - const SAMPLE_ACCURATE_AUTOMATION: bool = true; - - type SysExMessage = (); - type BackgroundTask = (); - - fn initialize( - &mut self, - _audio_io_layout: &AudioIOLayout, - buffer_config: &BufferConfig, - _context: &mut impl InitContext, - ) -> bool { - self.engine = Engine::new( - _audio_io_layout.main_input_channels.map(|c| c.get() as usize).unwrap_or(2), - buffer_config.sample_rate, - ); - true - } - - fn reset(&mut self) { - // Clear all clips on reset - self.engine.clips.clear(); - self.pending_midi.clear(); - } - - fn process( - &mut self, - buffer: &mut Buffer, - _aux: &mut AuxiliaryBuffers, - context: &mut impl ProcessContext, - ) -> ProcessStatus { - // Process MIDI input events - while let Some(event) = context.next_event() { - match event { - NoteEvent::NoteOn { timing, note, velocity, channel, voice_id } => { - let output_velocity = self.engine.process_midi_note(note, velocity as u8, true); - // Queue output MIDI event - self.pending_midi.push(NoteEvent::NoteOn { - timing, - note, - velocity: output_velocity as f32, - channel, - voice_id, - }); - } - NoteEvent::NoteOff { timing, note, velocity, channel, voice_id } => { - let output_velocity = self.engine.process_midi_note(note, velocity as u8, false); - self.pending_midi.push(NoteEvent::NoteOff { - timing, - note, - velocity: output_velocity as f32, - channel, - voice_id, - }); - } - _ => {} - } - } - - // Process audio - let num_channels = buffer.channels() as usize; - let num_frames = buffer.samples() as usize; - let input_slices = buffer.as_slice(); // &[&[f32]] - - // Build interleaved input buffer - let mut interleaved_input = vec![0.0; num_frames * num_channels]; - for ch in 0..num_channels { - for frame in 0..num_frames { - interleaved_input[frame * num_channels + ch] = input_slices[ch][frame]; - } - } - - // Build interleaved output buffer (will be filled by engine) - let mut interleaved_output = vec![0.0; num_frames * num_channels]; - - self.engine.process_audio(&interleaved_input, &mut interleaved_output, num_frames); - - // Copy back to buffer's output channels - for (ch, channel_samples) in buffer.iter_samples().enumerate() { - let (_, output) = channel_samples.as_slice(); - for frame in 0..num_frames { - output[frame] = interleaved_output[frame * num_channels + ch]; - } - } - - // Send pending MIDI output events - for event in self.pending_midi.drain(..) { - context.send_event(event); - } - - ProcessStatus::Normal - } - - fn params(&self) -> Arc { - Arc::new(ClipLauncherParams { - dummy: BoolParam::new("Dummy", false), - }) - } -} - -impl Vst3Plugin for ClipLauncher { - const VST3_CLASS_ID: [u8; 16] = *b"ClipLauncher1234"; - const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = &[Vst3SubCategory::Fx]; -} - -impl ClapPlugin for ClipLauncher { - const CLAP_ID: &'static str = "com.yourcompany.clip-launcher"; - const CLAP_DESCRIPTION: Option<&'static str> = Some("A clip launcher plugin"); - const CLAP_MANUAL_URL: Option<&'static str> = None; - const CLAP_SUPPORT_URL: Option<&'static str> = None; - const CLAP_FEATURES: &'static [ClapFeature] = &[ClapFeature::AudioEffect, ClapFeature::Stereo]; -} - -/// Empty params struct (no parameters needed for this plugin) -#[derive(Params)] -struct ClipLauncherParams { - /// Placeholder parameter to satisfy the derive macro - #[id = "dummy"] - dummy: BoolParam, -} - -// Export the plugin -nih_export_vst3!(ClipLauncher); -nih_export_clap!(ClipLauncher); diff --git a/test_engine b/test_engine new file mode 100755 index 0000000000000000000000000000000000000000..2879b0918de1af7710f857965f272a143ddcba5e GIT binary patch literal 41088 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 literal 0 HcmV?d00001 diff --git a/test_engine.o b/test_engine.o new file mode 100644 index 0000000000000000000000000000000000000000..060dd8ac1258e60b6dd97b38800da0eb96cfb449 GIT binary patch literal 34088 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 VA0GdB)i1o#uAjDFoPTxw{{@Ju!m$7V literal 0 HcmV?d00001