├── Examples ├── tremolo-8bit.wav ├── triads-8bit.wav ├── vibrato-8bit.wav ├── DemoSong-16bit.wav ├── DemoSong-8bit.wav ├── NoiseBeep-8bit.wav ├── SineBeep-8bit.wav ├── SawtoothBeep-8bit.wav ├── SquareBeep-8bit.wav ├── SweetDreams-16bit.wav ├── SweetDreams-8bit.wav ├── majorScale-8bit.wav ├── minorScale-8bit.wav └── SweetChildOMine-8bit.wav ├── Rest.rb ├── Includes.rb ├── Song.rb ├── RubySynth.rb ├── Note.rb ├── Track.rb ├── MusicTheory.rb ├── PerfTest.rb ├── Oscillator.rb ├── SweetChildOMine.rb ├── Instrument.rb ├── ExampleSounds.rb ├── WaveFile.rb ├── SweetDreams.rb └── DemoSong.rb /Examples/tremolo-8bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/tremolo-8bit.wav -------------------------------------------------------------------------------- /Examples/triads-8bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/triads-8bit.wav -------------------------------------------------------------------------------- /Examples/vibrato-8bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/vibrato-8bit.wav -------------------------------------------------------------------------------- /Examples/DemoSong-16bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/DemoSong-16bit.wav -------------------------------------------------------------------------------- /Examples/DemoSong-8bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/DemoSong-8bit.wav -------------------------------------------------------------------------------- /Examples/NoiseBeep-8bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/NoiseBeep-8bit.wav -------------------------------------------------------------------------------- /Examples/SineBeep-8bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/SineBeep-8bit.wav -------------------------------------------------------------------------------- /Examples/SawtoothBeep-8bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/SawtoothBeep-8bit.wav -------------------------------------------------------------------------------- /Examples/SquareBeep-8bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/SquareBeep-8bit.wav -------------------------------------------------------------------------------- /Examples/SweetDreams-16bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/SweetDreams-16bit.wav -------------------------------------------------------------------------------- /Examples/SweetDreams-8bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/SweetDreams-8bit.wav -------------------------------------------------------------------------------- /Examples/majorScale-8bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/majorScale-8bit.wav -------------------------------------------------------------------------------- /Examples/minorScale-8bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/minorScale-8bit.wav -------------------------------------------------------------------------------- /Examples/SweetChildOMine-8bit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jstrait/rubysynth/HEAD/Examples/SweetChildOMine-8bit.wav -------------------------------------------------------------------------------- /Rest.rb: -------------------------------------------------------------------------------- 1 | class Rest 2 | def initialize(duration) 3 | @duration = duration 4 | end 5 | 6 | attr_reader :duration 7 | end -------------------------------------------------------------------------------- /Includes.rb: -------------------------------------------------------------------------------- 1 | require 'MusicTheory' 2 | require 'Oscillator' 3 | require 'Instrument' 4 | require 'Song' 5 | require 'Track' 6 | require 'Note' 7 | require 'Rest' 8 | require 'WaveFile' 9 | 10 | require 'ExampleSounds' 11 | require 'DemoSong' 12 | require 'SweetChildOMine' 13 | require 'SweetDreams' -------------------------------------------------------------------------------- /Song.rb: -------------------------------------------------------------------------------- 1 | class Song 2 | def initialize 3 | @tracks = [] 4 | end 5 | 6 | def nextSample 7 | sample = @tracks.inject(0.0) {|sum, track| sum += track.nextSample() } 8 | sample = sample / @tracks.length 9 | end 10 | 11 | def nextSamples(numSamples) 12 | samples = Array.new(numSamples) 13 | 14 | (0..numSamples).each do |i| 15 | samples[i - 1] = nextSample() 16 | end 17 | 18 | samples 19 | end 20 | 21 | def sampleLength() 22 | @tracks.inject(0) {|longest, track| (longest > track.sampleLength) ? longest : track.sampleLength} 23 | end 24 | 25 | attr_accessor :tracks 26 | end -------------------------------------------------------------------------------- /RubySynth.rb: -------------------------------------------------------------------------------- 1 | require 'Includes' 2 | 3 | startTime = Time.now 4 | data = sineBeep() 5 | #data = DemoSong() 6 | #data = SweetChildOMine() 7 | #data = SweetDreams() 8 | stopTime = Time.now 9 | 10 | puts "Total samples: " + data.length.to_s 11 | puts "Max sample: " + data.max.to_s 12 | puts "Min sample: " + data.min.to_s 13 | puts "Time to generate sample data: " + (stopTime - startTime).to_s + " seconds." 14 | 15 | startTime = Time.now 16 | wave = WaveFile.new(1, 44100, 8) 17 | wave.sampleData = data 18 | wave.save(ARGV[0]) 19 | stopTime = Time.now 20 | puts "Time to save wave file: " + (stopTime - startTime).to_s + " seconds." -------------------------------------------------------------------------------- /Note.rb: -------------------------------------------------------------------------------- 1 | require 'MusicTheory.rb' 2 | 3 | class Note 4 | MIDDLEA_FREQUENCY = 440.0 5 | MIDDLE_OCTAVE = 4.0 6 | 7 | def initialize(noteName, octave, duration) 8 | @noteName = noteName 9 | @octave = octave 10 | @duration = duration 11 | 12 | if @noteName != "" 13 | octaveMultiplier = 2.0 ** (octave - MIDDLE_OCTAVE) 14 | noteRatio = MusicTheory.noteRatio(MusicTheory.enharmonicEquivalent(noteName)) * octaveMultiplier 15 | @frequency = noteRatio * MIDDLEA_FREQUENCY 16 | else 17 | @frequency = 0.0 18 | end 19 | end 20 | 21 | attr_reader :noteName, :octave, :duration, :frequency 22 | end -------------------------------------------------------------------------------- /Track.rb: -------------------------------------------------------------------------------- 1 | class Track 2 | def initialize(instrument) 3 | @instrument = instrument 4 | @notes = [] 5 | @noteIndex = -1 6 | @sampleLength = 0 7 | end 8 | 9 | def nextSample 10 | if @noteIndex == -1 11 | @instrument.note = @notes[0] 12 | @noteIndex = 0 13 | end 14 | 15 | if @instrument.note != nil 16 | sample = @instrument.nextSample 17 | else 18 | if @noteIndex < @notes.length 19 | @noteIndex += 1 20 | 21 | @instrument.note = @notes[@noteIndex] 22 | sample = @instrument.nextSample() 23 | else 24 | sample = 0.0 25 | end 26 | end 27 | 28 | sample 29 | end 30 | 31 | def nextSamples(numSamples) 32 | samples = Array.new(numSamples) 33 | 34 | (0..numSamples).each do |i| 35 | samples[i - 1] = nextSample() 36 | end 37 | 38 | samples 39 | end 40 | 41 | def sampleLength() 42 | @notes.inject(0) {|sum, note| sum + (@instrument.samplesPerBeat * (4.0 / note.duration)) } 43 | end 44 | 45 | attr_accessor :notes, :instrument 46 | end -------------------------------------------------------------------------------- /MusicTheory.rb: -------------------------------------------------------------------------------- 1 | class MusicTheory 2 | NOTE_RATIOS = 3 | { 4 | "A" => 1.0, 5 | "A#" => 16.0 / 15.0, 6 | "B" => 9.0 / 8.0, 7 | "C" => 6.0 / 5.0, 8 | "C#" => 5.0 / 4.0, 9 | "D" => 4.0 / 3.0, 10 | "D#" => 45.0 / 32.0, 11 | "E" => 3.0 / 2.0, 12 | "F" => 8.0 / 5.0, 13 | "F#" => 5.0 / 3.0, 14 | "G" => 9.0 / 5.0, 15 | "G#" => 15.0 / 8.0 16 | } 17 | 18 | ENHARMONIC_EQUIVALENTS = 19 | { 20 | "A" => "A", 21 | "G##" => "A", 22 | "Bbb" => "A", 23 | 24 | "A#" => "A#", 25 | "Bb" => "A#", 26 | "Cbb" => "A#", 27 | 28 | "B" => "B", 29 | "A##" => "B", 30 | "Cb" => "B", 31 | 32 | "C" => "C", 33 | "B#" => "C", 34 | "Dbb" => "C", 35 | 36 | "C#" => "C#", 37 | "B##" => "C#", 38 | "Db" => "C#", 39 | 40 | "D" => "D", 41 | "C##" => "D", 42 | "Ebb" => "D", 43 | 44 | "D#" => "D#", 45 | "Eb" => "D#", 46 | "Fbb" => "D#", 47 | 48 | "E" => "E", 49 | "D##" => "E", 50 | "Fb" => "E", 51 | 52 | "F" => "F", 53 | "E#" => "F", 54 | "Gbb" => "F", 55 | 56 | "F#" => "F#", 57 | "E##" => "F#", 58 | "Gb" => "F#", 59 | 60 | "G" => "G", 61 | "F##" => "G", 62 | "Abb" => "G", 63 | 64 | "G#" => "G#", 65 | "Ab" => "G#" 66 | } 67 | 68 | def MusicTheory.noteRatio(noteName) 69 | NOTE_RATIOS[noteName] 70 | end 71 | 72 | def MusicTheory.enharmonicEquivalent(noteName) 73 | ENHARMONIC_EQUIVALENTS[noteName] 74 | end 75 | end -------------------------------------------------------------------------------- /PerfTest.rb: -------------------------------------------------------------------------------- 1 | require 'Includes' 2 | 3 | SAMPLES_PER_SECOND = 44100 4 | 5 | def timer(&block) 6 | startTime = Time.now 7 | yield 8 | return Time.now - startTime 9 | end 10 | 11 | # Test performance of basic oscillators 12 | sine = SineOscillator.new(SAMPLES_PER_SECOND, 440.0, 0.5) 13 | square = SquareOscillator.new(SAMPLES_PER_SECOND, 440.0, 0.5) 14 | saw = SawtoothOscillator.new(SAMPLES_PER_SECOND, 440.0, 0.5) 15 | noise = NoiseOscillator.new(SAMPLES_PER_SECOND, 440.0, 0.5) 16 | 17 | o = SineOscillator.new(SAMPLES_PER_SECOND, 440.0, 0.5) 18 | time = timer { o.nextSamples(SAMPLES_PER_SECOND * 10) } 19 | puts "Sine Oscillator: " + time.to_s + " seconds" 20 | 21 | o = SquareOscillator.new(SAMPLES_PER_SECOND, 440.0, 0.5) 22 | time = timer { o.nextSamples(SAMPLES_PER_SECOND * 10) } 23 | puts "Square Oscillator: " + time.to_s + " seconds" 24 | 25 | o = SawtoothOscillator.new(SAMPLES_PER_SECOND, 440.0, 0.5) 26 | time = timer { o.nextSamples(SAMPLES_PER_SECOND * 10) } 27 | puts "Sawtooth Oscillator: " + time.to_s + " seconds" 28 | 29 | o = NoiseOscillator.new(SAMPLES_PER_SECOND, 440.0, 0.5) 30 | time = timer { o.nextSamples(SAMPLES_PER_SECOND * 10) } 31 | puts "Noise Oscillator: " + time.to_s + " seconds" 32 | 33 | =begin 34 | tests = [["Sine Oscillator", { sine.nextSamples(SAMPLES_PER_SECOND) }], 35 | ["Square Oscillator", { square.nextSamples(SAMPLES_PER_SECOND) }], 36 | ["Sawtooth Oscillator", { sawtooth.nextSamples(SAMPLES_PER_SECOND) }], 37 | ["Noise Oscillator", { noise.nextSamples(SAMPLES_PER_SECOND) }]] 38 | 39 | 40 | time = timer { o.nextSamples(SAMPLES_PER_SECOND) } 41 | puts time 42 | =end -------------------------------------------------------------------------------- /Oscillator.rb: -------------------------------------------------------------------------------- 1 | class Oscillator 2 | def initialize(sampleRate, frequency, amplitude) 3 | @sampleRate = sampleRate 4 | self.frequency = frequency 5 | @amplitude = amplitude 6 | 7 | @periodOffset = 0.0 8 | end 9 | 10 | def nextSample 11 | sample = waveFunction 12 | 13 | @periodOffset += @periodDelta 14 | if @periodOffset >= 1.0 15 | @periodOffset -= 1.0 16 | end 17 | 18 | return sample 19 | end 20 | 21 | def nextSamples(numSamples) 22 | samples = Array.new(numSamples) 23 | 24 | (0..numSamples).each do |i| 25 | samples[i - 1] = nextSample() 26 | end 27 | 28 | samples 29 | end 30 | 31 | def frequency 32 | @frequency 33 | end 34 | 35 | def frequency=(newFrequency) 36 | @frequency = newFrequency 37 | @periodDelta = @frequency / @sampleRate 38 | end 39 | 40 | attr_accessor :amplitude 41 | attr_reader :sampleRate, :periodOffset, :periodDelta 42 | end 43 | 44 | 45 | class SineOscillator < Oscillator 46 | def waveFunction 47 | @amplitude * Math::sin(@periodOffset * 2.0 * Math::PI) 48 | end 49 | end 50 | 51 | 52 | class SquareOscillator < Oscillator 53 | def waveFunction 54 | if @periodOffset >= 0.5 55 | return @amplitude 56 | else 57 | return -@amplitude 58 | end 59 | end 60 | end 61 | 62 | 63 | class SawtoothOscillator < Oscillator 64 | def waveFunction 65 | (1.0 - (@periodOffset * 2.0)) / (1.0 / @amplitude) 66 | end 67 | end 68 | 69 | 70 | class NoiseOscillator < Oscillator 71 | def waveFunction 72 | if @frequency == 0.0 73 | return 0.0 74 | else 75 | return (1.0 - (rand() * 2.0)) / (1.0 / @amplitude) 76 | end 77 | end 78 | end -------------------------------------------------------------------------------- /SweetChildOMine.rb: -------------------------------------------------------------------------------- 1 | def SweetChildOMine 2 | bpm = 140 3 | lead = Instrument.new(bpm, SawtoothOscillator.new(44100, 220.0, 0.3), [], nil, nil) 4 | 5 | leadTrack = Track.new(lead) 6 | 2.times { 7 | leadTrack.notes << Note.new("A", 3, 8) 8 | leadTrack.notes << Note.new("A", 4, 8) 9 | leadTrack.notes << Note.new("E", 3, 8) 10 | leadTrack.notes << Note.new("D", 3, 8) 11 | leadTrack.notes << Note.new("D", 4, 8) 12 | leadTrack.notes << Note.new("E", 3, 8) 13 | leadTrack.notes << Note.new("C#", 4, 8) 14 | leadTrack.notes << Note.new("E", 3, 8) 15 | } 16 | 2.times { 17 | leadTrack.notes << Note.new("B", 3, 8) 18 | leadTrack.notes << Note.new("A", 4, 8) 19 | leadTrack.notes << Note.new("E", 3, 8) 20 | leadTrack.notes << Note.new("D", 3, 8) 21 | leadTrack.notes << Note.new("D", 4, 8) 22 | leadTrack.notes << Note.new("E", 3, 8) 23 | leadTrack.notes << Note.new("C#", 4, 8) 24 | leadTrack.notes << Note.new("E", 3, 8) 25 | } 26 | 2.times { 27 | leadTrack.notes << Note.new("D", 3, 8) 28 | leadTrack.notes << Note.new("A", 4, 8) 29 | leadTrack.notes << Note.new("E", 3, 8) 30 | leadTrack.notes << Note.new("D", 3, 8) 31 | leadTrack.notes << Note.new("D", 4, 8) 32 | leadTrack.notes << Note.new("E", 3, 8) 33 | leadTrack.notes << Note.new("C#", 4, 8) 34 | leadTrack.notes << Note.new("E", 3, 8) 35 | } 36 | 2.times { 37 | leadTrack.notes << Note.new("A", 3, 8) 38 | leadTrack.notes << Note.new("A", 4, 8) 39 | leadTrack.notes << Note.new("E", 3, 8) 40 | leadTrack.notes << Note.new("D", 3, 8) 41 | leadTrack.notes << Note.new("D", 4, 8) 42 | leadTrack.notes << Note.new("E", 3, 8) 43 | leadTrack.notes << Note.new("C#", 4, 8) 44 | leadTrack.notes << Note.new("E", 3, 8) 45 | } 46 | leadTrack.notes << Note.new("A", 3, 2) 47 | 48 | 49 | s = Song.new() 50 | s.tracks = [leadTrack] 51 | 52 | return s.nextSamples(s.sampleLength) 53 | end -------------------------------------------------------------------------------- /Instrument.rb: -------------------------------------------------------------------------------- 1 | class Instrument 2 | def initialize(beatsPerMinute, oscillator, overtones, vibratoLFO = nil, volumeLFO = nil) 3 | @beatsPerMinute = beatsPerMinute 4 | @oscillator = oscillator 5 | @overtones = overtones 6 | @vibratoLFO = vibratoLFO 7 | @volumeLFO = volumeLFO 8 | 9 | @prevVibratoSample = 0.0 10 | @prevVolumeSample = 0.0 11 | 12 | @samplesPerBeat = (oscillator.sampleRate * 60) / beatsPerMinute; 13 | @note = nil 14 | @noteSampleLength = 0 15 | @sampleIndex = 0 16 | 17 | @overtoneOscillators = Array.new(@overtones.length) 18 | i = 0 19 | while i < @overtones.length do 20 | @overtoneOscillators[i] = @oscillator.clone 21 | @overtoneOscillators[i].frequency = @oscillator.frequency * (i + 2) 22 | @overtoneOscillators[i].amplitude = @overtones[i] 23 | i += 1 24 | end 25 | end 26 | 27 | def updateFrequencies 28 | currentVibratoSample = @vibratoLFO.nextSample() 29 | vibratoDelta = currentVibratoSample - @prevVibratoSample 30 | @prevVibratoSample = currentVibratoSample 31 | 32 | # Adjust frequency of the oscillators by the vibrato delta 33 | @oscillator.frequency += vibratoDelta 34 | (0..@overtones.length - 1).each do |i| 35 | @overtoneOscillators[i].frequency = @oscillator.frequency * (i + 2) 36 | end 37 | end 38 | 39 | def updateAmplitudes 40 | currentVolumeSample = @volumeLFO.nextSample() 41 | volumeDelta = currentVolumeSample - @prevVolumeSample 42 | @prevVolumeSample = currentVolumeSample 43 | 44 | @oscillator.amplitude += volumeDelta 45 | if @oscillator.amplitude > 1.0 46 | @oscillator.amplitude = 1.0 47 | end 48 | 49 | @overtoneOscillators.each do |o| 50 | o.amplitude += volumeDelta 51 | if o.amplitude > 1.0 52 | o.amplitude = 1.0 53 | end 54 | end 55 | end 56 | 57 | def nextSample 58 | sample = 0.0 59 | 60 | if @note != nil && @sampleIndex < @noteSampleLength 61 | 62 | if @vibratoLFO != nil 63 | updateFrequencies() 64 | end 65 | 66 | if @volumeLFO != nil 67 | updateAmplitudes() 68 | end 69 | 70 | sample = @oscillator.nextSample() 71 | 72 | @overtoneOscillators.each do |o| 73 | sample += o.nextSample() 74 | end 75 | sample = sample / (@overtones.length + 1) 76 | 77 | @sampleIndex += 1 78 | 79 | if(@sampleIndex >= @noteSampleLength) 80 | @note = nil 81 | end 82 | end 83 | 84 | sample 85 | end 86 | 87 | def nextSamples(numSamples) 88 | samples = Array.new(numSamples) 89 | 90 | (0..numSamples).each do |i| 91 | samples[i - 1] = nextSample() 92 | end 93 | 94 | samples 95 | end 96 | 97 | def note=(newNote) 98 | if(newNote != nil) 99 | @note = newNote 100 | @oscillator.frequency = newNote.frequency 101 | 102 | (0..@overtoneOscillators.length - 1).each do |i| 103 | @overtoneOscillators[i].frequency = newNote.frequency * (i + 2) 104 | end 105 | 106 | @sampleIndex = 0 107 | @noteSampleLength = @samplesPerBeat * (4.0 / newNote.duration) 108 | end 109 | end 110 | 111 | attr_reader :tempo, :beatsPerMinute, :overtones, :note, :noteSampleLength, :samplesPerBeat 112 | end -------------------------------------------------------------------------------- /ExampleSounds.rb: -------------------------------------------------------------------------------- 1 | def sineBeep 2 | return SineOscillator.new(44100, 440.0, 0.5).nextSamples(44100) 3 | end 4 | 5 | def squareBeep 6 | return SquareOscillator.new(44100, 440.0, 0.5).nextSamples(44100) 7 | end 8 | 9 | def sawtoothBeep 10 | return SawtoothOscillator.new(44100, 440.0, 0.5).nextSamples(44100) 11 | end 12 | 13 | def noiseBeep 14 | return NoiseOscillator.new(44100, 440.0, 0.5).nextSamples(44100) 15 | end 16 | 17 | def majorScale 18 | bpm = 140 19 | saw = Instrument.new(120, SineOscillator.new(44100, 220.0, 0.5), [], nil, nil) 20 | 21 | t = Track.new(saw) 22 | t.notes << Note.new("A", 4, 4) 23 | t.notes << Note.new("B", 4, 4) 24 | t.notes << Note.new("C#", 4, 4) 25 | t.notes << Note.new("D", 4, 4) 26 | t.notes << Note.new("E", 4, 4) 27 | t.notes << Note.new("F#", 4, 4) 28 | t.notes << Note.new("G#", 4, 4) 29 | t.notes << Note.new("A", 5, 4) 30 | 31 | return t.nextSamples(t.sampleLength) 32 | end 33 | 34 | def minorScale 35 | bpm = 140 36 | saw = Instrument.new(120, SineOscillator.new(44100, 220.0, 0.5), [], nil, nil) 37 | 38 | t = Track.new(saw) 39 | t.notes << Note.new("A", 3, 4) 40 | t.notes << Note.new("B", 3, 4) 41 | t.notes << Note.new("C", 3, 4) 42 | t.notes << Note.new("D", 3, 4) 43 | t.notes << Note.new("E", 3, 4) 44 | t.notes << Note.new("F", 3, 4) 45 | t.notes << Note.new("G#", 3, 4) 46 | t.notes << Note.new("A", 4, 4) 47 | 48 | return t.nextSamples(t.sampleLength) 49 | end 50 | 51 | def triads 52 | bottom = Instrument.new(120, SineOscillator.new(44100, 220.0, 0.5), [], nil, nil) 53 | middle = Instrument.new(120, SineOscillator.new(44100, 220.0, 0.5), [], nil, nil) 54 | top = Instrument.new(120, SineOscillator.new(44100, 220.0, 0.5), [], nil, nil) 55 | 56 | bottomTrack = Track.new(bottom) 57 | bottomTrack.notes << Note.new("C", 3, 2) 58 | bottomTrack.notes << Note.new("C", 3, 2) 59 | bottomTrack.notes << Note.new("D", 3, 2) 60 | bottomTrack.notes << Note.new("C", 3, 2) 61 | 62 | middleTrack = Track.new(middle) 63 | middleTrack.notes << Note.new("E", 3, 2) 64 | middleTrack.notes << Note.new("F", 3, 2) 65 | middleTrack.notes << Note.new("G", 3, 2) 66 | middleTrack.notes << Note.new("E", 3, 2) 67 | 68 | topTrack = Track.new(top) 69 | topTrack.notes << Note.new("G", 3, 2) 70 | topTrack.notes << Note.new("A", 4, 2) 71 | topTrack.notes << Note.new("B", 4, 2) 72 | topTrack.notes << Note.new("G", 3, 2) 73 | 74 | s = Song.new() 75 | s.tracks = [bottomTrack, middleTrack, topTrack] 76 | 77 | return s.nextSamples(s.sampleLength) 78 | end 79 | 80 | def vibratoExample 81 | normal = Instrument.new(120, SawtoothOscillator.new(44100, 220.0, 0.3), [], nil, nil) 82 | vibrato = Instrument.new(120, SawtoothOscillator.new(44100, 220.0, 0.3), [], SineOscillator.new(44100, 9.0, 15.0), nil) 83 | 84 | normalTrack = Track.new(normal) 85 | normalTrack.notes << Note.new("A", 3, 1) 86 | 87 | vibratoTrack = Track.new(vibrato) 88 | vibratoTrack.notes << Note.new("", 3, 1) 89 | vibratoTrack.notes << Note.new("A", 3, 1) 90 | 91 | s = Song.new() 92 | s.tracks = [normalTrack, vibratoTrack] 93 | 94 | return s.nextSamples(s.sampleLength) 95 | end 96 | 97 | def tremoloExample 98 | normal = Instrument.new(120, SawtoothOscillator.new(44100, 220.0, 0.3), [], nil, nil) 99 | tremolo = Instrument.new(120, SawtoothOscillator.new(44100, 220.0, 0.3), [], nil, SineOscillator.new(44100, 5.0, 0.3)) 100 | 101 | normalTrack = Track.new(normal) 102 | normalTrack.notes << Note.new("A", 3, 1) 103 | 104 | tremoloTrack = Track.new(tremolo) 105 | tremoloTrack.notes << Note.new("", 3, 1) 106 | tremoloTrack.notes << Note.new("A", 3, 1) 107 | 108 | s = Song.new() 109 | s.tracks = [normalTrack, tremoloTrack] 110 | 111 | return s.nextSamples(s.sampleLength) 112 | end -------------------------------------------------------------------------------- /WaveFile.rb: -------------------------------------------------------------------------------- 1 | =begin 2 | WAV File Specification 3 | FROM http://ccrma.stanford.edu/courses/422/projects/WaveFormat/ 4 | The canonical WAVE format starts with the RIFF header: 5 | 0 4 ChunkID Contains the letters "RIFF" in ASCII form 6 | (0x52494646 big-endian form). 7 | 4 4 ChunkSize 36 + SubChunk2Size, or more precisely: 8 | 4 + (8 + SubChunk1Size) + (8 + SubChunk2Size) 9 | This is the size of the rest of the chunk 10 | following this number. This is the size of the 11 | entire file in bytes minus 8 bytes for the 12 | two fields not included in this count: 13 | ChunkID and ChunkSize. 14 | 8 4 Format Contains the letters "WAVE" 15 | (0x57415645 big-endian form). 16 | 17 | The "WAVE" format consists of two subchunks: "fmt " and "data": 18 | The "fmt " subchunk describes the sound data's format: 19 | 12 4 Subchunk1ID Contains the letters "fmt " 20 | (0x666d7420 big-endian form). 21 | 16 4 Subchunk1Size 16 for PCM. This is the size of the 22 | rest of the Subchunk which follows this number. 23 | 20 2 AudioFormat PCM = 1 (i.e. Linear quantization) 24 | Values other than 1 indicate some 25 | form of compression. 26 | 22 2 NumChannels Mono = 1, Stereo = 2, etc. 27 | 24 4 SampleRate 8000, 44100, etc. 28 | 28 4 ByteRate == SampleRate * NumChannels * BitsPerSample/8 29 | 32 2 BlockAlign == NumChannels * BitsPerSample/8 30 | The number of bytes for one sample including 31 | all channels. I wonder what happens when 32 | this number isn't an integer? 33 | 34 2 BitsPerSample 8 bits = 8, 16 bits = 16, etc. 34 | 35 | The "data" subchunk contains the size of the data and the actual sound: 36 | 36 4 Subchunk2ID Contains the letters "data" 37 | (0x64617461 big-endian form). 38 | 40 4 Subchunk2Size == NumSamples * NumChannels * BitsPerSample/8 39 | This is the number of bytes in the data. 40 | You can also think of this as the size 41 | of the read of the subchunk following this 42 | number. 43 | 44 * Data The actual sound data. 44 | =end 45 | 46 | class WaveFile 47 | CHUNK_ID = "RIFF" 48 | FORMAT = "WAVE" 49 | SUB_CHUNK1_ID = "fmt " 50 | SUB_CHUNK1_SIZE = 16 51 | AUDIO_FORMAT = 1 52 | SUB_CHUNK2_ID = "data" 53 | HEADER_SIZE = 36 54 | 55 | def initialize(numChannels, sampleRate, bitsPerSample) 56 | @numChannels = numChannels 57 | @sampleRate = sampleRate 58 | @bitsPerSample = bitsPerSample 59 | 60 | @byteRate = sampleRate * numChannels * (bitsPerSample / 8) 61 | @blockAlign = numChannels * (bitsPerSample / 8) 62 | @sampleData = [] 63 | @chunkSize = HEADER_SIZE + 0 64 | end 65 | 66 | def save(path) 67 | # All numeric values should be saved in little-endian format 68 | 69 | sampleDataSize = @sampleData.length * @numChannels * (@bitsPerSample / 8) 70 | 71 | fileContents = CHUNK_ID 72 | fileContents += [HEADER_SIZE + sampleDataSize].pack("V") 73 | fileContents += FORMAT 74 | fileContents += SUB_CHUNK1_ID 75 | fileContents += [SUB_CHUNK1_SIZE].pack("V") 76 | fileContents += [AUDIO_FORMAT].pack("v") 77 | fileContents += [@numChannels].pack("v") 78 | fileContents += [@sampleRate].pack("V") 79 | fileContents += [@byteRate].pack("V") 80 | fileContents += [@blockAlign].pack("v") 81 | fileContents += [@bitsPerSample].pack("v") 82 | fileContents += SUB_CHUNK2_ID 83 | fileContents += [sampleDataSize].pack("V") 84 | 85 | if @bitsPerSample == 8 86 | # Samples in 8-bit wave files are stored as a unsigned byte 87 | # Effective values are 0 to 255 88 | @sampleData = @sampleData.map {|sample| ((sample * 127.0).to_i) + 127 } 89 | fileContents += @sampleData.pack("C*") 90 | elsif @bitsPerSample == 16 91 | # Samples in 16-bit wave files are stored as a signed little-endian short 92 | # Effective values are -32768 to 32767 93 | @sampleData = @sampleData.map {|sample| (sample * 32767.0).to_i } 94 | fileContents += @sampleData.pack("v*") 95 | else 96 | # Throw an error 97 | end 98 | 99 | aFile = File.open(path, "w") 100 | aFile.syswrite(fileContents) 101 | aFile.close 102 | @sampleData 103 | end 104 | 105 | attr_reader :numChannels, :sampleRate, :bitsPerSample, :byteRate, :blockAlign 106 | attr_writer :sampleData 107 | end -------------------------------------------------------------------------------- /SweetDreams.rb: -------------------------------------------------------------------------------- 1 | def SweetDreams 2 | bpm = 140 3 | noiseDrum = Instrument.new(bpm, NoiseOscillator.new(44100, 123.0, 0.6), [], nil, nil) 4 | synthBass = Instrument.new(bpm, SawtoothOscillator.new(44100, 123.0, 0.3), [1.0], nil, nil) 5 | voice = Instrument.new(bpm, SineOscillator.new(44100, 220.0, 0.8), [0.1, 0.4], nil, nil) 6 | 7 | drumTrack = Track.new(noiseDrum) 8 | 24.times { 9 | drumTrack.notes << Note.new("", 0, 4) 10 | drumTrack.notes << Note.new("A", 0, 16) 11 | drumTrack.notes << Note.new("", 0, 16) 12 | drumTrack.notes << Note.new("", 0, 8) 13 | } 14 | 15 | bassTrack = Track.new(synthBass) 16 | 6.times { 17 | bassTrack.notes << Note.new("A", 0, 16) 18 | bassTrack.notes << Note.new("", 1, 16) 19 | bassTrack.notes << Note.new("A", 1, 16) 20 | bassTrack.notes << Note.new("", 1, 16) 21 | bassTrack.notes << Note.new("A", 2, 16) 22 | bassTrack.notes << Note.new("", 1, 16) 23 | bassTrack.notes << Note.new("A", 2, 16) 24 | bassTrack.notes << Note.new("", 1, 16) 25 | bassTrack.notes << Note.new("C", 1, 16) 26 | bassTrack.notes << Note.new("", 1, 16) 27 | bassTrack.notes << Note.new("C", 2, 16) 28 | bassTrack.notes << Note.new("", 1, 16) 29 | bassTrack.notes << Note.new("A", 1, 16) 30 | bassTrack.notes << Note.new("", 1, 16) 31 | bassTrack.notes << Note.new("A", 2, 16) 32 | bassTrack.notes << Note.new("", 1, 16) 33 | bassTrack.notes << Note.new("F", 0, 16) 34 | bassTrack.notes << Note.new("", 1, 16) 35 | bassTrack.notes << Note.new("F", 0, 16) 36 | bassTrack.notes << Note.new("", 1, 16) 37 | bassTrack.notes << Note.new("F", 1, 16) 38 | bassTrack.notes << Note.new("", 1, 16) 39 | bassTrack.notes << Note.new("A", 2, 16) 40 | bassTrack.notes << Note.new("", 1, 16) 41 | bassTrack.notes << Note.new("E", 0, 16) 42 | bassTrack.notes << Note.new("", 1, 16) 43 | bassTrack.notes << Note.new("E", 0, 16) 44 | bassTrack.notes << Note.new("", 1, 16) 45 | bassTrack.notes << Note.new("E", 1, 16) 46 | bassTrack.notes << Note.new("", 1, 16) 47 | bassTrack.notes << Note.new("A", 2, 16) 48 | bassTrack.notes << Note.new("", 1, 16) 49 | } 50 | 51 | voiceTrack = Track.new(voice) 52 | 4.times { 53 | voiceTrack.notes << Note.new("", 1, 1) 54 | } 55 | voiceTrack.notes << Note.new("", 1, 4) 56 | voiceTrack.notes << Note.new("C", 4, 8) 57 | voiceTrack.notes << Note.new("C", 4, 16) 58 | voiceTrack.notes << Note.new("", 4, 16) 59 | voiceTrack.notes << Note.new("C", 4, 8) 60 | voiceTrack.notes << Note.new("C", 4, 16) 61 | voiceTrack.notes << Note.new("", 4, 16) 62 | voiceTrack.notes << Note.new("A", 4, 4) 63 | voiceTrack.notes << Note.new("C", 4, 16) 64 | voiceTrack.notes << Note.new("C", 4, 32) 65 | voiceTrack.notes << Note.new("", 4, 32) 66 | voiceTrack.notes << Note.new("C", 4, 8) 67 | voiceTrack.notes << Note.new("C", 4, 16) 68 | voiceTrack.notes << Note.new("", 4, 16) 69 | voiceTrack.notes << Note.new("C", 4, 8) 70 | voiceTrack.notes << Note.new("B", 4, 2) 71 | 72 | voiceTrack.notes << Note.new("C", 4, 16) 73 | voiceTrack.notes << Note.new("C", 4, 32) 74 | voiceTrack.notes << Note.new("", 4, 32) 75 | voiceTrack.notes << Note.new("C", 4, 16) 76 | voiceTrack.notes << Note.new("C", 4, 32) 77 | voiceTrack.notes << Note.new("", 4, 32) 78 | voiceTrack.notes << Note.new("A", 4, 8) 79 | voiceTrack.notes << Note.new("C", 4, 4) 80 | voiceTrack.notes << Note.new("C", 4, 8) 81 | voiceTrack.notes << Note.new("A", 4, 16) 82 | voiceTrack.notes << Note.new("A", 4, 32) 83 | voiceTrack.notes << Note.new("", 4, 32) 84 | voiceTrack.notes << Note.new("A", 4, 16) 85 | voiceTrack.notes << Note.new("A", 4, 32) 86 | voiceTrack.notes << Note.new("", 4, 32) 87 | voiceTrack.notes << Note.new("C", 4, 8) 88 | voiceTrack.notes << Note.new("D", 4, 4) 89 | voiceTrack.notes << Note.new("D", 4, 16) 90 | voiceTrack.notes << Note.new("", 4, 16) 91 | voiceTrack.notes << Note.new("C", 4, 8) 92 | voiceTrack.notes << Note.new("B", 4, 8) 93 | voiceTrack.notes << Note.new("", 4, 8) 94 | voiceTrack.notes << Note.new("A", 4, 8) 95 | 96 | voiceTrack.notes << Note.new("C", 4, 16) 97 | voiceTrack.notes << Note.new("C", 4, 32) 98 | voiceTrack.notes << Note.new("", 4, 32) 99 | voiceTrack.notes << Note.new("C", 4, 16) 100 | voiceTrack.notes << Note.new("C", 4, 32) 101 | voiceTrack.notes << Note.new("", 4, 32) 102 | voiceTrack.notes << Note.new("A", 4, 8) 103 | voiceTrack.notes << Note.new("C", 4, 4) 104 | voiceTrack.notes << Note.new("C", 4, 8) 105 | voiceTrack.notes << Note.new("A", 4, 16) 106 | voiceTrack.notes << Note.new("A", 4, 32) 107 | voiceTrack.notes << Note.new("", 4, 32) 108 | voiceTrack.notes << Note.new("A", 4, 16) 109 | voiceTrack.notes << Note.new("A", 4, 32) 110 | voiceTrack.notes << Note.new("", 4, 32) 111 | voiceTrack.notes << Note.new("C", 4, 16) 112 | voiceTrack.notes << Note.new("C", 4, 32) 113 | voiceTrack.notes << Note.new("", 4, 32) 114 | voiceTrack.notes << Note.new("C", 4, 8) 115 | voiceTrack.notes << Note.new("C", 4, 16) 116 | voiceTrack.notes << Note.new("C", 4, 32) 117 | voiceTrack.notes << Note.new("", 4, 32) 118 | voiceTrack.notes << Note.new("C", 4, 8) 119 | voiceTrack.notes << Note.new("B", 4, 4) 120 | voiceTrack.notes << Note.new("", 4, 4) 121 | 122 | voiceTrack.notes << Note.new("C", 4, 4) 123 | voiceTrack.notes << Note.new("A", 4, 8) 124 | voiceTrack.notes << Note.new("C", 4, 4) 125 | voiceTrack.notes << Note.new("A", 4, 4) 126 | voiceTrack.notes << Note.new("A", 4, 8) 127 | voiceTrack.notes << Note.new("C", 4, 16) 128 | voiceTrack.notes << Note.new("C", 4, 32) 129 | voiceTrack.notes << Note.new("", 4, 32) 130 | voiceTrack.notes << Note.new("C", 4, 8) 131 | voiceTrack.notes << Note.new("D", 4, 8) 132 | voiceTrack.notes << Note.new("C", 4, 4) 133 | voiceTrack.notes << Note.new("B", 4, 4) 134 | 135 | 136 | s = Song.new() 137 | s.tracks = [drumTrack, bassTrack, voiceTrack] 138 | 139 | return s.nextSamples(s.sampleLength) 140 | end -------------------------------------------------------------------------------- /DemoSong.rb: -------------------------------------------------------------------------------- 1 | def DemoSong 2 | bpm = 120 3 | noiseDrum = Instrument.new(bpm, NoiseOscillator.new(44100, 220.0, 1.0), []) 4 | synthBass = Instrument.new(bpm, SquareOscillator.new(44100, 220.0, 0.3), [1.0], nil, nil) 5 | #voice = Instrument.new(bpm, SawtoothOscillator.new(44100, 220.0, 0.3), [], SineOscillator.new(44100, 3.0, 5.0), nil) 6 | #voice = Instrument.new(bpm, SawtoothOscillator.new(44100, 220.0, 0.3), [], nil, SineOscillator.new(44100, 10.0, 0.3)) 7 | voice = Instrument.new(bpm, SawtoothOscillator.new(44100, 220.0, 0.3), [], nil, nil) 8 | 9 | drumTrack = Track.new(noiseDrum) 10 | 2.times { 11 | 7.times { 12 | drumTrack.notes << Note.new("", 0, 4) 13 | drumTrack.notes << Note.new("A", 0, 16) 14 | drumTrack.notes << Note.new("", 0, 16) 15 | drumTrack.notes << Note.new("", 0, 8) 16 | } 17 | drumTrack.notes << Note.new("", 0, 4) 18 | drumTrack.notes << Note.new("A", 0, 16) 19 | drumTrack.notes << Note.new("", 0, 16) 20 | drumTrack.notes << Note.new("A", 0, 32) 21 | drumTrack.notes << Note.new("", 0, 32) 22 | drumTrack.notes << Note.new("A", 0, 16) 23 | } 24 | 4.times { 25 | 3.times { 26 | drumTrack.notes << Note.new("", 0, 4) 27 | drumTrack.notes << Note.new("A", 0, 16) 28 | drumTrack.notes << Note.new("", 0, 16) 29 | drumTrack.notes << Note.new("", 0, 8) 30 | } 31 | drumTrack.notes << Note.new("", 0, 4) 32 | 4.times { 33 | drumTrack.notes << Note.new("A", 0, 32) 34 | drumTrack.notes << Note.new("", 0, 32) 35 | } 36 | } 37 | 38 | 39 | bassTrack = Track.new(synthBass) 40 | 2.times { 41 | 4.times { 42 | bassTrack.notes << Note.new("A", 1, 16) 43 | bassTrack.notes << Note.new("", 1, 16) 44 | } 45 | 4.times { 46 | bassTrack.notes << Note.new("D", 1, 16) 47 | bassTrack.notes << Note.new("", 1, 16) 48 | } 49 | 4.times { 50 | bassTrack.notes << Note.new("C", 1, 16) 51 | bassTrack.notes << Note.new("", 1, 16) 52 | } 53 | 4.times { 54 | bassTrack.notes << Note.new("B", 1, 16) 55 | bassTrack.notes << Note.new("", 1, 16) 56 | } 57 | 4.times { 58 | bassTrack.notes << Note.new("A", 1, 16) 59 | bassTrack.notes << Note.new("", 1, 16) 60 | } 61 | 4.times { 62 | bassTrack.notes << Note.new("D", 1, 16) 63 | bassTrack.notes << Note.new("", 1, 16) 64 | } 65 | 4.times { 66 | bassTrack.notes << Note.new("C", 1, 16) 67 | bassTrack.notes << Note.new("", 1, 16) 68 | } 69 | 3.times { 70 | bassTrack.notes << Note.new("E", 1, 16) 71 | bassTrack.notes << Note.new("", 1, 16) 72 | } 73 | bassTrack.notes << Note.new("Eb", 1, 16) 74 | bassTrack.notes << Note.new("E", 1, 16) 75 | } 76 | 2.times { 77 | 4.times { 78 | bassTrack.notes << Note.new("A", 1, 16) 79 | bassTrack.notes << Note.new("", 1, 16) 80 | } 81 | 4.times { 82 | bassTrack.notes << Note.new("D", 1, 16) 83 | bassTrack.notes << Note.new("", 1, 16) 84 | } 85 | 4.times { 86 | bassTrack.notes << Note.new("C", 1, 16) 87 | bassTrack.notes << Note.new("", 1, 16) 88 | } 89 | 4.times { 90 | bassTrack.notes << Note.new("B", 1, 16) 91 | bassTrack.notes << Note.new("", 1, 16) 92 | } 93 | 4.times { 94 | bassTrack.notes << Note.new("A", 1, 16) 95 | bassTrack.notes << Note.new("", 1, 16) 96 | } 97 | 4.times { 98 | bassTrack.notes << Note.new("D", 1, 16) 99 | bassTrack.notes << Note.new("", 1, 16) 100 | } 101 | 2.times { 102 | bassTrack.notes << Note.new("C", 1, 16) 103 | bassTrack.notes << Note.new("", 1, 16) 104 | } 105 | 2.times { 106 | bassTrack.notes << Note.new("D", 1, 16) 107 | bassTrack.notes << Note.new("", 1, 16) 108 | } 109 | 4.times { 110 | bassTrack.notes << Note.new("A", 1, 16) 111 | bassTrack.notes << Note.new("", 1, 16) 112 | } 113 | } 114 | 115 | 116 | voiceTrack = Track.new(voice) 117 | 2.times { 118 | voiceTrack.notes << Note.new("A", 3, 4) 119 | voiceTrack.notes << Note.new("E", 3, 8) 120 | voiceTrack.notes << Note.new("F#", 3, 4) 121 | voiceTrack.notes << Note.new("E", 3, 8) 122 | voiceTrack.notes << Note.new("D", 3, 8) 123 | voiceTrack.notes << Note.new("E", 3, 4) 124 | voiceTrack.notes << Note.new("D", 3, 8) 125 | voiceTrack.notes << Note.new("C", 3, 8) 126 | voiceTrack.notes << Note.new("E", 3, 2) 127 | voiceTrack.notes << Note.new("", 3, 8) 128 | 129 | voiceTrack.notes << Note.new("A", 3, 4) 130 | voiceTrack.notes << Note.new("E", 3, 8) 131 | voiceTrack.notes << Note.new("F#", 3, 4) 132 | voiceTrack.notes << Note.new("E", 3, 8) 133 | voiceTrack.notes << Note.new("D", 3, 8) 134 | voiceTrack.notes << Note.new("E", 3, 1) 135 | voiceTrack.notes << Note.new("", 3, 8) 136 | } 137 | 2.times { 138 | voiceTrack.notes << Note.new("A", 4, 4) 139 | voiceTrack.notes << Note.new("F#", 3, 8) 140 | voiceTrack.notes << Note.new("A", 4, 8) 141 | voiceTrack.notes << Note.new("B", 4, 8) 142 | voiceTrack.notes << Note.new("C#", 4, 8) 143 | voiceTrack.notes << Note.new("B", 4, 8) 144 | voiceTrack.notes << Note.new("A", 4, 8) 145 | voiceTrack.notes << Note.new("A", 4, 8) 146 | voiceTrack.notes << Note.new("A", 4, 8) 147 | voiceTrack.notes << Note.new("G", 3, 8) 148 | voiceTrack.notes << Note.new("G", 3, 8) 149 | voiceTrack.notes << Note.new("E", 3, 2) 150 | 151 | voiceTrack.notes << Note.new("A", 4, 4) 152 | voiceTrack.notes << Note.new("F#", 3, 8) 153 | voiceTrack.notes << Note.new("A", 4, 8) 154 | voiceTrack.notes << Note.new("B", 4, 8) 155 | voiceTrack.notes << Note.new("C#", 4, 8) 156 | voiceTrack.notes << Note.new("B", 4, 8) 157 | voiceTrack.notes << Note.new("A", 4, 8) 158 | voiceTrack.notes << Note.new("A", 4, 8) 159 | voiceTrack.notes << Note.new("A", 4, 8) 160 | voiceTrack.notes << Note.new("G", 3, 8) 161 | voiceTrack.notes << Note.new("G", 3, 8) 162 | voiceTrack.notes << Note.new("A", 4, 2) 163 | } 164 | 165 | 166 | s = Song.new() 167 | s.tracks = [drumTrack, bassTrack, voiceTrack] 168 | 169 | return s.nextSamples(s.sampleLength) 170 | end --------------------------------------------------------------------------------