Include my code
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| /target | ||||
| example.wav | ||||
| .vscode/ | ||||
							
								
								
									
										25
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| # This file is automatically @generated by Cargo. | ||||
| # It is not intended for manual editing. | ||||
| version = 3 | ||||
|  | ||||
| [[package]] | ||||
| name = "lansky" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "wav", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "riff" | ||||
| version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b9b1a3d5f46d53f4a3478e2be4a5a5ce5108ea58b100dcd139830eae7f79a3a1" | ||||
|  | ||||
| [[package]] | ||||
| name = "wav" | ||||
| version = "1.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a65e199c799848b4f997072aa4d673c034f80f40191f97fe2f0a23f410be1609" | ||||
| dependencies = [ | ||||
|  "riff", | ||||
| ] | ||||
							
								
								
									
										9
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| [package] | ||||
| name = "lansky" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
|  | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
|  | ||||
| [dependencies] | ||||
| wav = "1.0.0" | ||||
							
								
								
									
										77
									
								
								asm_notes.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								asm_notes.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| NOTES: Slightly out of date -- I haven't added the Imm mov mode or the ix register yet | ||||
|  | ||||
| Registers: | ||||
|     $a0..9: Corresponds to input 0 through 9 | ||||
|     $l0..9: "Locals" 0..9 | ||||
|     $z0..9: Corresponds to output 0 through 9 | ||||
|     $r0..255: Auxiliary uint64_ts | ||||
|  | ||||
|     $ix: Index base | ||||
|  | ||||
| Channels: | ||||
|     A channel is a vector of samples with a known "formal range" -- | ||||
|     this formal range includes its start (relative to the whole song) | ||||
|     and its basic length. | ||||
|  | ||||
|     You can use outputs that aren't exposed as part of your device.  | ||||
|     This can be a good place to hide wavetables and stuff. | ||||
|     They just won't be returned. | ||||
|  | ||||
| Memory model: | ||||
|     $a0..9, $l0..9, and $z0..9 correspond to arrays | ||||
|     When you read $a0, it reads channel a0 at $ix+a0.formal_start+a0.loop_start | ||||
|     When you write $z0, it writes channel z0 at $ix+z0.formal_start+z0.loop_start | ||||
|  | ||||
|     Unknown positions are 0. | ||||
|  | ||||
|     A channel has an informal_start and an informal_length which initially match | ||||
|     formal_start and formal_length. | ||||
|  | ||||
|     Writing to a position outside of informal_start..informal_start+informal_length  | ||||
|     extends the bounds. | ||||
|  | ||||
|     In looped mode, then the ultimate indices are wrapped to  | ||||
|     (formal_start+loop_start..formal_start+loop_start+loop_length)  | ||||
|     and all other behavior remains the same as above. | ||||
|  | ||||
| Instructions: | ||||
|     mov|add|xor ($l0..9, $z0..9, $r0..255), ($a0..9, $l0..9, $z0..9, $r0..255) | ||||
|     Copy value, add value, or xor value. | ||||
|  | ||||
|     range ($a0..9, $l0..9, $z0..9), loop_start, loop_length | ||||
|     Start and length are immediate values. | ||||
|     If length is nonzero, the channel is in "looped" mode.  | ||||
|  | ||||
|     inp ($a0..9, $l0..9, $z0..9) | ||||
|     Push a copy of the channel to the stack for "invoke". | ||||
|     The channel will be sliced: | ||||
|     - if loop_start is zero and loop_length is zero:  | ||||
|         The formal range runs from formal_start to formal_start+formal_length. | ||||
|         The informal range runs from informal_start to informal_start+informal_length. | ||||
|     - if loop_start is nonzero and loop_length is zero:  | ||||
|         The formal range runs from formal_start+loop_start to formal_start+formal_length | ||||
|         The informal range runs from formal_start+loop_start to informal_start+informal_length | ||||
|     - if loop_length is nonzero:  | ||||
|         The formal range runs from formal_start+loop_start to formal_start+loop_start+loop_length | ||||
|         The informal range is the same as the formal range. | ||||
|  | ||||
|     dup | ||||
|     Duplicate an item from the invoke stack. | ||||
|  | ||||
|     oup (mov|add|xor) ($l0..9, $z0..9) | ||||
|     Pop an output from the invoke stack and write it to an output channel or local. | ||||
|  | ||||
|     All indexes from informal_start to informal_start+informal_length are written. | ||||
|  | ||||
|     The instruction used to write can be mov, add, or xor | ||||
|  | ||||
|     invoke device, n_inputs, n_outputs | ||||
|     Invoke the device by name (an immediate string) | ||||
|     Asserts that the device takes n_inputs inputs (an immediate integer) and n_outputs outputs (an immediate integer) | ||||
|     Asserts that there are actually n_inputs inputs on the stack. | ||||
|     After invoke, there will be n_outputs outputs on the stack. | ||||
|     They will be ordered such that the first call to oup produces the first output, the second produces the second,  | ||||
|     and so on | ||||
|  | ||||
|     exit | ||||
|     Exit the current procedure. | ||||
							
								
								
									
										131
									
								
								src/audio.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/audio.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,131 @@ | ||||
