Skip to content

Commit

Permalink
Adds the basics
Browse files Browse the repository at this point in the history
  • Loading branch information
beneggett committed Mar 9, 2015
1 parent ffb4c68 commit a25c405
Show file tree
Hide file tree
Showing 12 changed files with 391 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@
mkmf.log
.ruby-version
.ruby-gemset

*.wav
presentation*
samples
15 changes: 14 additions & 1 deletion lib/music_theory.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
require 'wavefile'
require 'active_support/all'

require "music_theory/version"
require "music_theory/note"
require "music_theory/octave"
require "music_theory/scale"
require "music_theory/modes"
require "music_theory/third"
require "music_theory/chord"
require "music_theory/harmonize"
require "music_theory/output"
require "music_theory/play"


module MusicTheory
# Your code goes here...

end
37 changes: 37 additions & 0 deletions lib/music_theory/chord.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require 'music_theory/output'

module MusicTheory
class Chord
include MusicTheory::Output

attr_accessor :third, :duration, :all_notes, :output_file_name

def initialize(third, options = {})
@duration = options[:duration] || 2.0
@third = third
@output_file_name = options[:output_file_name] || 'chord' # File name to write (without extension)
end

def flatten_third
third.all_notes.each {|note| note.duration = duration}
new_samples = []
sample_count = third.all_notes.first.samples.count
third.samples.in_groups_of(sample_count).each do |group|
group.each_with_index do |value, i|
new_samples[i] ||= 0
new_samples[i] += value
end
end

max = new_samples.map {|s| s.abs }.max
multiplier = 1.0 / max
new_samples.map!{ |s| multiplier * s }
end


def samples
flatten_third
end

end
end
26 changes: 26 additions & 0 deletions lib/music_theory/harmonize.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require 'music_theory/output'

module MusicTheory
class Harmonize
include MusicTheory::Output

attr_accessor :samples

def initialize(*things_to_flatten)
@samples = []
[*things_to_flatten].each do |group|
group.each_with_index do |value, i|
@samples[i] ||= 0
@samples[i] += value
end
end
max = @samples.map {|s| s.abs }.max
multiplier = 1.0 / max
@samples.map!{ |s| multiplier * s }
end




end
end
33 changes: 33 additions & 0 deletions lib/music_theory/modes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require 'music_theory/output'

module MusicTheory
class Modes
include MusicTheory::Output
S = 1
T = 2
I = [T, T, S, T, T, T, S]
II = I.rotate
III = I.rotate(2)
IV = I.rotate(3)
V = I.rotate(4)
VI = I.rotate(5)
VII = I.rotate(6)
CHROMATIC = [S,S,S,S,S,S,S,S,S,S,S,S]

# Map the music theory names as class methods

def self.ionian; I; end
self.singleton_class.send(:alias_method, :major, :ionian)
def self.dorian; II; end
def self.phrygian; III; end
def self.lydian; IV; end
def self.mixolydian; V; end
def self.aeolian; VI; end
self.singleton_class.send(:alias_method, :minor, :aeolian)
def self.locrian; VII; end
def self.chromatic; CHROMATIC; end


end

end
59 changes: 59 additions & 0 deletions lib/music_theory/note.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
require 'music_theory/output'

module MusicTheory
class Note
include MusicTheory::Output
attr_accessor :frequency, :duration, :output_file_name, :distort

def initialize(options = {})
@frequency = options[:frequency].to_f || 220.0 # Note frequency in Hz
@duration = options[:duration] || 1.0 # Number of seconds per note
@distort = options[:distort] || false
@output_file_name = options[:output_file_name] || 'note' # File name to write (without extension)
end

def total_frames
# We want 1 second of the note, so we need 1 second's worth of frames
(duration * sample_rate).to_i
end

def cycles_per_frame
# each frame, we want this fraction of a cycle:
frequency / sample_rate
end

def sine_wave_cycle
# A cycle is a full sine wave, which is 2π radians:
2 * Math::PI * cycles_per_frame
end

def samples
# So to create a note that's one second long, we need to write out all the samples in the sine waves
phase = 0
samples = total_frames.times.map do
sample = (Math.sin phase).to_f
phase += sine_wave_cycle
sample
end
samples = distort!(samples) if distort
samples
end

def distort!(samples)
samples.map do |sample|
negative = (sample) < 0
sample *= 8.to_f
if sample.abs > 5
sample = 5
sample *= -1 if negative
end
sample /= 8.to_f
end
end

def scale

end
end

end
35 changes: 35 additions & 0 deletions lib/music_theory/octave.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require 'music_theory/output'

