Пример #1
0
	def __init__(self, note, duration, volume=0.5, envelope=None,
				echo=False, echo_delay=50, echo_volume=0.5):
		self.main_buffer = AudioBuffer()  # to hold the final sound of note
		self.raw_buffer = None  # to hold unprocessed note signal
		self.note = note
		self.rest = False

		if self.note == '-':
			self.rest = True
		
		# all duration data is in milliseconds
		self.duration = duration
		# residual sound caused by Release and other FX
		# TOTAL DURATION of the note == duration + tail_duration
		self.tail_duration = 0

		self.total_duration = None

		self.frequency = FREQ[self.note]
		self.volume = volume
		self.envelope = envelope

		# "harmonics" is a list such as [0.5, 0, 0, 0.25, 0.25], where
		# harmonic[0] is the 1st harmonic, harmonic[1] is the 2nd harmonic...
		# and the value of each index is the harmonic's volume ratio. We don't
		# have to have them add up to 1, but a value close to 1 is advised.
		self.harmonics = [0.85, 0.10, 0.05]

		self.echo = echo
		self.echo_delay = echo_delay

		# A value of 1 means "same as self.volume". So it's relative.
		self.echo_volume = echo_volume
Пример #2
0
    def __init__(self, *args, **kwargs):
        # list of Track objects
        self.tracks = []

        self.buffer = AudioBuffer()

        # cursor determines where the next writing operation will occur
        self.cursor = 0
Пример #3
0
	def generateRawNote(self):
		if self.note == '-':
			self.raw_buffer = Sinusoid(
				0, self.duration, 0
			)
		else:
			self.raw_buffer = Sinusoid(self.frequency, self.duration
									+ self.tail_duration, self.volume)
			# place a copy of the raw buffer onto the main buffer
			self.main_buffer = AudioBuffer([val for val in self.raw_buffer])
Пример #4
0
	def process(self):
		# completely process and get the note ready for writing
		if not self.rest:
			self.calcTailDuration()
			self.generateRawNote()
			self.applyHarmonics()
			if self.envelope:
				self.applyEnvelope()
			if self.echo:
				self.applyEcho()
		else:
			self.main_buffer = AudioBuffer(
				[0] * int(AudioBuffer.sample_rate * self.duration / 1000))
			self.raw_buffer = self.main_buffer.deepcopy()
Пример #5
0
	def applyEcho(self):
		sr = AudioBuffer.sample_rate

		# here, we just make a sequence of zeroes followed by the main buffer
		echo = (
			AudioBuffer([0]) * int(sr * self.echo_delay / 1000)) + self.main_buffer
		
		self.main_buffer = self.main_buffer.add(echo)
Пример #6
0
class SongBuffer():
    """ Responsible to put the tracks together and create the final
	song buffer, and then write out to a wav file.
	As it is, this class is highly inefficient, and will be revisited.
	"""
    def __init__(self, *args, **kwargs):
        # list of Track objects
        self.tracks = []

        self.buffer = AudioBuffer()

        # cursor determines where the next writing operation will occur
        self.cursor = 0

    def addTrack(self, track):
        self.tracks.append(track)

    def makeSong(self):
        i = 1
        for track in self.tracks:
            print("Processing track {}/{}...".format(i, len(self.tracks)))
            i += 1
            for note in track.notes:
                note.process()
                self.writeToSelf(note)
                note.clearBuffers()
            self.cursor = 0
        self.scaleVolume()

    def writeToSelf(self, note):
        self.buffer[self.cursor:] = self.buffer[self.cursor:].add(
            note.main_buffer)
        self.cursor += int(self.buffer.sample_rate * note.duration /
                           1000)  #TODO: wtf?

    def scaleVolume(self):
        # the song is completely ready, and sitting on the buffer
        # except its amplitude ranges between [-1, 1].
        # so we scale it up according to our sample width
        # minus a bit of a safety margin.
        # (because audio FX may have raised the amplitude to a value above 1)
        self.buffer = self.buffer.multiply(.25 * MAX_AMPLITUDE)

    def writeToWav(self):
        from config import TARGET_FOLDER_NAME, COMPOSITION_ABS_PATH
        sr = self.buffer.sample_rate

        print("Packing python objects into primitive types.")
        # Convert our [-1, 1] float array into an integer array
        data = array('i', [0] * len(self.buffer))
        for i in range(len(self.buffer)):
            data[i] = int(self.buffer[i])

        print("Writing out to wav...")

        target_name = os.path.splitext(
            os.path.basename(COMPOSITION_ABS_PATH))[0] + ".wav"
        # ^ target_name == <name_of_song>.wav

        with wave.open(
                os.path.join(os.path.dirname(__file__), "..",
                             TARGET_FOLDER_NAME, target_name), "wb") as f:
            f.setsampwidth(SAMPLE_WIDTH)
            f.setnchannels(1)
            f.setframerate(sr)

            f.writeframesraw(data)
            f.writeframes(b'')
