Initial commit, very broken
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
1313
Cargo.lock
generated
Normal file
1313
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "tracker-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
modfile = "1.4.6"
|
||||
rodio = { version = "0.16.0", features = ["symphonia-all"] }
|
||||
sdl2 = { version = "0.35", optional = true }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
sdl2 = { version = "0.35", features = ["bundled", "static-link"], optional = true }
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Irjan Vučković Olsen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
16
src/main.rs
Normal file
16
src/main.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
mod note;
|
||||
mod tracker;
|
||||
mod sampler;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::time::Duration;
|
||||
use rodio::{Decoder, OutputStream, Sink, Source};
|
||||
use rodio::source::SineWave;
|
||||
use crate::tracker::Tracker;
|
||||
|
||||
fn main() {
|
||||
let mut tracker = Tracker::new(4, 64);
|
||||
tracker.load_file("./3266CHIP.MOD");
|
||||
tracker.play();
|
||||
}
|
||||
22
src/note.rs
Normal file
22
src/note.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Note {
|
||||
pub frequency: f32,
|
||||
pub sample: u8,
|
||||
pub effect: u16,
|
||||
}
|
||||
|
||||
impl Display for Note {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "freq {}, sample: {}, effect: {}", self.frequency, self.sample, self.effect)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
pub fn note_to_frequency() {
|
||||
|
||||
}
|
||||
}
|
||||
57
src/sampler.rs
Normal file
57
src/sampler.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use std::time::Duration;
|
||||
use modfile::ptmf::SampleInfo;
|
||||
use rodio::Source;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Sample {
|
||||
num_sample: usize,
|
||||
_sample_rate: u32,
|
||||
sample: SampleInfo,
|
||||
}
|
||||
|
||||
impl Sample {
|
||||
pub fn new(sample_rate: u32, sample: SampleInfo) -> Self {
|
||||
Self {
|
||||
num_sample: 0,
|
||||
_sample_rate: sample_rate,
|
||||
sample,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_sample_rate(&mut self, sample_rate: u32) {
|
||||
self._sample_rate = sample_rate;
|
||||
self.num_sample = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Sample {
|
||||
type Item = f32;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.sample.data.len() == 0 {
|
||||
return Some(0.0);
|
||||
}
|
||||
if self.num_sample >= self.sample.data.len() {
|
||||
self.num_sample = 0;
|
||||
}
|
||||
let value = self.sample.data[self.num_sample];
|
||||
self.num_sample = self.num_sample.wrapping_add(1);
|
||||
Some((value as i8 as f32)/(i8::MAX as f32))
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for Sample {
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
Some(self.sample.data.len()-self.num_sample)
|
||||
}
|
||||
|
||||
fn channels(&self) -> u16 { 1 }
|
||||
|
||||
fn sample_rate(&self) -> u32 {
|
||||
self._sample_rate
|
||||
}
|
||||
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
Some(Duration::new(0, (self.sample.data.len() * 1_000_000_000 / self._sample_rate as usize) as u32))
|
||||
}
|
||||
}
|
||||
131
src/tracker.rs
Normal file
131
src/tracker.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::time::Duration;
|
||||
use modfile::ptmf;
|
||||
use rodio::{OutputStream, OutputStreamHandle, Sink, Source};
|
||||
use rodio::source::SineWave;
|
||||
use crate::note::Note;
|
||||
use crate::sampler::Sample;
|
||||
|
||||
pub struct Tracker {
|
||||
stream: OutputStream,
|
||||
stream_handle: OutputStreamHandle,
|
||||
sinks: Vec<Sink>,
|
||||
patterns: Vec<Vec<Vec<Option<Note>>>>,
|
||||
positions: Vec<u8>,
|
||||
samples: Vec<Sample>,
|
||||
}
|
||||
|
||||
impl Tracker {
|
||||
pub fn new(tracks: u8, lines: u8) -> Self {
|
||||
let (stream, stream_handle) = OutputStream::try_default().unwrap();
|
||||
let mut _sinks = vec![];
|
||||
let mut _tracks = vec![];
|
||||
|
||||
for _ in 0..tracks {
|
||||
_sinks.push(Sink::try_new(&stream_handle).unwrap());
|
||||
_tracks.push(vec![None; lines as usize])
|
||||
}
|
||||
|
||||
|
||||
Self {
|
||||
stream, stream_handle,
|
||||
sinks: _sinks, patterns: vec![_tracks.clone()],
|
||||
positions: vec![],
|
||||
samples: vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play(&mut self) {
|
||||
for sink in &self.sinks {
|
||||
sink.pause();
|
||||
}
|
||||
|
||||
let lines = self.patterns[0][0].len();
|
||||
println!("Playing {} lines", lines);
|
||||
for &position in &self.positions {
|
||||
let pattern = &self.patterns[position as usize];
|
||||
for line in 0..lines {
|
||||
for (track, notes) in pattern.iter().enumerate() {
|
||||
let note = notes[line].as_ref();
|
||||
if note.is_some() {
|
||||
//println!("Playing note: {}", note.as_ref().unwrap());
|
||||
let note = note.unwrap();
|
||||
let frequency = note.frequency;
|
||||
let sample = note.sample;
|
||||
self.samples[sample as usize].set_sample_rate(frequency as u32);
|
||||
self.sinks[track].append(
|
||||
self.samples[sample as usize].clone()
|
||||
.take_duration(Duration::from_secs_f64(60.0/375.0))
|
||||
.amplify(0.20)
|
||||
)
|
||||
} else {
|
||||
//println!("Playing note: ---");
|
||||
self.sinks[track].append(
|
||||
SineWave::new(0.0)
|
||||
.take_duration(Duration::from_secs_f64(60.0/375.0))
|
||||
.amplify(0.20)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for sink in &self.sinks {
|
||||
sink.play();
|
||||
}
|
||||
|
||||
for sink in &self.sinks {
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_file(&mut self, file: &str) {
|
||||
let mut reader = BufReader::new(File::open(file).unwrap());
|
||||
let mut module = ptmf::read_mod(&mut reader, false).unwrap();
|
||||
|
||||
println!("Loading: {}", module.name);
|
||||
println!("\tLength: {:?}", module.length);
|
||||
//println!("\tPatterns: {:?}", module.patterns);
|
||||
println!("\tPositions: {:?}", module.positions);
|
||||
println!("\tSamples: {:?}", module.sample_info.len());
|
||||
//println!("\tSamples: {:?}", module.sample_info);
|
||||
|
||||
for i in 0..module.length {
|
||||
self.positions.push(module.positions.data[i as usize]);
|
||||
}
|
||||
|
||||
for sample in module.sample_info {
|
||||
self.samples.push(Sample::new(44100, sample.clone()));
|
||||
}
|
||||
|
||||
let mut prev_freqs = vec![1.0; module.length as usize];
|
||||
|
||||
for _ in 0..(module.patterns.len()-1) {
|
||||
self.patterns.push(self.patterns[0].clone());
|
||||
}
|
||||
|
||||
for (patern_index, pattern) in module.patterns.iter().enumerate() {
|
||||
for (line, row) in pattern.rows.iter().enumerate() {
|
||||
for (track, channel) in row.channels.iter().enumerate() {
|
||||
let frequency = if channel.period == 0 {
|
||||
prev_freqs[track]
|
||||
} else {
|
||||
7093789.2/(2.0*channel.period as f32)
|
||||
};
|
||||
|
||||
prev_freqs[track] = frequency;
|
||||
|
||||
println!("{patern_index}, {track}, {line}");
|
||||
|
||||
self.patterns[patern_index][track][line] = Some(Note {
|
||||
frequency,
|
||||
sample: channel.sample_number,
|
||||
effect: channel.effect
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user