Skip to content

Commit

Permalink
Add drumkit sounds
Browse files Browse the repository at this point in the history
  • Loading branch information
MPSxDev committed Sep 25, 2024
1 parent 2a859c9 commit ebdd8a7
Show file tree
Hide file tree
Showing 3 changed files with 290 additions and 0 deletions.
106 changes: 106 additions & 0 deletions src/contract/notes.cairo
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
use cairo_wave::note::Note;
use core::debug::PrintTrait;
use core::byte_array::ByteArray;

#[starknet::interface]
trait INotesContract<TContractState> {
fn get_note(self: @TContractState, note: Note) -> ByteArray;
fn get_melody(self: @TContractState, note_dur_ms: u32) -> ByteArray;
fn get_drum_kit(self: @TContractState) -> ByteArray;
}

#[starknet::contract]
mod NotesContract {
use core::traits::Into;
use core::debug::PrintTrait;
use core::byte_array::ByteArray;
use cairo_wave::note::{Note, NoteType, Music, MusicToWavFile};
use cairo_wave::wave::WavFile;
use cairo_wave::drum_kit::{DrumSound, get_drum_sound};

use super::INotesContract;

Expand Down Expand Up @@ -47,16 +53,102 @@ mod NotesContract {
let wav: WavFile = music.into();
wav.into()
}
fn get_drum_kit(self: @ContractState) -> ByteArray {
let sample_rate = 8000;
let bit_depth = 8;
// Get individual drum sounds
let kick = get_drum_sound(DrumSound::Kick, sample_rate, bit_depth);
let snare = get_drum_sound(DrumSound::Snare, sample_rate, bit_depth);
let hi_hat = get_drum_sound(DrumSound::HiHat, sample_rate, bit_depth);
let bass = get_drum_sound(DrumSound::Bass, sample_rate, bit_depth);
// Create silence
let silence_duration = 2000; // 1 second of silence at 8000 Hz
let mut silence: Array<u32> = ArrayTrait::new();
let mut i = 0;
loop {
if i == silence_duration {
break;
}
silence.append(0);
i += 1;
};
let mut pattern: Array<Array<u32>> = ArrayTrait::new();
// Demo pop rhythm
// Kick
pattern.append(kick.clone());
pattern.append(silence.clone());
// Hi-hat
pattern.append(hi_hat.clone());
pattern.append(silence.clone());
// Snare
pattern.append(snare.clone());
pattern.append(silence.clone());
// Hi-hat
pattern.append(hi_hat.clone());
pattern.append(silence.clone());
// Kick
pattern.append(kick.clone());
pattern.append(silence.clone());
// Hi-hat
pattern.append(hi_hat.clone());
pattern.append(silence.clone());
// Snare
pattern.append(snare.clone());
pattern.append(silence.clone());
// Hi-hat
pattern.append(hi_hat.clone());
pattern.append(silence.clone());
// Kick
pattern.append(kick.clone());
pattern.append(silence.clone());
// Hi-hat
pattern.append(hi_hat.clone());
pattern.append(silence.clone());
// Snare
pattern.append(snare.clone());
pattern.append(silence.clone());
// Hi-hat
pattern.append(hi_hat.clone());
pattern.append(silence.clone());
// Combine all samples into a single array
let mut all_samples: Array<u32> = ArrayTrait::new();
let mut i = 0;
loop {
if i >= pattern.len() {
break;
}
all_samples.append_span(pattern[i].span());
i += 1;
};
let total_samples = all_samples.len();
if total_samples == 0 {
return ByteArray { data: array![], pending_word: 0, pending_word_len: 0 };
}
// Create a WavFile struct
let wav = WavFile {
chunk_size: 36 + (total_samples * 8),
num_channels: 1, // Mono
sample_rate,
bits_per_sample: bit_depth,
subchunk2_size: total_samples * 8,
data: all_samples.span(),
};
// Convert WavFile to ByteArray
wav.into()
}
}
}

#[cfg(test)]
mod tests {
use core::serde::Serde;
use core::debug::PrintTrait;
use core::byte_array::ByteArray;
use super::NotesContract;
use super::{INotesContractDispatcher, INotesContractDispatcherTrait};
use cairo_wave::custom_note::CustomNoteImpl;
use cairo_wave::note::{Note, NoteType};
use cairo_wave::drum_kit::{DrumSound, get_drum_sound};

use starknet::deploy_syscall;

Expand Down Expand Up @@ -108,4 +200,18 @@ mod tests {
assert!(resultant_note[3] == 'F');
println!("custom here {:}", resultant_note);
}
#[test]
fn test_get_drum_kit() {
let contract = deploy();
let res: ByteArray = contract.get_drum_kit();
if res.len() == 0 {
'Empty drum kit result'.print();
return;
}
assert!(res[0] == 'R', "First byte should be 'R'");
assert!(res[1] == 'I', "Second byte should be 'I'");
assert!(res[2] == 'F', "Third byte should be 'F'");
assert!(res[3] == 'F', "Fourth byte should be 'F'");
println!("{:}", res);
}
}
183 changes: 183 additions & 0 deletions src/drum_kit.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
use cairo_wave::note::{Note, NoteType};
use cairo_wave::utils::{generate_square_wave, generate_sawtooth_wave, generate_triangle_wave, generate_sine_wave};
use core::array::SpanTrait;
use core::traits::Into;
use core::debug::PrintTrait;

