diff --git a/README.md b/README.md
index d8c41f0..3ea7b1e 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,7 @@ A bar is a collection of notes. You can set 3 options that will be kept througho
* beat - (b) The time for each note in the bar
* beats per minute - (bpm) The number of beats per minute. This determines how fast each beat is in actual time
-* ADSR - (adsr) How the note sounds.
+* envelope - (envelope) How the note sounds.
You can group bars together in order to play them at the same time. For example.
@@ -71,7 +71,7 @@ There are 3 options you can use in a note.
* beat - (b) This is the time for the note. The default is 1 if you don't specify, unless it's set at the bar level.
* volume - (v) This is how loud the note should be. The default is 10.
-* ADSR - (a) This indicates how the note should sound. The default ADSR is a cosine wave (check the code please).
+* envelope - (a) This indicates how the note should sound. The default envelope is a cosine wave (check the code please).
Use this keyboard as a reference when writing music.
@@ -111,6 +111,26 @@ You can also play chords. The convention for a chord is to start with an undersc
Which represents the C major, D7 and F minor chords accordingly. Chords, just like multiple notes, can have the same options as notes have.
+## Envelopes
+
+Envelopes defines the 'shape' of the sound. The sound wave when first generate is uniform. This means it will sound the same throughout the duration of the note. Applying an envelope creates the variation on how the note sounds.
+
+Envelopes can be specified per note using the 'e' option, or per bar, also with the 'e' option or the entire song, with the 'envelope' option.
+
+The default envelope, if no envelope is specified, is a cosine wave. This means, the note will sound the loudest initially, then taper off towards the end of the note.
+
+A smoother envelope named 'sine' is also provided, which gives a smoother tone of the note as the note gradually becomes louder and is the loudest in the middle of the note, only to taper off at the end.
+
+To add more envelopes, you can extend the Envelope module and add in your own implementation. Please refer to the code.
+
+## Harmonics
+
+Sound is created by waves. The default sound for each note in Muse is a pure sine wave, called the fundamental harmonic. This has a very 'electronic' sound. To change how the note sounds like, you can change the harmonic being used. Each harmonic is created by adding additional 'harmonics' on top of the fundamental harmonic, changing the shape of the wave.
+
+Harmonics can be specified per note using the 'h' option, or per bar also with the 'he' option or the entire song, with the 'harmonic' option.
+
+The default harmonic, if none is specified, as mentioned, is a pure sine wave. You can add in your own harmonics to create different sound effects. Please refer to the code to see how this is done.
+
## Examples
For examples look into the songs folder. This is an example of the first 9 bars of Alla Turca by Mozart.
diff --git a/lib/muse.rb b/lib/muse.rb
index 693ac45..2006d76 100644
--- a/lib/muse.rb
+++ b/lib/muse.rb
@@ -15,17 +15,19 @@
# along with this program. If not, see .
require "parallel"
-require "muse/wav"
-require "muse/config"
+require "#{File.dirname(__FILE__)}/muse/wav"
+require "#{File.dirname(__FILE__)}/muse/config"
module Muse
class Song
- attr :name, :bars
- def self.record(name, &block)
+ def self.record(name, options ={}, &block)
start_time = Time.now
puts "Start recording song named #{name}.wav"
@name = name
+ @bpm = options[:bpm] || 120
+ @envelope = options[:envelope] || 'default'
+ @harmonic = options[:harmonic] || 'default'
@bars = {}
puts "Processing ..."
instance_eval &block
@@ -36,7 +38,7 @@ def self.record(name, &block)
end
class Bar
- attr :bpm, :beats, :adsr
+ attr :bpm, :beats, :envelope, :harmonic
attr_accessor :stream
NOTES = %w(_ a ais b c cis d dis e f fis g gis)
@@ -56,7 +58,8 @@ class Bar
def initialize(id, options={})
@bpm = options[:bpm] || 120
@beats = (options[:b] || 1).to_f
- @adsr = options[:adsr] || 'default'
+ @envelope = options[:envelope] || 'default'
+ @harmonic = options[:harmonic] || 'default'
@stream = []
end
@@ -86,21 +89,24 @@ def note_data(note, octave=3, options={})
stream = []
if options
beats = options[:b].nil? ? (@beats || 1) : options[:b].to_f
- volume = (options[:v].nil? ? 10 : options[:v].to_i) * 1000
- adsr = options[:a].nil? ? @adsr : 'default'
+ volume = (options[:v].nil? ? 5 : options[:v].to_i) * 1000
+ envelope = options[:a].nil? ? @envelope : 'default'
+ harmonic = options[:h].nil? ? @harmonic : 'default'
else
- beats, volume, adsr = (@beats || 1), 10000, 'default'
+ beats, volume, envelope, harmonic = (@beats || 1), 5000, @envelope || 'default', @harmonic || 'default'
end
- puts "[#{note}] -> beats : #{beats}, :octave : #{octave}"
- duration = ((60 * Wav::SAMPLE_RATE * beats)/@bpm)/Wav::SAMPLE_RATE.to_f
+ puts "[#{note}] -> beats : #{beats}, octave : #{octave} bpm: #{bpm} envelope: #{envelope} harmonic : #{harmonic}"
+ duration = ((60 * WavHeader::SAMPLE_RATE * beats)/@bpm)/WavHeader::SAMPLE_RATE.to_f
note_frequency = note + octave.to_s
unless note == '_'
freq = frequency_of(FREQUENCIES[note_frequency.to_sym])
else
freq = 0
end
- (0.0..duration.to_f).step(1.0/Wav::SAMPLE_RATE) do |i|
- x = (Config.send(adsr.to_sym,i) * volume * Math.sin(2 * Math::PI * freq * i)).to_i
+ (0.0..duration.to_f).step(1.0/WavHeader::SAMPLE_RATE) do |i|
+ env = Envelope.send(envelope.to_sym,i, duration)
+ har = Harmonic.send(harmonic.to_sym, freq * i)
+ x = (env * volume * har).to_i
stream << [x,x]
end
return stream
@@ -137,6 +143,9 @@ def bar(id, options={})
unless @bars[id]
@bars[id] = []
end
+ options[:bpm] = @bpm || options[:bpm] || 120
+ options[:envelope] = @envelope || options[:envelope] || 'default'
+ options[:harmonic] = @harmonic || options[:harmonic] || 'default'
@bars[id] << Bar.new(id, options)
@bars[id].last
end
@@ -153,7 +162,7 @@ def right_size(bars)
def save
puts "Creating temporary files in parallel ..."
- results = Parallel.each_with_index(@bars.values, :in_processes => 4) do |item, id|
+ results = Parallel.each_with_index(@bars.values, :in_processes => Parallel.processor_count) do |item, id|
puts "Writing file - #{id}"
stream = []
container = []
@@ -167,7 +176,7 @@ def save
temp.stream[i].left = s[0]
temp.stream[i].right = s[1]
end
- File.open("#{@name}-#{id}.tmp", "w") {|file| temp.write(file) }
+ File.open("#{@name}-#{id.to_s.rjust(3,'0')}.tmp", "w") {|file| temp.write(file) }
puts "Completed file - #{id}"
end
@@ -177,7 +186,7 @@ def save
puts "Combining temporary files ..."
WavHeader.new("#{@name}.wav", stream_size)
- tmpfiles = Dir.glob("#{@name}-*.tmp")
+ tmpfiles = Dir.glob("#{@name}-*.tmp").sort
File.open("#{@name}.wav", "ab+") do |wav|
tmpfiles.each do |file|
File.open(file, "rb") { |tmp| File.copy_stream(tmp, wav) }
diff --git a/lib/muse/config/adsr_rb.html b/lib/muse/config/adsr_rb.html
deleted file mode 100644
index 0d69ace..0000000
--- a/lib/muse/config/adsr_rb.html
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-