Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fixed bugs, added in envelopes and harmonics
  • Loading branch information
sausheong committed Mar 18, 2012
1 parent 8fd2b0a commit 2fc8a5f
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 92 deletions.
24 changes: 22 additions & 2 deletions README.md
Expand Up @@ -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.

Expand All @@ -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.

Expand Down Expand Up @@ -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.
Expand Down
41 changes: 25 additions & 16 deletions lib/muse.rb
Expand Up @@ -15,17 +15,19 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

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
Expand All @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 = []
Expand All @@ -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

Expand All @@ -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) }
Expand Down
52 changes: 0 additions & 52 deletions lib/muse/config/adsr_rb.html

This file was deleted.

11 changes: 8 additions & 3 deletions lib/muse/config/adsr.rb → lib/muse/config/envelope.rb
Expand Up @@ -15,11 +15,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

module Muse
module Config
module Envelope
class << self
def default(input)
Math.cos(2* Math::PI*input).to_f
def default(input, duration)
Math.cos((Math::PI*input)/(2*duration.to_f))
end

def sine(input, duration)
Math.sin((Math::PI*input)/duration.to_f)
end

end
end
end
46 changes: 46 additions & 0 deletions lib/muse/config/harmonic.rb
@@ -0,0 +1,46 @@
# Muse
# Copyright (C) 2012 Chang Sau Sheong
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

module Muse
module Harmonic
class << self
def default(input)
Math.sin(2 * Math::PI * input)
end

def second(input)
Math.sin(2 * Math::PI * input) +
Math.sin(3* 2 * Math::PI * input)
end

def third(input)
Math.sin(2 * Math::PI * input) +
Math.sin(3* 2 * Math::PI * input) +
Math.sin(5 * 2 * Math::PI * input)
end

def organ(input)
Math.sin(2 * 2 * Math::PI * input) +
Math.sin(2 * Math::PI * input) +
Math.sin(Math::PI * input)

end



end
end
end
2 changes: 1 addition & 1 deletion lib/muse/version.rb
Expand Up @@ -15,5 +15,5 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

module Muse
VERSION = "0.0.5"
VERSION = "0.0.7"
end
24 changes: 24 additions & 0 deletions songs/beauty.rb
@@ -0,0 +1,24 @@
require "muse"
include Muse

Song.record 'beauty', bpm: 70, envelope: 'sine', harmonic: 'organ' do
bar(1, b:0.5).notes {a4; c5; e5; f5 b:1; ais4 b:1.5; a5; ais5; g5; a5;}
bar(1, b:0.5).notes {a3 b:2; ais3; d4; f4; ais4; c5 b:1; e4 b:1;}

bar(2, b:0.5).notes {a4_f5 b: 2; a4 b:1; c5 b:0.33; e5 b:0.33; f5 b:0.33; }
bar(2, b:0.5).notes {f3; c3; g3; a3 b:2.5;}

bar(3, b:0.5).notes {ais4_g5 b: 2; a5; ais5; g5; a5;}
bar(3, b:0.5).notes {f2; d3; f3; ais3 b:1.5; e b:1}

bar(4, b:0.5).notes {a4_f5 b:2; f4; g4; a4; ais4; }
bar(4, b:0.5).notes {f3; c3; g3; a3 b:2.5;}

bar(5, b:0.5).notes {e4_c5 b:2; c5 b:0.75; ais4 b:0.25; a4 b:0.75; g4 b: 0.25;}
bar(5, b:0.5).notes {a3; g3; f3; g3 b:2.5;}

bar(6, b:0.5).notes {f4 b:2; ais4 b:1; a4 b:0.33; g4 b:0.33; f4_d4 b:0.83; c4 b: 2.5; c4_e4 b:1}
bar(6, b:0.5).notes {ais2; d3; f3; ais3; d3_ais3 b:2; c3 b:1; ais2; c3; f3 b:1; e3 b:1;}


end
Binary file added songs/beauty.wav
Binary file not shown.
9 changes: 4 additions & 5 deletions songs/test.rb
@@ -1,9 +1,8 @@
require "muse"
include Muse

Song.record 'test_file' do
bar(1,b:0.5).notes { c4; e4;}
bar(2,b:0.5).notes { d4; f4;}
bar(3,b:0.5).notes { e4}

Song.record 'test_file', bpm: 70, envelope: 'default', harmonic: 'organ' do
bar(1,b:0.5).notes { c4}
bar(2,b:0.5).notes { e4 b:1;}
bar(3,b:0.5).notes { g4 b: 1.5}
end
Binary file added songs/test_file.wav
Binary file not shown.

0 comments on commit 2fc8a5f

Please sign in to comment.