#[derive(Drop, Copy, Serde)]
enum DrumSound {
Kick,
Snare,
Bass,
HiHat,
}

// Get drum sound
fn get_drum_sound(sound: DrumSound, sample_rate: u32, bit_depth: u16) -> Array<u32> {
let result = match sound {
DrumSound::Kick => generate_kick(sample_rate, bit_depth),
DrumSound::Snare => generate_snare(sample_rate, bit_depth),
DrumSound::Bass => generate_bass(sample_rate, bit_depth),
DrumSound::HiHat => generate_hi_hat(sample_rate, bit_depth),
};

if result.is_empty() {
return array![1];
}
let mut non_zero_samples: Array<u32> = array![];
let mut i: u32 = 0;
loop {
if i >= result.len() {
break;
}
let sample = *result.at(i);
if sample == 0 {
non_zero_samples.append(1);
} else {
non_zero_samples.append(sample);
}
i += 1;
};

non_zero_samples
}
// Kick
fn generate_kick(sample_rate: u32, bit_depth: u16) -> Array<u32> {
let mut kick = generate_square_wave(55, 130, sample_rate, bit_depth);
add_noise(ref kick, 30);
apply_decay(ref kick, 95);
let shortened_length = kick.len() / 2;
let mut shortened_kick = array![];
let mut i: u32 = 0;
loop {
if i >= shortened_length {
break;
}
shortened_kick.append(*kick.at(i));
i += 1;
};

shortened_kick
}
// Snare
fn generate_snare(sample_rate: u32, bit_depth: u16) -> Array<u32> {
let mut snare = generate_sawtooth_wave(220, 80, sample_rate, bit_depth);
let mut snare_high = generate_square_wave(330, 60, sample_rate, bit_depth);
// Mix the two waves
let mut mixed_snare = array![];
let mut i: u32 = 0;
let min_len = if snare.len() < snare_high.len() { snare.len() } else { snare_high.len() };
loop {
if i >= min_len {
break;
}
let sample1 = *snare.at(i);
let sample2 = *snare_high.at(i);
let mixed_sample = (sample1 * 2 + sample2) / 3;
mixed_snare.append(mixed_sample);
i += 1;
};
add_noise(ref mixed_snare, 100);
apply_decay(ref mixed_snare, 40);

mixed_snare
}
// Bass
fn generate_bass(sample_rate: u32, bit_depth: u16) -> Array<u32> {
let mut bass = generate_sine_wave(90, 100, sample_rate, bit_depth);
apply_decay(ref bass, 40);
bass
}
// Hi-hat
fn generate_hi_hat(sample_rate: u32, bit_depth: u16) -> Array<u32> {
let mut hi_hat1 = generate_triangle_wave(200, 16, sample_rate, bit_depth);
let mut hi_hat2 = generate_square_wave(220, 13, sample_rate, bit_depth);
let mut hi_hat3 = generate_sawtooth_wave(230, 15, sample_rate, bit_depth);
add_noise(ref hi_hat1, 80);
add_noise(ref hi_hat2, 90);
add_noise(ref hi_hat3, 100);

// Mix the components
let mut mixed_hi_hat = array![];
let mut i: u32 = 0;
let min_len = min(min(hi_hat1.len(), hi_hat2.len()), hi_hat3.len());
loop {
if i >= min_len {
break;
}
let sample1 = *hi_hat1.at(i);
let sample2 = *hi_hat2.at(i);
let sample3 = *hi_hat3.at(i);
let mixed_sample = (sample1 / 5 + sample2 / 3 + sample3 / 2);
mixed_hi_hat.append(mixed_sample);
i += 1;
};
apply_decay(ref mixed_hi_hat, 99);
add_noise(ref mixed_hi_hat, 150);


mixed_hi_hat
}

fn apply_decay(ref samples: Array<u32>, decay_factor: u32) {
let mut new_samples: Array<u32> = array![];
let len = samples.len();
if len == 0 {
samples.append(1);
return;
}
let mut i: u32 = 0;
loop {
if i >= len {
break;
}
let sample = *samples.at(i);
let decay = if len > 1 {
(decay_factor * i) / (len - 1)
} else {
0
};

let decayed_sample = if sample > decay {
sample - decay
} else {
1
};
new_samples.append(decayed_sample);
i += 1;
};
samples = new_samples;
}

fn add_noise(ref samples: Array<u32>, noise_factor: u32) {
let mut new_samples: Array<u32> = array![];
let len = samples.len();
if len == 0 {
return;
}
let mut i: u32 = 0;
loop {
if i >= len {
break;
}
let sample = *samples.at(i);
let noise = (random() * noise_factor.into() * sample.into()) / 50;
let noisy_sample: u32 = (sample.into() + noise).try_into().unwrap();
new_samples.append(noisy_sample);
i += 1;
};
samples = new_samples;
}

fn random() -> u64 {
42
}


fn min(a: u32, b: u32) -> u32 {
if a < b {
a
} else {
b
}
}
1 change: 1 addition & 0 deletions src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ mod wave;
mod note;
mod utils;
mod custom_note;
mod drum_kit;

0 comments on commit ebdd8a7

Please sign in to comment.