Commits

Takayuki KONDO committed 662a54a

initial commit

Comments (0)

Files changed (6)

+# -*- coding: utf-8 -*-
+import struct
+import wave
+import math
+import random
+import itertools
+
+class Config(object):
+    SampleRate = 44100
+    SampleWidth = 2
+    SampleBits = SampleWidth * 8
+    ValueRange = ((2 ** SampleBits / 2 - 1), -(2 ** SampleBits / 2))
+    MaxGain = ValueRange[0]
+    DataTypeSignature = 'h'
+    Channels = 2
+
+
+class Oscillator(object):
+    def __init__(self, frequency=440.0, duty=1.0):
+        self.frequency = frequency
+        self.duty = duty
+
+    def get_value(self, tick):
+        raise NotImplemented()
+
+
+class ConstantValueGenerator(object):
+    def __init__(self, value=0):
+        self.value = value
+
+
+    def get_value(self, tick):
+        return [self.value,] * Config.Channels
+    
+
+
+class NoiseGenerator(Oscillator):
+    def __init__(self, frequency=44100):
+        super(self.__class__, self).__init__(frequency=frequency)
+        self.current_value = random.uniform(-1.0, 1.0)
+        
+    def get_value(self, tick):
+        if (tick % (Config.SampleRate / self.frequency)) == 0:
+            self.current_value = random.uniform(-1.0, 1.0)
+
+        return [self.current_value,] * Config.Channels
+
+
+
+class SineWaveOscillator(Oscillator):
+    def get_value(self, tick):
+        value = math.sin((2.0 * math.pi) * \
+                         (self.frequency / Config.SampleRate) * \
+                         tick + (64 / math.pi))
+        
+        return [value,] * Config.Channels
+
+
+
+class SquareWaveOscillator(Oscillator):
+    def __init__(self, frequency=440.0, duty=0.5):
+        super(self.__class__, self).__init__(frequency=frequency, duty=duty)
+        self.current_value = 1
+        
+    def get_value(self, tick):
+        l = Config.SampleRate / self.frequency
+        change_point = l * self.duty
+
+        if (tick % l) <= change_point:
+            return [1,] * Config.Channels
+        else:
+            return [-1,] * Config.Channels
+
+
+
+class SawWaveOscillator(Oscillator):
+    def get_value(self, tick):
+        l = Config.SampleRate / self.frequency
+        value = -2 * (tick % l) / (l / (1.0 / self.duty)) + 1
+        if value < -1:
+            value = -1
+        return [value,] * Config.Channels
+
+
+
+class LFO(object):
+    def __init__(self, oscillator_class=None, frequency=2, duty=1.0):
+        self.oscillator = oscillator_class(frequency=frequency, duty=duty)
+
+
+    def get_value(self, tick):
+        value = (self.oscillator.get_value(tick)[0] + 1) / 2.0
+        return value
+    
+
+
+class Amplifeir(object):
+    def __init__(self, source=None, gain=0, attenuate=1.0):
+        self.source = source
+        self.gain = gain
+        self.attenuate = attenuate
+
+
+    def get_value(self, tick):
+        gained_values = list()
+        for value in self.source.get_value(tick):
+            value *= self.gain
+            value = min((Config.ValueRange[0], value))
+            value = max((Config.ValueRange[1], value))
+            gained_values.append(value)
+            
+        return [value * self.attenuate for value in gained_values]
+
+
+class Compressor(object):
+    def __init__(self, source=None, threshold=1.0, ratio=1.0):
+        self.source = source
+        self.threshold = threshold
+        self.ratio = ratio
+
+
+    def get_value(self, tick):
+        result = list()
+        for value in self.soruce.get_value(tick):
+            if value >= self.threshold:
+                pass
+                
+        
+class Clock(object):
+    def __init__(self, end=None):
+        self.tick = -1
+        self.end = end
+
+
+    def next(self):
+        self.tick += 1
+        if (self.end is not None) and (self.tick > self.end):
+            return None
+        else:
+            return self.tick
+
+
+class Multiplication(object):
+    def __init__(self, multiplicand, multiplicator):
+        self.multiplicand = multiplicand
+        self.multiplicator = multiplicator
+
+
+    def get_value(self, tick):
+        multiplicand_value = self.multiplicand.get_value(tick)
+        multiplicator_value = self.multiplicator.get_value(tick)
+        
+        return [value * multiplicator_value for value in multiplicand_value]
+
+
+class Subtraction(object):
+    def __init__(self, minuend, subtrahend):
+        self.minuend = minuend
+        self.subtrahend = subtrahend
+
+
+    def get_value(self, tick):
+        return [(x - y) for (x, y) in zip(self.minuend.get_value(tick), self.subtrahend.get_value(tick))]
+
+
+class Inverter(object):
+    def __init__(self, source=None):
+        self.source = source
+
+
+    def get_value(self, tick):
+        return [-value for value in self.source.get_value(tick)]
+
+
+
+class Gate(object):
+    def __init__(self, source=None, state=list()):
+        self.source = source
+        self.state = state
+
+    def get_value(self, tick):
+        return [(value if state is True else 0) for (state, value) in zip(self.state, self.source.get_value(tick))]
+
+
+
+class FrequencyModulator(object):
+    def __init__(self, source=None, delta=None):
+        self.source = source
+        self.delta = delta
+        
+
+    def get_value(self, tick):
+        detuning = self.delta.get_value(tick)[0]
+        self.source.frequency += detuning
+        result = self.source.get_value(tick)
+        self.source.frequency -= detuning
+        return result
+
+
+
+class Mixer(object):
+    def __init__(self):
+        self.tracks = dict()
+
+
+    def add_track(self, track_id, track_name, track_source):
+        self.tracks[track_id] = (track_name, track_source)
+
+
+    def get_value(self, tick):
+        result = [0.0,] * Config.Channels
+        for (track_id, (name, source)) in self.tracks.iteritems():
+            result = [sum(x) for x in zip(result, source.get_value(tick))]
+
+        return [value / len(self.tracks) for value in result]
+
+
+class Renderer(object):
+    def __init__(self, clock=None, source=None, sink=None):
+        self.clock = clock
+        self.source = source
+        self.sink = sink
+
+
+    def do_rendering(self):
+        self.sink.open()
+        
+        while True:
+            tick = self.clock.next()
+            if tick is None:
+                break
+            
+            data = self.source.get_value(tick)
+            if data is None:
+                break
+
+            self.sink.write(data)
+
+            if (tick % 50000) == 0:
+                print("%10d ... " % tick)
+
+        self.sink.close()
+
+
+
+class WaveFileSink(object):
+    def __init__(self, output_file_name="output.wav"):
+        self.output_file_name = output_file_name
+
+
+    def open(self):
+        self.output_wave_file = wave.open(self.output_file_name, "wb")
+        self.output_wave_file.setnchannels(Config.Channels)
+        self.output_wave_file.setsampwidth(Config.SampleWidth)
+        self.output_wave_file.setframerate(Config.SampleRate)
+
+
+    def close(self):
+        self.output_wave_file.close()
+
+
+    def write(self, data):
+        self.output_wave_file.writeframesraw("".join(
+            [struct.pack(Config.DataTypeSignature, x) for x in data]))
+# -*- coding: utf-8 -*-
+from Components import Config
+from Components import SineWaveOscillator
+from Components import Amplifeir
+from Components import Clock
+from Components import Renderer
+from Components import WaveFileSink
+
+
+def main():
+    osc = SineWaveOscillator(frequency=440.0)
+    amp = Amplifeir(source=osc, gain=Config.MaxGain, attenuate=1.0)
+    sink = WaveFileSink(output_file_name="output.wav")
+
+    clock = Clock(end=Config.SampleRate)
+
+    renderer = Renderer(clock=clock, source=amp, sink=sink)
+    renderer.do_rendering()
+
+
+if __name__ == "__main__":
+    main()
+# -*- coding: utf-8 -*-
+from Components import Config
+from Components import SquareWaveOscillator
+from Components import Clock
+from Components import Renderer
+from Components import WaveFileSink
+
+from Sequencer import Sequencer
+from Sequencer import MMLCompiler
+
+MML = "t120o4l4cdefedcrefgagfercrcrcrcrl16crcrdrdrererfrfrl4edcr"
+
+def main():
+    mml_compiler = MMLCompiler()
+    music_sequence = mml_compiler.to_sequence(MML)
+    
+    osc = SquareWaveOscillator()
+    sequencer = Sequencer()
+    sequencer.add_track(0, "tone1", osc, osc)
+    sequencer.add_sequence(0, music_sequence)
+
+    sink = WaveFileSink(output_file_name="output.wav")
+    clock = Clock()
+    renderer = Renderer(clock=clock, source=sequencer, sink=sink)
+    renderer.do_rendering()
+
+
+if __name__ == "__main__":
+    main()
+class Vibrato(object):
+    def __init__(self, source=None, frequency=1.0, depth=1.0):
+        viv_oscillator = SineWaveOscillator(frequency=frequency)
+        amp = Amplifeir(source=viv_oscillator, gain=depth, attenuate=1.0)
+        self.output = FrequencyModulator(source=source, diff=amp)
+
+    def get_value(self, tick):
+        return self.output.get_value(tick)
+    
+    
+
+class DeTunedSquareWaveOscillator(object):
+    def __init__(self, frequency=0, depth=0):
+        self.depth = depth
+        self.osc1 = SquareWaveOscillator(frequency=frequency)
+        self.osc2 = SquareWaveOscillator(frequency=frequency + depth)
+        
+        self.mixer = Mixer()
+        self.mixer.add_track(0, "base_tone", self.osc1)
+        self.mixer.add_track(1, "detuned_tone", self.osc2)
+
+
+    def get_value(self, tick):
+        return self.mixer.get_value(tick)
+
+
+    def get_frequency(self):
+        return self.osc1.frequency
+
+
+    def set_frequency(self, value):
+        self.osc1.frequency = value
+        self.osc2.frequency = value + self.depth
+        
+    frequency = property(get_frequency, set_frequency)
+    
+# -*- coding: utf-8 -*-
+import pprint
+from Components import Config
+from Components import Clock
+from Components import ConstantValueGenerator
+from Components import NoiseGenerator
+from Components import SineWaveOscillator
+from Components import SquareWaveOscillator
+from Components import SawWaveOscillator
+from Components import LFO
+from Components import Multiplication
+from Components import Subtraction
+from Components import Inverter
+from Components import Gate
+from Components import FrequencyModulator
+from Components import Mixer
+from Components import Amplifire
+from Components import Renderer
+from Components import WaveFileSink
+
+from Sequencer import MMLCompiler
+from Sequencer import Sequencer
+
+def percusstion_test():
+    bass_base = Subtraction(SineWaveOscillator(frequency=40.0),
+                            Amplifire(source=SquareWaveOscillator(frequency=80.0, duty=0.125),
+                                      gain=1,
+                                      attenuate=0.075))
+
+    bass_drum = Multiplication(bass_base,
+                               LFO(oscillator_class=SawWaveOscillator, frequency=1.0, duty=0.5))
+
+    snare_drum = Multiplication(NoiseGenerator(frequency=6000.0),
+                                LFO(oscillator_class=SawWaveOscillator, frequency=1.0, duty=0.25))
+    
+    hihat = Multiplication(NoiseGenerator(frequency=44100.0),
+                           LFO(oscillator_class=SawWaveOscillator, frequency=4.0, duty=0.25))
+
+
+    mixer = Mixer()
+    mixer.add_track(0, "bus_drum", bass_drum)
+    mixer.add_track(1, "snare_drum", snare_drum)
+    mixer.add_track(2, "hihat", hihat)
+    
+    amplifire = Amplifire(source=mixer, gain=Config.ValueRange[0], attenuate=1.0)
+    sink = WaveFileSink(output_file_name="output.wav")
+    clock = Clock(end=Config.SampleRate * 3)
+
+    renderer = Renderer(clock=clock, source=amplifire, sink=sink)
+    renderer.do_rendering()
+
+
+def chorus_test():
+    osc1 = SquareWaveOscillator(frequency=440.0)
+    osc2 = Inverter(source=FrequencyModulator(source=osc1,
+                                              delta=Multiplication(
+                                                  ConstantValueGenerator(2.0),
+                                                  LFO(oscillator_class=SineWaveOscillator,
+                                                      frequency=3.0))))
+        
+
+    mixer = Mixer()
+    mixer.add_track(0, "base_tone", Gate(source=osc1, state=(True, False)))
+    mixer.add_track(1, "detuned_tone", Gate(source=osc2, state=(True, True)))
+
+    amplifire = Amplifire(source=mixer, gain=Config.MaxGain, attenuate=0.75)
+    sink = WaveFileSink(output_file_name="output.wav")
+    clock = Clock(end=Config.SampleRate)
+
+    renderer = Renderer(clock=clock, source=amplifire, sink=sink)
+    renderer.do_rendering()
+
+
+class Tone(object):
+    def __setattr__(self, name, value):
+        if name == "frequency":
+            self.osc1.frequency = value
+            self.osc2.frequency = value
+        else:
+            object.__setattr__(self, name, value)
+                
+    
+    def __init__(self):
+        self.osc1 = SquareWaveOscillator()
+        self.osc2 = SquareWaveOscillator()
+        
+        mod_osc2 = Inverter(source=FrequencyModulator(source=self.osc2,
+                                                  delta=ConstantValueGenerator(2.0)))
+
+        self.mixer = Mixer()
+        self.mixer.add_track(0, "base_tone", Gate(source=self.osc1, state=(True, False)))
+        self.mixer.add_track(1, "detuned_tone", Gate(source=mod_osc2, state=(False, True)))
+
+
+    def get_value(self, tick):
+        return self.mixer.get_value(tick)
+        
+        
+
+
+def compiler_test():
+    mmls = ("t115o5l8f2r8cfa>c<ar8fgr8ggr8d4r8gfr8ef2r8cfa>c<ar8fg-r8g-g-r8f1r8",
+            "t115o4l8a2r8aa>cfcr8<ab-r8b-b-r8b-4r8b-b-r8b-a2r8aa>cfcr8<ab-r8b-b-r8a1r8",
+            "t115o2l16v6f.r32f.r32f.r32f.r32r4f.r32f.r32f.r32f.r32r4"
+            "f.r32f.r32f.r32f.r32r4f.r32f.r32f.r32f.r32r4"
+            "f.r32f.r32f.r32f.r32r4f.r32f.r32f.r32f.r32r4"
+            "f.r32f.r32f.r32f.r32r8f1r8")
+    
+    
+    mml_compiler = MMLCompiler()
+    sequences = [mml_compiler.to_sequence(mml) for mml in mmls]
+    tones = [Tone(), Tone(), SquareWaveOscillator()]
+                    
+    seq = Sequencer()
+    seq.add_track(0, "track_0", tones[0], tones[0])
+    seq.add_track(1, "track_1", tones[1], tones[1])
+    seq.add_track(2, "track_3", tones[2], tones[2])
+    
+    seq.add_sequence(0, sequences[0])
+    seq.add_sequence(1, sequences[1])
+    seq.add_sequence(2, sequences[2])
+    
+    sink = WaveFileSink(output_file_name="output.wav")
+    clock = Clock()
+
+    renderer = Renderer(clock=clock, source=seq, sink=sink)
+    renderer.do_rendering()
+
+        
+
+def main():
+    # percusstion_test()
+    compiler_test()
+    
+
+if __name__ == "__main__":
+    main()
+# -*- coding: utf-8 -*-
+import re
+import math
+from Components import Config
+from Components import Clock
+from Components import Mixer
+from Components import Amplifeir
+
+
+class MMLCompiler(object):
+    def __init__(self, tuning=440, tempo=120, octave=4, on_length=4, volume=8):
+        self.tick_position = 0
+        self.tuning = tuning
+        self.tempo = tempo
+        self.octave = octave
+        self.on_length = on_length
+        self.volume = volume
+
+        self.note_diff = {"c":  -9, "c+": -8, "d-": -8, "d":  -7, "d+": -6,
+                          "e-": -6, "e":  -5, "f":  -4, "f+": -3, "g-": -3,
+                          "g":  -2, "g+": -1, "a-": -1, "a":   0, "a+":  1,
+                          "b-": 1, "b": 2, "r": None}
+
+        self.note_pattern = re.compile(r"^([a-g]|r)(\+|\#|\-)?(\d+)?(\.)?", re.IGNORECASE)
+        self.command_pattern = re.compile(r"^(t|l|v|o)(\d+)", re.IGNORECASE)
+        self.octave_up_down_pattern = re.compile(r"(\<|\>)")
+
+
+    def to_sequence(self, mml):
+        mml = mml.lower()
+        sequence = list()
+        read_position = 0
+
+        while read_position < len(mml):
+            note_match = self.note_pattern.search(mml[read_position:])
+            if note_match:
+                # 音符の処理
+                (note_code, accidential, on_length, period) = note_match.groups()
+
+                # 発声長の取得
+                note_length = int(on_length) if on_length else self.on_length
+                note_on_tick = (Config.SampleRate / 1.0) * (60.0 / self.tempo) * (4.0 / note_length)
+
+                # 符点があったら音長を1.5倍にする
+                note_on_tick *= 1.5 if period else 1.0
+
+                # 発声終了時刻を更新する
+                self.tick_position += note_on_tick
+
+                # 音程の取得と周波数の計算
+                if note_code == "r":
+                    cut_off = True
+                    note_frequency = 1
+
+                else:
+                    cut_off = False
+                    note_diff = self.note_diff[note_code]
+
+                    # 半音符号の処理
+                    if accidential:
+                        if accidential in "#+":
+                            note_diff += 1
+                        elif accidential == "-":
+                            note_diff -= 1
+
+                    # 周波数の取得(平均12律)
+                    note_frequency = self.tuning * \
+                                     (2 ** (self.octave - 4)) * \
+                                     ((2 ** note_diff) ** (1 / 12.0))
+
+
+                # 音量指示をアッテネーター値に計算
+                attenuate = 0.0 if cut_off else (self.volume / 15.0)
+                    
+                # シーケンスに記録
+                sequence.append((attenuate, note_frequency, int(self.tick_position)))
+                read_position += note_match.end()
+                continue
+            
+
+            # コマンド系文字の処理
+            command_match = self.command_pattern.search(mml[read_position:])
+            if command_match:
+                (command_letter, value) = command_match.groups()
+
+                if command_letter == "t":
+                    # テンポ
+                    self.tempo = int(value)
+                    
+                elif command_letter == "v":
+                    # ボリューム
+                    self.volume = int(value)
+
+                elif command_letter == "l":
+                    # 既定音長
+                    self.on_length = int(value)
+
+                elif command_letter == "o":
+                    # オクターブ(絶対指定)
+                    self.octave = int(value)
+
+                read_position += command_match.end()
+                continue
+
+            if mml[read_position] in "<>":
+                # オクターブの相対指定
+                if mml[read_position] == ">":
+                    self.octave += 1
+                else:
+                    self.octave -= 1
+
+                read_position += 1
+                continue
+
+            raise ValueError(
+                "Invalid MML syntax at %d on '%s'" % (read_position, mml[read_position:]))
+
+        return sequence
+
+
+class Sequencer(object):
+    def __init__(self):
+        self.tracks = dict()
+        self.sequences = dict()
+        self.mixer = Mixer()
+
+
+    def add_track(self, track_id, track_name, input_component, output_component):
+        output_component = Amplifeir(source=output_component,
+                                     gain=Config.MaxGain,
+                                     attenuate=0.5)
+        self.tracks[track_id] = (track_name, input_component, output_component)
+        self.mixer.add_track(track_id, track_name, output_component)
+        self.sequences[track_id] = list()
+
+        
+    def add_sequence(self, track_id, sequence_data):
+        self.sequences[track_id].extend(sequence_data)
+
+
+    def get_value(self, tick):
+        for (track_id, sequence_data) in self.sequences.items():
+            if tick > sequence_data[0][2]:
+                del sequence_data[0]
+
+            if len(sequence_data) == 0:
+                return None
+            
+            (attenuate, frequency, off_tick) = sequence_data[0]
+            (track_name, input_component, output_component) = self.tracks[track_id]
+
+            input_component.frequency = frequency
+            output_component.attenuate = attenuate
+
+        return self.mixer.get_value(tick)
+            
+            
+            
+