Пример #7
0
class Note():
	"""Creates an AudioBuffer that holds a note.
	When process()'d, its main_buffer becomes an AudioBuffer with all desired
	effects applied, and whose total length	is the original length of the node
	+ the tail length. The tail is the residual sound tacked onto the note as
	a result of various Sound FX like echo and the envelope's release.
	
	---
	*** GENERAL LIFE CYCLE OF A NOTE OBJECT:

			Hello, I am a Note. I don't exist yet. This is my life cycle.

		----------------------------------------------------------------------
		1) 	I am usually created when someone wants to add me to a Track.
			The Track initializes me with the information it somehow knows.
		----------------------------------------------------------------------

			Now I am initialized.
			I know the frequency of my musical note.
			I know how long I should sing it, when the time comes.
			That's all I know.

			I don't know HOW to sing.

			I live inside my Track. He is our supervisor and manager.
			We sit in that Track with my other Note friends. And we sit there
			in an ordered way. We don't know how we are ordered, other people
			manage that for us.
			
			We wait for the day we sing.
			Actually...

			They tell me I will never "really" sing before an audience anyway.
			They tell me that it's because I will be long dead by then.
			They tell me I will just sing into a buffer, and get destroyed.

			They say it's because one day I will call my process() method,
			and that will make me large. Very large. Too large for anyone to
			want to keep. Then they will make me sing to a buffer, and kill me.

			I try not to think of these things.
			It's early for that stuff anyway.
			I don't ever want to call my process() method.
			Life here in my Track is okay.
		
		----------------------------------------------------------------------
		2) 	The Track then sets our envelope and other settings.
		----------------------------------------------------------------------
			Now all of my settings are the same as my friends' in the Track.
			They say I am now equipped with the necessary knowledge to call my
			process() method when needed.
			But it's early for that.
			Are the rumors even true?
			... Someone get me out.

		----------------------------------------------------------------------
		3)	Now our Track has a new boss. The SongBuffer. He has plans for us.
		----------------------------------------------------------------------
			We call it our MASTER. MASTER is our supervisor's boss.
			MASTER is scary.
			Today, our Track said:
				"When the time comes, I will have to let MASTER use you."
				"What will he do with us, supervisor?"
				"I am sorry..."
			When it was night time, I sneaked into our Track's bedroom and
			read through his diary. It read:
				"I feel helpless. My opinions don't matter anymore. He has
				gone CRAZY. He said he will separate the Notes. He said he will
				take them one by one, force them to call process(), and when
				they get large, make them sing into his buffer.
				And then... Oh god... Kill them. With a hammer.
				Why hammer? That's got to be some sick, cruel joke.
				I know death is a part of life, one that I should be able to
				embrace. But what keeps me up at night is the fact that, *when*
				they die, they will be all alone. Not a soul to accompany them.
				This isn't right.
				They never deserved this.
				WE never deserved this.
				I wish there was something I could do."

				They took C#2 today. They took him somewhere we couldn't
			see. But we did hear him... Oh, the screams...

			Is it going to be that painful?

			We felt him get larger and larger. He had become a monster.
			We could not see him, but we heard him sing his note.

			If you can call that "singing".

			Then we never heard of him ever again.

			I still hear him every night. Please make the nightmares stop.

		----------------------------------------------------------------------
		4) Now it is my turn. MASTER's men are taking me. I do not resist.
		----------------------------------------------------------------------
			For all my friends. C#2, Db4, A6, F4... who died alone.

		----------------------------------------------------------------------
		5) MASTER forces me to call my process() method.
		6) I become very large.
		7) MASTER forces me to sing into the final buffer.
		----------------------------------------------------------------------
			"the final buffer"... They named it so aptly.
			I sung into it today. It was bittersweet.
			I know what is coming my way. I will not make it harder for anyone.
			Life has been kind to me. I choose to go with a smile.
			For all my friends and family:
				Even though we have been made to suffer
				At least we've sung in the same buffer
				They never let us sing together in life
				But they can't stop us in the afterlife
		----------------------------------------------------------------------
		8) MASTER kills me with a hammer. There is blood everywhere. Why hammer
		----------------------------------------------------------------------
			In my final moments I get to watch my body parts get reallocated,
			and relabeled. For a brief moment, I see my MASTER's boss.
			He has many layers, eyes, arms and moving parts.
			He seems impatient. He is grand, and impressive.
			He takes no notice of me.
			I shift my focus. More of my parts are being reallocated.
			A funny thought... What if some of them end up next to Bb3's?
			Maybe this is all... recurrent... after all.
			Maybe my parts will... sing again...
			My vision gets dark. A smile lands on my face.
			I know I have done my job.
			I have been a good Note.
	---
	"""

	def __init__(self, note, duration, volume=0.5, envelope=None,
				echo=False, echo_delay=50, echo_volume=0.5):
		self.main_buffer = AudioBuffer()  # to hold the final sound of note
		self.raw_buffer = None  # to hold unprocessed note signal
		self.note = note
		self.rest = False

		if self.note == '-':
			self.rest = True
		
		# all duration data is in milliseconds
		self.duration = duration
		# residual sound caused by Release and other FX
		# TOTAL DURATION of the note == duration + tail_duration
		self.tail_duration = 0

		self.total_duration = None

		self.frequency = FREQ[self.note]
		self.volume = volume
		self.envelope = envelope

		# "harmonics" is a list such as [0.5, 0, 0, 0.25, 0.25], where
		# harmonic[0] is the 1st harmonic, harmonic[1] is the 2nd harmonic...
		# and the value of each index is the harmonic's volume ratio. We don't
		# have to have them add up to 1, but a value close to 1 is advised.
		self.harmonics = [0.85, 0.10, 0.05]

		self.echo = echo
		self.echo_delay = echo_delay

		# A value of 1 means "same as self.volume". So it's relative.
		self.echo_volume = echo_volume

	def process(self):
		# completely process and get the note ready for writing
		if not self.rest:
			self.calcTailDuration()
			self.generateRawNote()
			self.applyHarmonics()
			if self.envelope:
				self.applyEnvelope()
			if self.echo:
				self.applyEcho()
		else:
			self.main_buffer = AudioBuffer(
				[0] * int(AudioBuffer.sample_rate * self.duration / 1000))
			self.raw_buffer = self.main_buffer.deepcopy()

	def calcTailDuration(self):
		self.tail_duration = 0
		if self.envelope:
			self.tail_duration += self.envelope.release_time
		if self.echo:
			self.tail_duration += self.echo_delay
		self.total_duration = self.duration + self.tail_duration

	def generateRawNote(self):
		if self.note == '-':
			self.raw_buffer = Sinusoid(
				0, self.duration, 0
			)
		else:
			self.raw_buffer = Sinusoid(self.frequency, self.duration
									+ self.tail_duration, self.volume)
			# place a copy of the raw buffer onto the main buffer
			self.main_buffer = AudioBuffer([val for val in self.raw_buffer])

	def applyHarmonics(self):		
		# apply the volume to the 1st harmonic.
		self.main_buffer = self.main_buffer.multiply(self.harmonics[0])

		# generate the signals for the other harmonics
		# and add them all onto the main buffer
		for i in range(1, len(self.harmonics)):
			if self.harmonics[i] != 0:
				# looks complicated but it's not. see comments on "harmonics"
				harm = Sinusoid(self.frequency * (i+1),
								self.total_duration, self.harmonics[i])
				self.main_buffer = self.main_buffer.add(harm)


	def applyEnvelope(self):
		self.main_buffer = self.envelope.apply(self)

	def applyEcho(self):
		sr = AudioBuffer.sample_rate

		# here, we just make a sequence of zeroes followed by the main buffer
		echo = (
			AudioBuffer([0]) * int(sr * self.echo_delay / 1000)) + self.main_buffer
		
		self.main_buffer = self.main_buffer.add(echo)

	def clearBuffers(self):
		self.main_buffer.clear()
		self.raw_buffer.clear()