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 @@ - - - - - - - - File: adsr.rb [muse-0.0.4 Documentation] - - - - - - - - - - -
-
-
Last Modified
-
2012-03-11 00:32:31 +0800
- - -
Requires
-
-
    - -
-
- - - -
-
- -
- -
-

Description

- -
- -
- - - diff --git a/lib/muse/config/adsr.rb b/lib/muse/config/envelope.rb similarity index 77% rename from lib/muse/config/adsr.rb rename to lib/muse/config/envelope.rb index 0ab736f..aa95ad9 100644 --- a/lib/muse/config/adsr.rb +++ b/lib/muse/config/envelope.rb @@ -15,11 +15,16 @@ # along with this program. If not, see . 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 diff --git a/lib/muse/config/harmonic.rb b/lib/muse/config/harmonic.rb new file mode 100644 index 0000000..37b9b02 --- /dev/null +++ b/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 . + +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 diff --git a/lib/muse/version.rb b/lib/muse/version.rb index ecd9121..1349f15 100644 --- a/lib/muse/version.rb +++ b/lib/muse/version.rb @@ -15,5 +15,5 @@ # along with this program. If not, see . module Muse - VERSION = "0.0.5" + VERSION = "0.0.7" end diff --git a/songs/beauty.rb b/songs/beauty.rb new file mode 100644 index 0000000..01e6698 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/songs/beauty.wav b/songs/beauty.wav new file mode 100644 index 0000000..d1c304e Binary files /dev/null and b/songs/beauty.wav differ diff --git a/songs/test.rb b/songs/test.rb index 8acc708..da2f262 100644 --- a/songs/test.rb +++ b/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 diff --git a/songs/test_file.wav b/songs/test_file.wav new file mode 100644 index 0000000..4d0216d Binary files /dev/null and b/songs/test_file.wav differ diff --git a/songs/turkish_march.rb b/songs/turkish_march.rb index 471dd71..89aee60 100644 --- a/songs/turkish_march.rb +++ b/songs/turkish_march.rb @@ -5,30 +5,82 @@ # more popularly known as the 'Turkish March' or 'Turkish Rondo' # first 9 bars only -Song.record 'turkish_march' do +Song.record 'turkish_march', envelope: 'default', harmonic: 'organ' do bar(1,b:0.25).notes {b4; a4; gis4; a4;} - + bar(2,b:0.25).notes {c5 b:0.5; _ b:0.5; d5; c5; b4; c5;} bar(2,b:0.5).notes {a3; c4_e4; c4_e4; c4_e4;} - + bar(3,b:0.25).notes {e5 b:0.5; _ b:0.5; f5; e5; dis5; e5;} bar(3,b:0.5).notes {a3; c4_e4; c4_e4; c4_e4;} - + bar(4,b:0.25).notes {b5; a5; gis5; a5; b5; a5; gis5; a5;} bar(4,b:0.5).notes {a3; c4_e4; a3; c4_e4;} - + bar(5,b:0.5).notes {c6 b:1; a5; c6;} bar(5,b:0.5).notes {a3; c4_e4; c4_e4; c4_e4;} - + bar(6,b:0.5).notes {b5; fis5_a5; e5_g5; fis5_a5; } - bar(6,b:0.5).notes {e3; b4_e4; b4_e4; b4_e4;} - + bar(6,b:0.5).notes {e3 v:13; b3_e4; b3_e4; b3_e4;} + bar(7,b:0.5).notes {b5; fis5_a5; e5_g5; fis5_a5; } - bar(7,b:0.5).notes {e3; b4_e4; b4_e4; b4_e4;} - + bar(7,b:0.5).notes {e3 v:13; b3_e4; b3_e4; b3_e4;} + bar(8,b:0.5).notes {b5; fis5_a5; e5_g5; dis5_eis5; } - bar(8,b:0.5).notes {e3; b4_e4; b2; b4;} - - bar(9).notes {e4} + bar(8,b:0.5).notes {e3 v:13; b3_e4; b2; b3;} + + bar(9).notes {e5} bar(9).notes {e4} + + bar(10,b:0.5).notes {c5_e5; d5_f5;} + + bar(11,b:0.5).notes {e5_g5; e5_g5; a5 b:0.25; g5 b:0.25; f5 b:0.25; e5 b:0.25;} + bar(11,b:0.5).notes {c3; c4; e3; e4;} + + bar(12,b:0.5).notes {b4_d5; g4; c5_e5; d5_e5;} + bar(12,b:0.5).notes {g3; _ b:2;} + + bar(13,b:0.5).notes {e5_g5; e5_g5; a5 b:0.25; g5 b:0.25; f5 b:0.25; e5 b:0.25;} + bar(13,b:0.5).notes {c3; c4; e3; e4;} + + bar(14,b:0.5).notes {b4_d5 b:1; a4_c5; b4_d5;} + bar(14,b:0.5).notes {g3; _ b:2;} + + bar(15,b:0.5).notes {c5_e5; c5_e5; f5 b:0.25; e5 b:0.25; d5 b:0.25; c5 b:0.25;} + bar(15,b:0.5).notes {a2; a3; c3; c4;} + + bar(16,b:0.5).notes {gis4_b4; e4; a4_c5; b4_d5;} + bar(16,b:0.5).notes {e3; _ b:2;} + + bar(17,b:0.5).notes {c5_e5; c5_e5; f5 b:0.25; e5 b:0.25; d5 b:0.25; c5 b:0.25;} + bar(17,b:0.5).notes {a2; a3; c3; c4;} + + bar(18,b:0.5).notes {gis4_b4 b:1; b4 b:0.25; a4 b:0.25; gis4 b:0.25; a4 b:0.25;} + bar(18,b:0.5).notes {e3; _ b:2;} + + bar(19,b:0.25).notes {c5 b:0.5; _ b:0.5; d5; c5; b4; c5;} + bar(19,b:0.5).notes {a3; c4_e4; c4_e4; c4_e4;} + + bar(20,b:0.25).notes {e5 b:0.5; _ b:0.5; f5; e5; dis5; e5;} + bar(20,b:0.5).notes {a3; c4_e4; c4_e4; c4_e4;} + + bar(21,b:0.25).notes {b5; a5; gis5; a5; b5; a5; gis5; a5;} + bar(21,b:0.5).notes {a3; c4_e4; a3; c4_e4;} + + bar(22,b:0.5).notes {c6 b:1; a5; b5;} + bar(22,b:0.5).notes {f3; a3_dis4; a3_dis4; a3_dis4;} + + bar(23,b:0.5).notes {c6; b5; a5; gis5;} + bar(23,b:0.5).notes {e3; a3_e4; d3; f3_b3;} + + bar(24,b:0.5).notes {a5; e5; f5; d5;} + bar(24,b:0.5).notes {c3; e3_a3; d3; f3_b3;} + + bar(25,b:0.0833).notes {c5 b:1; b4;c5;b4;c5;b4;c5;b4;c5;b4; a4 b:0.125; b4 b:0.125;} + bar(25,b:0.5).notes {e3_a3; e3_a3; e3_gis3; e3_gis3;} + + bar(26,b:1).notes {a4} + bar(26,b:1).notes {a2_a3} + + end diff --git a/songs/turkish_march.wav b/songs/turkish_march.wav new file mode 100644 index 0000000..26fe403 Binary files /dev/null and b/songs/turkish_march.wav differ