module MusicTheory
class Octave
include MusicTheory::Output
attr_accessor :starting_note, :number_of_octaves, :direction, :output_file_name, :all_notes

def initialize(options = {})
@starting_note = options[:starting_note] || MusicTheory::Note.new # Note to start on
@number_of_octaves= options[:number_of_octaves] || 2 # Number of octaves to repeat
@direction = options[:direction] || 'asc' # Number of seconds per note
@output_file_name = options[:output_file_name] || 'octave' # File name to write (without extension)
@all_notes = []
all_notes << starting_note
number_of_octaves.to_i.times do
new_note = all_notes.last.clone
if direction == 'asc'
new_note.frequency = all_notes.last.frequency * 2
elsif direction == 'desc'
new_note.frequency = all_notes.last.frequency / 2
end
all_notes << new_note unless new_note.frequency > 20000
end
end

def samples
all_notes.map(&:samples).flatten
end

def sample_rate
starting_note.sample_rate
end
end

end
30 changes: 30 additions & 0 deletions lib/music_theory/output.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module MusicTheory
module Output

def sample_rate
22050
end

def format
WaveFile::Format.new :mono, :pcm_16, sample_rate
end

def buffer_format
WaveFile::Format.new :mono, :float, sample_rate
end

def output_track
WaveFile::Writer.new "#{output_file_name || 'music'}.wav", format do |writer|
buffer = WaveFile::Buffer.new samples, buffer_format
writer.write buffer
end
end

def play
output_track # unless File.file?("#{output_file_name}.wav")
`afplay #{output_file_name}.wav`
nil
end

end
end
20 changes: 20 additions & 0 deletions lib/music_theory/play.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require 'music_theory/output'

module MusicTheory
class Play
include MusicTheory::Output

attr_accessor :samples, :output_file_name, :playable_music
def initialize(playable_music = [], options = {} )
@playable_music = playable_music
@output_file_name = options[:output_file_name] || 'music'
extract_samples
play
end

def extract_samples
@samples = playable_music.map { |music| music.samples }.flatten
end
end

end
70 changes: 70 additions & 0 deletions lib/music_theory/scale.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
require 'music_theory/output'

module MusicTheory
class Scale
include MusicTheory::Output
attr_accessor :starting_note, :number_of_octaves, :direction, :output_file_name, :all_notes, :scale_type, :scale_notes, :distort, :duration, :frequency, :sample_rate

def initialize(scale_type = :major, options = {})
@scale_type = scale_type
@distort = options[:distort] || false
@duration = options[:duration] || 0.5
@frequency = options[:frequency] || 220.0
@starting_note = create_new_note # Note to start on

@number_of_octaves= options[:number_of_octaves] || 2 # Number of octaves to repeat
@direction = options[:direction] || 'asc' # Number of seconds per note
@output_file_name = options[:output_file_name] || 'scale' # File name to write (without extension)
set_all_notes
set_scale_notes
end

def third
third ||= MusicTheory::Third.new self
end

def chord
third.chord
end

def twelth_root_of_two
(2 ** (1.0/12))
end

def mode
MusicTheory::Modes.send(scale_type)
end

def set_scale_notes
@scale_notes = [all_notes.first]
note_index = 0
mode.each do |interval|
note_index += interval
scale_notes << all_notes[note_index]
end
end

def create_new_note
MusicTheory::Note.new( frequency: frequency, duration: duration, distort: distort)
end

def set_all_notes
@all_notes = [@starting_note]
12.times do
new_note = create_new_note
new_note.frequency = (all_notes.last.frequency * twelth_root_of_two)
all_notes << new_note
end
end

def samples
scale_notes.map(&:samples).flatten
end

def sample_rate
starting_note.sample_rate
end

end

end
37 changes: 37 additions & 0 deletions lib/music_theory/third.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require 'music_theory/output'

module MusicTheory
class Third
include MusicTheory::Output

attr_accessor :scale, :all_notes

def initialize(scale)
@scale = scale
@all_notes = [@scale.scale_notes.first]
current = 0
double_scale_notes = @scale.scale_notes * 2
@scale.mode.in_groups_of(2, false) do |group|
current += group.sum
all_notes << double_scale_notes[current]
end
all_notes.uniq! {|note| note.frequency}
all_notes.sort_by! {|note| note.frequency}
end


def output_file_name
scale.output_file_name || 'thirds'
end

def samples
all_notes.map(&:samples).flatten
# chord_samples
end

def chord
chord ||= MusicTheory::Chord.new self
end

end
end
Loading

0 comments on commit a25c405

Please sign in to comment.