| use std::ops::{Range, Index, IndexMut}; | ||||
|  | ||||
| // Fixed point: 0xFFFF_FFFF.FFFF_FFFF | ||||
| #[derive(Clone, Copy)] | ||||
| pub struct Sample(pub i64); | ||||
|  | ||||
| impl Sample { | ||||
|     pub fn to_i16_sample(&self) -> i16 { | ||||
|         // interpret self as fixed-point | ||||
|         if self.0 > i32::MAX as i64 { return i16::MAX } | ||||
|         if self.0 < i32::MIN as i64 { return i16::MIN } | ||||
|         let low_part = self.0 as i32; | ||||
|         return (low_part >> 16) as i16 | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for Sample { | ||||
|     fn default() -> Self { BLANK_SAMPLE } | ||||
| } | ||||
|  | ||||
| const BLANK_SAMPLE: Sample = Sample(0); | ||||
|  | ||||
| #[derive(Clone)] | ||||
| pub struct Channel { | ||||
|     formal: Range<i32>, | ||||
|  | ||||
|     minus: Vec<Sample>, | ||||
|     plus: Vec<Sample>, | ||||
| } | ||||
|  | ||||
| pub struct Bounds { | ||||
|     pub formal: Range<i32>, | ||||
|     pub informal: Range<i32>, | ||||
| } | ||||
|  | ||||
| impl Channel { | ||||
|     pub fn new(formal: Range<i32>) -> Self { | ||||
|         assert!(formal.end >= formal.start); | ||||
|         Channel { | ||||
|             formal: formal, | ||||
|             minus: vec![], | ||||
|             plus: vec![], | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn formal(&self) -> Range<i32> { | ||||
|         self.formal.clone() | ||||
|     } | ||||
|  | ||||
|     pub fn bounds(&self) -> Bounds { | ||||
|         let formal = self.formal.clone(); | ||||
|         let informal_start = formal.start - self.minus.len() as i32; | ||||
|         let informal_len_0 = self.minus.len() + self.plus.len(); | ||||
|         let informal_len_1 = (formal.end - informal_start) as usize;  | ||||
|         let informal_len = informal_len_0.max(informal_len_1); | ||||
|  | ||||
|         return Bounds { | ||||
|             formal, | ||||
|             informal: informal_start..(informal_start + informal_len as i32) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn blank() -> Channel { | ||||
|         return Channel::new(0..0); | ||||
|     } | ||||
|  | ||||
|     pub fn slice(&self, start: i32, length: i32) -> Channel { | ||||
|         if start == 0 && length == 0 { | ||||
|             return self.clone() | ||||
|         } | ||||
|         if start != 0 && length == 0 { | ||||
|             let formal_start = self.formal.start + start; | ||||
|             let mut formal_end = self.formal.start + self.formal.end; | ||||
|             if formal_end < formal_start { formal_end = formal_start; } | ||||
|  | ||||
|             let mut chan = Channel::new(formal_start..formal_end); | ||||
|             for i in 0..self.plus.len() { | ||||
|                 chan[self.formal.start + i as i32] = self[self.formal.start + i as i32] | ||||
|             } | ||||
|             return chan | ||||
|         } | ||||
|  | ||||
|         let formal_start = self.formal.start + start; | ||||
|         let formal_end = self.formal.start + start + length; | ||||
|         let mut chan = Channel::new(formal_start..formal_end); | ||||
|         for i in formal_start..formal_end { | ||||
|             chan[i] = self[i]; | ||||
|         } | ||||
|         return chan | ||||
|     } | ||||
| }  | ||||
|  | ||||
| impl Index<i32> for Channel { | ||||
|     type Output = Sample; | ||||
|  | ||||
|     fn index(&self, index: i32) -> &Self::Output { | ||||
|         let adj_idx = index - self.formal.start; | ||||
|         if adj_idx < 0 { | ||||
|             let neg_adj_idx = (-adj_idx) as usize - 1; | ||||
|             self.minus.get(neg_adj_idx).unwrap_or(&BLANK_SAMPLE) | ||||
|         } else { | ||||
|             let pos_adj_idx = adj_idx as usize; | ||||
|             self.plus.get(pos_adj_idx).unwrap_or(&BLANK_SAMPLE) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl IndexMut<i32> for Channel { | ||||
|     fn index_mut(&mut self, index: i32) -> &mut Self::Output { | ||||
|         let adj_idx = index - self.formal.start; | ||||
|         if adj_idx < 0 { | ||||
|             let neg_adj_idx = (-adj_idx) as usize - 1; | ||||
|             if self.minus.len() < neg_adj_idx + 1 { | ||||
|                 self.minus.reserve(neg_adj_idx + 1); | ||||
|                 while self.minus.len() < neg_adj_idx + 1 { | ||||
|                     self.minus.push(BLANK_SAMPLE); | ||||
|                 } | ||||
|             } | ||||
|             return &mut self.minus[neg_adj_idx] | ||||
|         } else { | ||||
|             let pos_adj_idx = adj_idx as usize; | ||||
|             if self.plus.len() < pos_adj_idx + 1{ | ||||
|                 self.plus.reserve(pos_adj_idx + 1); | ||||
|                 while self.plus.len() < pos_adj_idx + 1 { | ||||
|                     self.plus.push(BLANK_SAMPLE); | ||||
|                 } | ||||
|             } | ||||
|             return &mut self.plus[pos_adj_idx] | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										122
									
								
								src/device.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/device.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| use std::{collections::HashMap, cell::Cell}; | ||||
|  | ||||
| use crate::{audio::Channel, error::{Lansky, LanskyError}, vm::{Procedure, Block}, rack::Rack}; | ||||
|  | ||||
| pub struct Device { | ||||
|     proto: Proto, | ||||
|     implementation: Implementation, | ||||
| } | ||||
|  | ||||
| #[derive(Clone)] | ||||
| pub struct Proto {   | ||||
|     pub name: String, | ||||
|     pub inputs: Vec<String>, | ||||
|     pub outputs: Vec<String>, | ||||
| } | ||||
|  | ||||
|  | ||||
| enum Implementation { | ||||
|     Native(fn(Vec<Channel>) -> Lansky<Vec<Channel>>), | ||||
|     Hosted(Procedure), | ||||
| } | ||||
|  | ||||
| impl Device { | ||||
|     pub fn native( | ||||
|         name: String,  | ||||
|         inputs: Vec<String>,  | ||||
|         outputs: Vec<String>,  | ||||
|         native: fn(Vec<Channel>) -> Lansky<Vec<Channel>> | ||||
|     ) -> Lansky<Self> { | ||||
|         let proto = Proto { name, inputs, outputs }; | ||||
|         Self::new(proto, Implementation::Native(native)) | ||||
|     } | ||||
|  | ||||
|     pub fn hosted( | ||||
|         name: String,  | ||||
|         inputs: Vec<String>,  | ||||
|         outputs: Vec<String>,  | ||||
|         blocks: Vec<Block>, | ||||
|     ) -> Lansky<Self> { | ||||
|         let proto = Proto { name, inputs, outputs }; | ||||
|         Self::new(proto.clone(), Implementation::Hosted(Procedure::new(proto, blocks))) | ||||
|     } | ||||
|  | ||||
|     fn new(proto: Proto, implementation: Implementation) -> Lansky<Self> { | ||||
|         for i0 in 0..proto.inputs.len() { | ||||
|             for i1 in i0+1..proto.inputs.len() { | ||||
|                 if proto.inputs[i0] == proto.inputs[i1] {  | ||||
|                     return Err(LanskyError::DuplicateInput { name: proto.inputs[i0].clone(), i0, i1 })  | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         for i0 in 0..proto.outputs.len() { | ||||
|             for i1 in i0+1..proto.outputs.len() { | ||||
|                 if proto.outputs[i0] == proto.outputs[i1] {  | ||||
|                     return Err(LanskyError::DuplicateOutput { name: proto.outputs[i0].clone(), i0, i1 })  | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // TODO: Validate the code in some way? | ||||
|         // Not needed for native devices | ||||
|  | ||||
|         Ok(Device { proto, implementation }) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn analyze(&self, rack: &Rack) -> Lansky<()> { | ||||
|         match &self.implementation { | ||||
|             Implementation::Native(_) => {} | ||||
|             Implementation::Hosted(h) => { | ||||
|                 h.analyze(rack)?; | ||||
|             } | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub fn get_proto(&self) -> &Proto { | ||||
|         &self.proto | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn invoke_named(&self, rack: &Rack, mut inputs: HashMap<String, Channel>) -> Lansky<HashMap<String, Channel>> { | ||||
|         let expected = self.proto.inputs.len(); | ||||
|         let got = inputs.len(); | ||||
|         if expected != got { return Err(LanskyError::WrongNInputs { expected, got }) } | ||||
|  | ||||
|         let mut inputs_2 = Vec::with_capacity(self.proto.inputs.len()); | ||||
|         for i in self.proto.inputs.iter() { | ||||
|             let Some(channel_in) = inputs.remove(i) else { | ||||
|                 return Err(LanskyError::MissingInput { name: i.clone() }) | ||||
|             }; | ||||
|             inputs_2.push(channel_in) | ||||
|         } | ||||
|         for i in inputs.keys() { | ||||
|             if !self.proto.inputs.contains(i) { | ||||
|                 return Err(LanskyError::UnknownInput { name: i.clone() }) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let outputs = self.invoke_positional(rack, inputs_2)?; | ||||
|  | ||||
|         let mut outputs_2 = HashMap::new(); | ||||
|         for (k, v) in self.proto.outputs.iter().zip(outputs.into_iter()) { | ||||
|             outputs_2.insert(k.clone(), v); | ||||
|         } | ||||
|         Ok(outputs_2) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn invoke_positional(&self, rack: &Rack, inputs: Vec<Channel>) -> Lansky<Vec<Channel>> { | ||||
|         let expected = self.proto.inputs.len(); | ||||
|         let got = inputs.len(); | ||||
|         if expected != got { return Err(LanskyError::WrongNInputs { expected, got }) } | ||||
|         let outputs = match &self.implementation { | ||||
|             Implementation::Native(f) => f(inputs)?, | ||||
|             Implementation::Hosted(procedure) => procedure.invoke(rack, inputs)?, | ||||
|         }; | ||||
|  | ||||
|         let expected = self.proto.outputs.len(); | ||||
|         let got = outputs.len(); | ||||
|         if expected != got { return Err(LanskyError::WrongNOutputs { expected, got }) } | ||||
|         Ok(outputs) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										38
									
								
								src/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/error.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| pub type Lansky<T> = Result<T, LanskyError>; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub enum LanskyError { | ||||
|     DuplicateDevice { name: String }, | ||||
|     UnknownDevice { name: String }, | ||||
|  | ||||
|     DuplicateInput { name: String, i0: usize, i1: usize }, | ||||
|     DuplicateOutput { name: String, i0: usize, i1: usize }, | ||||
|      | ||||
|     MissingInput { name: String }, | ||||
|     UnknownInput { name: String }, | ||||
|  | ||||
|     WrongNInputs { expected: usize, got: usize }, | ||||
|     WrongNOutputs { expected: usize, got: usize }, | ||||
|  | ||||
|     NoFirstBlock,  | ||||
|  | ||||
|     WrongNStack { why: LanskyStackReason, expected: usize, got: usize }, | ||||
|  | ||||
|     MissingRegA { i: usize }, | ||||
|     MissingRegZ { i: usize }, | ||||
|  | ||||
|     WrongNChannels { expected: usize, got: usize }, | ||||
|  | ||||
|     IO(std::io::Error), | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub enum LanskyStackReason { | ||||
|     CannotDupEmptyStack, | ||||
|     CannotOupEmptyStack, | ||||
|     CannotPopEmptyStack, | ||||
|  | ||||
|     BlockMustEndWithEmptyStack, | ||||
|  | ||||
|     InvokeNInputs | ||||
| } | ||||
							
								
								
									
										98
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| // TODOs: | ||||
| // Generate at 88200Hz, downsample to 44100Hz | ||||
| // Parser for assembly language | ||||
| // More context in error messages | ||||
| // Replace the sample type with something more Pico 8 fixedpoint-like (right now we are always falling down on the i64 interface) | ||||
| // Use the _top_ part when coercing the sample type to an i32 | ||||
| // jmp and similar instructions | ||||
| // Use primarily the named interface and allow defaulting | ||||
| // Channel-like objects that produce a constant value  | ||||
|  | ||||
| use std::{path::Path, fs::File}; | ||||
|  | ||||
| use audio::{Sample, Channel}; | ||||
| use device::Device; | ||||
| use error::{Lansky, LanskyError}; | ||||
| use rack::Rack; | ||||
| use vm::{Block, BodyInstruction, ExitInstruction, MovSrc, OupDst, RegZ}; | ||||
|  | ||||
| mod audio; | ||||
| mod error; | ||||
| mod device; | ||||
| mod standard_devices; | ||||
| mod rack; | ||||
| mod vm; | ||||
|  | ||||
| fn main() { | ||||
|     main_err().expect("crash:"); | ||||
| } | ||||
|  | ||||
|  | ||||
| fn main_err() -> Lansky<()> { | ||||
|     let mut rack = Rack::new(); | ||||
|  | ||||
|     rack.add_device(Device::hosted( | ||||
|         "main".to_string(), | ||||
|         vec![], | ||||
|         vec!["audio".to_string()], | ||||
|         vec![ | ||||
|             Block { | ||||
|                 name: "entry".to_string(), | ||||
|                 body: vec![ | ||||
|                     BodyInstruction::New(MovSrc::Imm(Sample(0)), MovSrc::Imm(Sample(100000)), MovSrc::Imm(Sample(440))), | ||||
|                     BodyInstruction::Invoke("gensin".to_string(), 1, 1), | ||||
|                     BodyInstruction::Pop(OupDst::Z(RegZ(0))), | ||||
|                 ], | ||||
|                 exit: ExitInstruction::Exit, | ||||
|             } | ||||
|         ], | ||||
|     )?)?; | ||||
|  | ||||
|     rack.analyze_all()?; | ||||
|  | ||||
|     let result =  | ||||
|         rack.invoke_positional("main", vec![])?; | ||||
|  | ||||
|     export(Path::new("example.wav"), result)?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn export(path: &Path, audio: Vec<Channel>) -> Lansky<()> { | ||||
|     match &audio[..] { | ||||
|         [mono] => { | ||||
|             let header = wav::Header::new( | ||||
|                 wav::header::WAV_FORMAT_PCM, | ||||
|                 1, | ||||
|                 44100, | ||||
|                 16 | ||||
|             ); | ||||
|  | ||||
|             let mut out_file = File::create(path).map_err(|e| LanskyError::IO(e))?; | ||||
|             let data = channel_to_data(&mono); | ||||
|             wav::write(header, &data, &mut out_file).map_err(|e| LanskyError::IO(e))?; | ||||
|  | ||||
|             Ok(()) | ||||
|         }, | ||||
|         _ => { | ||||
|             return Err(LanskyError::WrongNChannels { expected: 1, got: audio.len() }) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn channel_to_data(channel: &Channel) -> wav::BitDepth { | ||||
|     let mut v: Vec<i16> = vec![]; | ||||
|     for i in channel.bounds().informal { | ||||
|         v.push(channel[i].to_i16_sample()) | ||||
|     } | ||||
|     wav::BitDepth::Sixteen(v) | ||||
| } | ||||
|  | ||||
| /* | ||||
| fn main_err() -> std::io::Result<()> { | ||||
|     let mut out_file = File::create(Path::new("output.wav"))?; | ||||
|     wav::write(header, &data, &mut out_file)?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
| */ | ||||
							
								
								
									
										56
									
								
								src/rack.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/rack.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use crate::{device::{Device, Proto}, error::{Lansky, LanskyError}, standard_devices, audio::Channel}; | ||||
|  | ||||
| pub struct Rack { | ||||
|     devices: HashMap<String, Device> | ||||
| } | ||||
|  | ||||
| impl Rack { | ||||
|     pub fn new() -> Self { | ||||
|         let mut r = Rack { | ||||
|             devices: HashMap::new() | ||||
|         }; | ||||
|         standard_devices::augment(&mut r); | ||||
|         r | ||||
|     } | ||||
|  | ||||
|     pub fn analyze_all(&self) -> Lansky<()> { | ||||
|         for d in self.devices.values() { | ||||
|             d.analyze(self)?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     pub fn add_device(&mut self, device: Device) -> Lansky<()> { | ||||
|         if self.devices.contains_key(&device.get_proto().name) { | ||||
|             return Err(LanskyError::DuplicateDevice { name: device.get_proto().name.clone() }) | ||||
|         } | ||||
|  | ||||
|         let name = device.get_proto().name.clone(); | ||||
|         self.devices.insert(name, device); | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn get_device(&self, name: &str) -> Lansky<&Device> { | ||||
|         let Some(device) = self.devices.get(name) else { | ||||
|             return Err(LanskyError::UnknownDevice { name: name.to_string() }) | ||||
|         }; | ||||
|  | ||||
|         Ok(device) | ||||
|     } | ||||
|  | ||||
|     pub fn invoke_named(&self, name: &str, inputs: HashMap<String, Channel>) -> Lansky<HashMap<String, Channel>> { | ||||
|         self.get_device(name)?.invoke_named(self, inputs) | ||||
|     } | ||||
|  | ||||
|     pub fn invoke_positional(&self, name: &str, inputs: Vec<Channel>) -> Lansky<Vec<Channel>> { | ||||
|         self.get_device(name)?.invoke_positional(self, inputs) | ||||
|     } | ||||
|  | ||||
|     pub fn get_proto(&self, name: &str) -> Lansky<&Proto> { | ||||
|         Ok(self.get_device(name)?.get_proto()) | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/standard_devices/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/standard_devices/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| use std::f32::consts::PI; | ||||
|  | ||||
| use crate::{rack::Rack, device::Device, audio::{Channel, Sample}, error::Lansky}; | ||||
|  | ||||
| pub fn augment(r: &mut Rack) -> Lansky<()> { | ||||
|     r.add_device(Device::native( | ||||
|         "gensin".to_string(),  | ||||
|         vec!["frequency".to_string()], | ||||
|         vec!["signal".to_string()], | ||||
|         gensin | ||||
|     )?)?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| fn gensin(mut chans: Vec<Channel>) -> Lansky<Vec<Channel>> { | ||||
|     assert!(chans.len() == 1); | ||||
|     let inp = chans.pop().expect("must be one channel"); | ||||
|  | ||||
|     let mut pos = 0.0; | ||||
|     let sr = 44100.0; | ||||
|  | ||||
|     let mut oup = Channel::new(inp.formal()); | ||||
|  | ||||
|     for i in inp.bounds().informal { | ||||
|         let freq = inp[i].0 as f32; | ||||
|         pos += freq/sr; | ||||
|         pos %= 1.0; | ||||
|         oup[i] = Sample(((pos * PI * 2.0).sin() * i32::MAX as f32) as i64); | ||||
|     } | ||||
|  | ||||
|     Ok(vec![oup]) | ||||
| } | ||||
							
								
								
									
										519
									
								
								src/vm.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										519
									
								
								src/vm.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,519 @@ | ||||
| use std::cell::Cell; | ||||
|  | ||||
| use crate::{audio::{Channel, Sample}, error::{Lansky, LanskyError, LanskyStackReason}, device::Proto, rack::Rack}; | ||||
|  | ||||
| pub(crate) struct Procedure { | ||||
|     proto: Proto, | ||||
|     blocks: Vec<Block>, | ||||
|     analysis: Cell<Option<Analysis>>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| struct Analysis { | ||||
|     stack_depth: usize, | ||||
|  | ||||
|     proto_n_inputs: usize, | ||||
|     proto_n_outputs: usize, | ||||
|  | ||||
|     n_reg_ls: usize, | ||||
| } | ||||
|  | ||||
| pub struct Block { | ||||
|     pub name: String, | ||||
|     pub body: Vec<BodyInstruction>, | ||||
|     pub exit: ExitInstruction | ||||
| } | ||||
|  | ||||
| pub enum BodyInstruction { | ||||
|     Mov(MovType, MovDst, MovSrc), | ||||
|     Range(RangeDst, i32, i32), | ||||
|     Inp(InpSrc), | ||||
|     Dup(), | ||||
|     Oup(MovType, OupDst), | ||||
|     Pop(OupDst), | ||||
|     New(MovSrc, MovSrc, MovSrc), | ||||
|     Invoke(String, usize, usize), | ||||
| } | ||||
|  | ||||
| pub enum ExitInstruction { | ||||
|     Exit, | ||||
| } | ||||
|  | ||||
| pub enum MovType { Mov, Add, Xor } | ||||
|  | ||||
| #[derive(Clone, Copy)] pub enum MovDst { L(RegL), R(RegR), Z(RegZ), Ix } | ||||
| #[derive(Clone, Copy)] pub enum MovSrc { A(RegA), L(RegL), R(RegR), Z(RegZ), Ix, Imm(Sample) } | ||||
| #[derive(Clone, Copy)] pub enum RangeDst { A(RegA), L(RegL), Z(RegZ) } | ||||
| #[derive(Clone, Copy)] pub enum InpSrc { A(RegA), L(RegL), Z(RegZ) } | ||||
| #[derive(Clone, Copy)] pub enum OupDst { L(RegL), Z(RegZ) } | ||||
|  | ||||
| impl OupDst { | ||||
|     fn to_mov_dst(self) -> MovDst { | ||||
|         match self { | ||||
|             Self::L(l) => MovDst::L(l), | ||||
|             Self::Z(z) => MovDst::Z(z), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] pub struct RegA(pub u8); | ||||
| #[derive(Clone, Copy)] pub struct RegL(pub u8); | ||||
| #[derive(Clone, Copy)] pub struct RegR(pub u8); | ||||
| #[derive(Clone, Copy)] pub struct RegZ(pub u8); | ||||
|  | ||||
| struct Scope { | ||||
|     ix: Sample, | ||||
|  | ||||
|     reg_as: Vec<Channel>, | ||||
|     reg_ls: Vec<Channel>, | ||||
|     reg_rs: [Sample; 256], | ||||
|     reg_zs: Vec<Channel>, | ||||
|  | ||||
|     loop_as: [LoopRange; 256], | ||||
|     loop_ls: [LoopRange; 256], | ||||
|     loop_zs: [LoopRange; 256], | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Copy)] | ||||
| struct LoopRange { | ||||
|     start: i32, | ||||
|     length: i32,  | ||||
| } | ||||
|  | ||||
| impl Default for LoopRange { | ||||
|     fn default() -> Self { | ||||
|         Self { start: 0, length: 0 } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Scope { | ||||
|     fn new(analysis: Analysis, inputs: Vec<Channel>) -> Scope { | ||||
|         assert_eq!(inputs.len(), analysis.proto_n_inputs); // analysis is done elsewhere | ||||
|  | ||||
|         let reg_as: Vec<Channel> = inputs; | ||||
|         let mut reg_ls: Vec<Channel> = vec![]; | ||||
|         let reg_rs: [Sample; 256] = [Sample::default(); 256]; | ||||
|         let mut reg_zs: Vec<Channel> = vec![]; | ||||
|  | ||||
|         for _ in 0..analysis.n_reg_ls { reg_ls.push(Channel::blank()); } | ||||
|         for _ in 0..analysis.proto_n_outputs { reg_zs.push(Channel::blank()); } | ||||
|  | ||||
|         Scope { | ||||
|             ix: Sample(0), | ||||
|  | ||||
|             reg_as,  | ||||
|             reg_ls,  | ||||
|             reg_rs,  | ||||
|             reg_zs, | ||||
|  | ||||
|             loop_as: [LoopRange::default(); 256], | ||||
|             loop_ls: [LoopRange::default(); 256], | ||||
|             loop_zs: [LoopRange::default(); 256], | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn exit(self) -> Vec<Channel> { | ||||
|         self.reg_zs | ||||
|     } | ||||
|  | ||||
|     fn read(&self, src: MovSrc) -> Sample { | ||||
|         let flat_ix = self.ix.0 as i32; | ||||
|         let (chan, loop_) = match src { | ||||
|             MovSrc::A(RegA(a)) => { | ||||
|                 let a = a as usize; | ||||
|                 (&self.reg_as[a], &self.loop_as[a]) | ||||
|             } | ||||
|             MovSrc::L(RegL(l)) => { | ||||
|                 let l = l as usize; | ||||
|                 (&self.reg_ls[l], &self.loop_ls[l]) | ||||
|             } | ||||
|             MovSrc::R(RegR(r)) => { | ||||
|                 let r = r as usize; | ||||
|                 return self.reg_rs[r] | ||||
|             } | ||||
|             MovSrc::Z(RegZ(z)) => { | ||||
|                 let z = z as usize; | ||||
|                 (&self.reg_zs[z], &self.loop_zs[z]) | ||||
|             } | ||||
|             MovSrc::Ix => return self.ix, | ||||
|             MovSrc::Imm(i) => return i, | ||||
|         }; | ||||
|  | ||||
|         let mut ix = flat_ix; | ||||
|         if loop_.length != 0 { ix %= loop_.length; } | ||||
|         ix += chan.formal().start + loop_.start; | ||||
|  | ||||
|         chan[ix] | ||||
|     } | ||||
|  | ||||
|     fn write(&mut self, ty: &MovType, dst: MovDst, value: Sample, offset: i32) { | ||||
|         let flat_ix = self.ix.0 as i32; | ||||
|         let (chan, loop_) = match dst { | ||||
|             MovDst::L(RegL(l)) => { | ||||
|                 let l = l as usize; | ||||
|                 (&mut self.reg_ls[l], &self.loop_ls[l]) | ||||
|             } | ||||
|             MovDst::R(RegR(r)) => { | ||||
|                 let r = r as usize; | ||||
|                 self.reg_rs[r] = value; return | ||||
|             } | ||||
|             MovDst::Z(RegZ(z)) => { | ||||
|                 let z = z as usize; | ||||
|                 (&mut self.reg_zs[z], &self.loop_zs[z]) | ||||
|             } | ||||
|             MovDst::Ix => { self.ix = value; return } | ||||
|         }; | ||||
|  | ||||
|         let mut ix = flat_ix + offset; | ||||
|         if loop_.length != 0 { ix %= loop_.length; } | ||||
|         ix += chan.formal().start + loop_.start; | ||||
|  | ||||
|         chan[ix] = value | ||||
|     } | ||||
|  | ||||
|     fn replace(&mut self, dst: OupDst, value: Channel) { | ||||
|         match dst { | ||||
|             OupDst::L(RegL(l)) => {  | ||||
|                 self.reg_ls[l as usize] = value;  | ||||
|                 self.loop_ls[l as usize] = LoopRange::default()  | ||||
|             } | ||||
|             OupDst::Z(RegZ(z)) => {  | ||||
|                 self.reg_zs[z as usize] = value;  | ||||
|                 self.loop_zs[z as usize] = LoopRange::default()  | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn apply_range(&mut self, dst: RangeDst, start: i32, length: i32) { | ||||
|         let lr = LoopRange{start, length}; | ||||
|         match dst { | ||||
|             RangeDst::A(RegA(a)) => { | ||||
|                 self.loop_as[a as usize] = lr | ||||
|             } | ||||
|             RangeDst::L(RegL(l)) => { | ||||
|                 self.loop_ls[l as usize] = lr | ||||
|             } | ||||
|             RangeDst::Z(RegZ(z)) => { | ||||
|                 self.loop_zs[z as usize] = lr | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn inp(&self, src: InpSrc) -> Channel { | ||||
|         let (chan, loop_) = match src { | ||||
|             InpSrc::A(RegA(a)) => { | ||||
|                 let a = a as usize; | ||||
|                 (&self.reg_as[a], &self.loop_as[a]) | ||||
|             } | ||||
|             InpSrc::L(RegL(l)) => { | ||||
|                 let l = l as usize; | ||||
|                 (&self.reg_ls[l], &self.loop_ls[l]) | ||||
|             } | ||||
|             InpSrc::Z(RegZ(z)) => { | ||||
|                 let z = z as usize; | ||||
|                 (&self.reg_zs[z], &self.loop_zs[z]) | ||||
|             } | ||||
|         }; | ||||
|         chan.slice(loop_.start, loop_.length) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Procedure { | ||||
|     pub(crate) fn new(proto: Proto, blocks: Vec<Block>) -> Procedure { | ||||
|         Procedure { | ||||
|             proto, | ||||
|             blocks, | ||||
|             analysis: Cell::new(None), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn invoke(&self, rack: &Rack, inputs: Vec<Channel>) -> Lansky<Vec<Channel>> { | ||||
|         let analysis = self.internal_analyze(rack)?; | ||||
|  | ||||
|         let mut scope = Scope::new(analysis, inputs); | ||||
|         let mut stack: Vec<Channel> = vec![]; | ||||
|  | ||||
|         let block_ix = 0; | ||||
|         loop { | ||||
|             let block = &self.blocks[block_ix]; | ||||
|             for instruction in block.body.iter() { | ||||
|                 match instruction { | ||||
|                     BodyInstruction::Mov(ty, dst, src) => { | ||||
|                         scope.write(ty, *dst, scope.read(*src), 0) | ||||
|                     } | ||||
|                     BodyInstruction::Range(dst, start, len) => { | ||||
|                         scope.apply_range(*dst, *start, *len) | ||||
|                     } | ||||
|                     BodyInstruction::Inp(src) => { | ||||
|                         let slice = scope.inp(*src); | ||||
|                         stack.push(slice) | ||||
|                     } | ||||
|                     BodyInstruction::Dup() => { | ||||
|                         let top = stack.pop().expect("stack can't be empty"); | ||||
|                         stack.push(top.clone()) | ||||
|                     } | ||||
|                     BodyInstruction::Oup(ty, dst) => { | ||||
|                         let top = stack.pop().expect("stack can't be empty"); | ||||
|                         let bounds = top.bounds(); | ||||
|                         for ix in bounds.informal { | ||||
|                             let offset = ix - bounds.formal.start; | ||||
|                             scope.write(ty, dst.to_mov_dst(), top[ix], offset) | ||||
|                         } | ||||
|                     } | ||||
|                     BodyInstruction::Pop(dst) => { | ||||
|                         let top = stack.pop().expect("stack can't be empty"); | ||||
|                         scope.replace(*dst, top) | ||||
|                     } | ||||
|                     BodyInstruction::New(formal_start, formal_len, fill) => { | ||||
|                         let start = scope.read(*formal_start); | ||||
|                         let len = scope.read(*formal_len); | ||||
|                         let fill = scope.read(*fill); | ||||
|                         let mut chan = Channel::new((start.0 as i32)..((start.0+len.0) as i32)); | ||||
|                         for i in chan.formal() { | ||||
|                             chan[i] = fill | ||||
|                         } | ||||
|                         stack.push(chan) | ||||
|                     } | ||||
|                     BodyInstruction::Invoke(device, n_inputs, n_outputs) => { | ||||
|                         let mut inputs: Vec<Channel> = vec![];  | ||||
|                         std::mem::swap(&mut stack, &mut inputs); | ||||
|                         assert_eq!(inputs.len(), *n_inputs); | ||||
|                         let outputs = rack.invoke_positional(device, inputs)?; | ||||
|                         assert_eq!(outputs.len(), *n_outputs); | ||||
|                         stack.extend(outputs); | ||||
|                         stack.reverse() | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             match block.exit { | ||||
|                 ExitInstruction::Exit => return Ok(scope.exit()) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn analyze(&self, rack: &Rack) -> Lansky<()> { | ||||
|         self.internal_analyze(rack)?; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn internal_analyze(&self, rack: &Rack) -> Lansky<Analysis> { | ||||
|         if let Some(x) = self.analysis.get() { return Ok(x) } | ||||
|  | ||||
|         let mut analysis = Analysis {  | ||||
|             stack_depth: 0,  | ||||
|  | ||||
|             proto_n_inputs: self.proto.inputs.len(), | ||||
|             proto_n_outputs: self.proto.outputs.len(), | ||||
|  | ||||
|             n_reg_ls: 0  | ||||
|         }; | ||||
|  | ||||
|         if self.blocks.len() == 0 { | ||||
|             return Err(LanskyError::NoFirstBlock); | ||||
|         } | ||||
|  | ||||
|         for block in self.blocks.iter() { | ||||
|             block.touch_analysis( | ||||
|                 &mut analysis, &|d| rack.get_proto(d).cloned() | ||||
|             )?; | ||||
|         } | ||||
|  | ||||
|         self.analysis.set(Some(analysis)); | ||||
|  | ||||
|         return Ok(analysis) | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| impl Block { | ||||
|     fn touch_analysis(&self, analysis: &mut Analysis, get_proto: &impl Fn(&str) -> Lansky<Proto>) -> Lansky<()> { | ||||
|         for instruction in self.body.iter() { | ||||
|             instruction.touch_analysis(analysis, get_proto)?; | ||||
|         } | ||||
|  | ||||
|         if analysis.stack_depth != 0 { | ||||
|             return Err(LanskyError::WrongNStack {  | ||||
|                 why: LanskyStackReason::BlockMustEndWithEmptyStack, | ||||
|                 got: analysis.stack_depth, expected: 0  | ||||
|             }) | ||||
|         } | ||||
|         self.exit.touch_analysis(analysis)?; | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl BodyInstruction { | ||||
|     fn touch_analysis(&self, analysis: &mut Analysis, get_proto: &impl Fn(&str) -> Lansky<Proto>) -> Lansky<()> { | ||||
|         match self { | ||||
|             BodyInstruction::Mov(_, dst, src) => { | ||||
|                 dst.touch_analysis(analysis)?; | ||||
|                 src.touch_analysis(analysis)?; | ||||
|             } | ||||
|             BodyInstruction::Range(dst, _, _) => { | ||||
|                 dst.touch_analysis(analysis)?; | ||||
|             } | ||||
|             BodyInstruction::Inp(src) => { | ||||
|                 analysis.stack_depth += 1; | ||||
|                 src.touch_analysis(analysis)?; | ||||
|             } | ||||
|             BodyInstruction::Dup() => { | ||||
|                 if analysis.stack_depth == 0 { | ||||
|                     return Err(LanskyError::WrongNStack {  | ||||
|                         why: LanskyStackReason::CannotDupEmptyStack, | ||||
|                         expected: 1, got: 0  | ||||
|                     }) | ||||
|                 } | ||||
|                 analysis.stack_depth += 1; | ||||
|             } | ||||
|             BodyInstruction::Oup(_, dst) => { | ||||
|                 if analysis.stack_depth == 0 { | ||||
|                     return Err(LanskyError::WrongNStack {  | ||||
|                         why: LanskyStackReason::CannotOupEmptyStack, | ||||
|                         expected: 1, got: 0  | ||||
|                     }) | ||||
|                 } | ||||
|                 analysis.stack_depth -= 1; | ||||
|  | ||||
|                 dst.touch_analysis(analysis)?; | ||||
|             } | ||||
|             BodyInstruction::Pop(dst) => { | ||||
|                 if analysis.stack_depth == 0 { | ||||
|                     return Err(LanskyError::WrongNStack {  | ||||
|                         why: LanskyStackReason::CannotPopEmptyStack, | ||||
|                         expected: 1, got: 0  | ||||
|                     }) | ||||
|                 } | ||||
|                 analysis.stack_depth -= 1; | ||||
|  | ||||
|                 dst.touch_analysis(analysis)?; | ||||
|             } | ||||
|             BodyInstruction::New(formal_start, formal_len, fill) => { | ||||
|                 analysis.stack_depth += 1; | ||||
|  | ||||
|                 formal_start.touch_analysis(analysis)?; | ||||
|                 formal_len.touch_analysis(analysis)?; | ||||
|                 fill.touch_analysis(analysis)?; | ||||
|             } | ||||
|             BodyInstruction::Invoke(device, n_inputs, n_outputs) => { | ||||
|                 let proto = get_proto(device)?; | ||||
|  | ||||
|                 let actual_n_inputs = proto.inputs.len(); | ||||
|                 let actual_n_outputs = proto.outputs.len(); | ||||
|  | ||||
|                 if *n_inputs != actual_n_inputs { | ||||
|                     return Err(LanskyError::WrongNInputs { expected: actual_n_inputs, got: *n_inputs }) | ||||
|                 } | ||||
|  | ||||
|                 if *n_outputs != actual_n_outputs { | ||||
|                     return Err(LanskyError::WrongNInputs { expected: actual_n_inputs, got: *n_outputs }) | ||||
|                 } | ||||
|  | ||||
|                 if analysis.stack_depth != *n_inputs { | ||||
|                     return Err(LanskyError::WrongNStack { | ||||
|                         why: LanskyStackReason::InvokeNInputs, | ||||
|                         expected: *n_inputs, | ||||
|                         got: analysis.stack_depth, | ||||
|                     }) | ||||
|                 } | ||||
|                 analysis.stack_depth = *n_outputs; | ||||
|             } | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ExitInstruction { | ||||
|     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||
|         match self { | ||||
|             ExitInstruction::Exit => {} | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MovDst { | ||||
|     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||
|         match self { | ||||
|             Self::L(l) => l.touch_analysis(analysis), | ||||
|             Self::R(r) => r.touch_analysis(analysis), | ||||
|             Self::Z(z) => z.touch_analysis(analysis), | ||||
|             Self::Ix => Ok(()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MovSrc { | ||||
|     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||
|         match self { | ||||
|             Self::A(a) => a.touch_analysis(analysis), | ||||
|             Self::L(l) => l.touch_analysis(analysis), | ||||
|             Self::R(r) => r.touch_analysis(analysis), | ||||
|             Self::Z(z) => z.touch_analysis(analysis), | ||||
|             Self::Ix => Ok(()), | ||||
|             Self::Imm(_) => Ok(()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl RangeDst { | ||||
|     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||
|         match self { | ||||
|             Self::A(a) => a.touch_analysis(analysis), | ||||
|             Self::L(l) => l.touch_analysis(analysis), | ||||
|             Self::Z(z) => z.touch_analysis(analysis), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl InpSrc { | ||||
|     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||
|         match self { | ||||
|             Self::A(a) => a.touch_analysis(analysis), | ||||
|             Self::L(l) => l.touch_analysis(analysis), | ||||
|             Self::Z(z) => z.touch_analysis(analysis), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl OupDst { | ||||
|     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||
|         match self { | ||||
|             Self::L(l) => l.touch_analysis(analysis), | ||||
|             Self::Z(z) => z.touch_analysis(analysis), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| impl RegA { | ||||
|     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||
|         if self.0 as usize >= analysis.proto_n_inputs { | ||||
|             return Err(LanskyError::MissingRegA { i: self.0 as usize }); | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl RegL { | ||||
|     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||
|         analysis.n_reg_ls = analysis.n_reg_ls.max(self.0 as usize); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl RegR { | ||||
|     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl RegZ { | ||||
|     fn touch_analysis(&self, analysis: &mut Analysis) -> Lansky<()> { | ||||
|         if self.0 as usize >= analysis.proto_n_outputs { | ||||
|             return Err(LanskyError::MissingRegZ { i: self.0 as usize }); | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user