def __init__(self, maxSizeInSeconds = 30, pitch = A4, volume = 127, channels = 2): # SampleLength in milliseconds print "Max recording time:", maxSizeInSeconds, "secs" self.SampleSize = maxSizeInSeconds * 1000 # convert seconds into milliseconds self.MAX_LOOP_TIME = self.__msToFrames__(self.SampleSize) self.LOOP_CHANNELS = channels # holds recorded audio self.sample = FloatSample(self.MAX_LOOP_TIME, self.LOOP_CHANNELS) # create units self.lineIn = LineIn() # create input line (stereo) self.lineOut = LineOut() # create output line (stereo)(mixes output to computer's audio (DAC) card) self.panning = 63 # ranges from 0 (left) to 127 (right) - 63 is center self.panLeft = Pan() # Pan control for the left channel self.panRight = Pan() # Pan control for the right channel self.setPanning(self.panning) # initialize panning to center (63) # create sample player (mono or stereo, as needed) and connect to lineOut mixer if self.LOOP_CHANNELS == 1: # mono audio? # handle input self.writer = FixedRateMonoWriter() # captures incoming audio (mono) self.lineIn.output.connect(0, self.writer.input, 0) # connect line input to the sample writer (recorder) # handle output self.player = VariableRateMonoReader() # create mono sample player self.player.output.connect( 0, self.panLeft.input, 0) # connect single channel to pan control self.player.output.connect( 0, self.panRight.input, 0) elif self.LOOP_CHANNELS == 2: # stereo audio? # handle input self.writer = FixedRateStereoWriter() # captures incoming audio self.lineIn.output.connect(0, self.writer.input, 0) # connect line input to the sample writer (recorder) self.lineIn.output.connect(0, self.writer.input, 1) # handle output self.player = VariableRateStereoReader() # create stereo sample player self.player.output.connect( 0, self.panLeft.input, 0) # connect both channels to pan control self.player.output.connect( 1, self.panRight.input, 0) else: raise TypeError( "Can only record mono (1) or stereo (2 channels)." ) # now that we have a player, set the default and current pitches self.defaultPitch = pitch # default pitch of the live sample self.pitch = pitch # playback pitch (may be different from default pitch) self.frequency = self.__convertPitchToFrequency__(pitch) # and corresponding frequency # smooth out (linearly ramp) changes in player amplitude (without this, we get clicks) self.amplitudeSmoother = LinearRamp() self.amplitudeSmoother.output.connect( self.player.amplitude ) # connect to player's amplitude self.amplitudeSmoother.input.setup( 0.0, 0.5, 1.0 ) # set minimum, current, and maximum settings for control self.amplitudeSmoother.time.set( 0.0002 ) # and how many seconds to take for smoothing amplitude changes self.player.rate.set(jSyn.FRAMERATE) self.volume = volume # holds current volume (0-127) self.setVolume(self.volume) # sets the desired volume # connect panned sample to line output self.panLeft.output.connect (0, self.lineOut.input, 0) self.panRight.output.connect (1, self.lineOut.input, 1) # remember is sample is paused or not - needed for function isPaused() self.hasPaused = False # create time stamp variables self.beginRecordingTimeStamp = None # holds timestamp of when we start recording into the sample self.endRecordingTimeStamp = None # holds timestamp of when we stop recording into the sample self.recordedSampleSize = None # holds overall length of time of the sample rounded to nearest int self.recordingFlag = False # boolean flag that is only true when the sample is being written to self.monitoringFlag = False # boolean flag that is only true when monitor is turned on jSyn.addLive(self) # connect sample unit to the jSyn synthesizer # remember that this LiveSample has been created and is active (so that it can be stopped by JEM, if desired) __ActiveAudioSamples__.append(self)
def __init__(self, filename, pitch=A4, volume=127): # ensure the file exists (jSyn will NOT complain on its own) if not os.path.isfile(filename): raise ValueError("File '" + str(filename) + "' does not exist.") # file exists, so continue self.filename = filename # remember is sample is paused or not - needed for function isPaused() self.hasPaused = False # load and create the audio sample SampleLoader.setJavaSoundPreferred( False ) # use internal jSyn sound processes datafile = File(self.filename) # get sound file self.sample = SampleLoader.loadFloatSample( datafile ) # load it as a a jSyn sample self.channels = self.sample.getChannelsPerFrame() # get number of channels in sample # create lineOut unit (it mixes output to computer's audio (DAC) card) self.lineOut = LineOut() # create panning control (we simulate this using two pan controls, one for the left channel and # another for the right channel) - to pan we adjust their respective pan self.panLeft = Pan() self.panRight = Pan() # NOTE: The two pan controls have only one of their outputs (as their names indicate) # connected to LineOut. This way, we can set their pan value as we would normally, and not worry # about clipping (i.e., doubling the output amplitude). Also, this works for both mono and # stereo samples. # create sample player (mono or stereo, as needed) and connect to lineOut mixer if self.channels == 1: # mono audio? self.player = VariableRateMonoReader() # create mono sample player self.player.output.connect( 0, self.panLeft.input, 0) # connect single channel to pan control self.player.output.connect( 0, self.panRight.input, 0) elif self.channels == 2: # stereo audio? self.player = VariableRateStereoReader() # create stereo sample player self.player.output.connect( 0, self.panLeft.input, 0) # connect both channels to pan control self.player.output.connect( 1, self.panRight.input, 0) else: raise TypeError( "Can only play mono or stereo samples." ) # now that we have a player, set the default and current pitches self.defaultPitch = pitch # the default pitch of the audio sample self.pitch = pitch # remember playback pitch (may be different from default pitch) self.frequency = self.__convertPitchToFrequency__(pitch) # and corresponding frequency # now, connect pan control to mixer self.panLeft.output.connect( 0, self.lineOut.input, 0 ) self.panRight.output.connect( 1, self.lineOut.input, 1 ) # now, that panning is set up, initialize it to center self.panning = 63 # ranges from 0 (left) to 127 (right) - 63 is center self.setPanning( self.panning ) # and initialize # smooth out (linearly ramp) changes in player amplitude (without this, we get clicks) self.amplitudeSmoother = LinearRamp() self.amplitudeSmoother.output.connect( self.player.amplitude ) # connect to player's amplitude self.amplitudeSmoother.input.setup( 0.0, 0.5, 1.0 ) # set minimum, current, and maximum settings for control self.amplitudeSmoother.time.set( 0.0002 ) # and how many seconds to take for smoothing amplitude changes # play at original pitch self.player.rate.set( self.sample.getFrameRate() ) self.volume = volume # holds current volume (0 - 127) self.setVolume( self.volume ) # set the desired volume # NOTE: Adding to global jSyn synthesizer jSyn.add(self) # connect sample unit to the jSyn synthesizer # remember that this AudioSample has been created and is active (so that it can be stopped by JEM, if desired) __ActiveAudioSamples__.append(self)
class LiveSample(): """ Encapsulates a sound object created from live sound via the computer microphone (or line-in), which can be played once, looped, paused, resumed, stopped, copied and erased. The first parameter, maxSizeInSeconds, is the recording capacity (default is 30 secs). The larger this value, the more memory the object occupies, so this needs to be handled carefully. Also, each sound has a MIDI pitch associated with it (default is A4), so we can play different pitches with it (through pitch shifting). Finally, we can set/get its volume (0-127), panning (0-127), pitch (0-127), and frequency (in Hz). """ def __init__(self, maxSizeInSeconds = 30, pitch = A4, volume = 127, channels = 2): # SampleLength in milliseconds print "Max recording time:", maxSizeInSeconds, "secs" self.SampleSize = maxSizeInSeconds * 1000 # convert seconds into milliseconds self.MAX_LOOP_TIME = self.__msToFrames__(self.SampleSize) self.LOOP_CHANNELS = channels # holds recorded audio self.sample = FloatSample(self.MAX_LOOP_TIME, self.LOOP_CHANNELS) # create units self.lineIn = LineIn() # create input line (stereo) self.lineOut = LineOut() # create output line (stereo)(mixes output to computer's audio (DAC) card) self.panning = 63 # ranges from 0 (left) to 127 (right) - 63 is center self.panLeft = Pan() # Pan control for the left channel self.panRight = Pan() # Pan control for the right channel self.setPanning(self.panning) # initialize panning to center (63) # create sample player (mono or stereo, as needed) and connect to lineOut mixer if self.LOOP_CHANNELS == 1: # mono audio? # handle input self.writer = FixedRateMonoWriter() # captures incoming audio (mono) self.lineIn.output.connect(0, self.writer.input, 0) # connect line input to the sample writer (recorder) # handle output self.player = VariableRateMonoReader() # create mono sample player self.player.output.connect( 0, self.panLeft.input, 0) # connect single channel to pan control self.player.output.connect( 0, self.panRight.input, 0) elif self.LOOP_CHANNELS == 2: # stereo audio? # handle input self.writer = FixedRateStereoWriter() # captures incoming audio self.lineIn.output.connect(0, self.writer.input, 0) # connect line input to the sample writer (recorder) self.lineIn.output.connect(0, self.writer.input, 1) # handle output self.player = VariableRateStereoReader() # create stereo sample player self.player.output.connect( 0, self.panLeft.input, 0) # connect both channels to pan control self.player.output.connect( 1, self.panRight.input, 0) else: raise TypeError( "Can only record mono (1) or stereo (2 channels)." ) # now that we have a player, set the default and current pitches self.defaultPitch = pitch # default pitch of the live sample self.pitch = pitch # playback pitch (may be different from default pitch) self.frequency = self.__convertPitchToFrequency__(pitch) # and corresponding frequency # smooth out (linearly ramp) changes in player amplitude (without this, we get clicks) self.amplitudeSmoother = LinearRamp() self.amplitudeSmoother.output.connect( self.player.amplitude ) # connect to player's amplitude self.amplitudeSmoother.input.setup( 0.0, 0.5, 1.0 ) # set minimum, current, and maximum settings for control self.amplitudeSmoother.time.set( 0.0002 ) # and how many seconds to take for smoothing amplitude changes self.player.rate.set(jSyn.FRAMERATE) self.volume = volume # holds current volume (0-127) self.setVolume(self.volume) # sets the desired volume # connect panned sample to line output self.panLeft.output.connect (0, self.lineOut.input, 0) self.panRight.output.connect (1, self.lineOut.input, 1) # remember is sample is paused or not - needed for function isPaused() self.hasPaused = False # create time stamp variables self.beginRecordingTimeStamp = None # holds timestamp of when we start recording into the sample self.endRecordingTimeStamp = None # holds timestamp of when we stop recording into the sample self.recordedSampleSize = None # holds overall length of time of the sample rounded to nearest int self.recordingFlag = False # boolean flag that is only true when the sample is being written to self.monitoringFlag = False # boolean flag that is only true when monitor is turned on jSyn.addLive(self) # connect sample unit to the jSyn synthesizer # remember that this LiveSample has been created and is active (so that it can be stopped by JEM, if desired) __ActiveAudioSamples__.append(self) def startRecording(self): """ Writes lineIn data to the sample data structure. Gets a time stamp so that, when we stop, we may calculate the duration of the recording. """ # make sure sample is empty if self.recordedSampleSize != None: print "Warning: cannot record over an existing sample. Use erase() first, to clear it." else: # sample is empty, so it's OK to record print "Recording..." # make sure we are not already recording if not self.recordingFlag: # get timestamp of when we started recording, # so, later, we can calculate duration of recording self.beginRecordingTimeStamp = jSyn.synth.createTimeStamp() # start recording into the sample # (self.writer will update self.sample - the latter is passive, just a data holder) self.writer.dataQueue.queueOn( self.sample ) # connect the writer to the sample self.writer.start() # and write into it self.recordingFlag = True # remember that recording has started else: # otherwise, we are already recording, so let them know print "But, you are already recording..." def stopRecording(self): """ Stops the writer from recording into the sample data structure. Also, gets another time stamp so that, now, we may calculate the duration of the recording. """ # make sure we are currently recording if not self.recordingFlag: print "But, you are not recording!" else: print "Stopped recording." # stop writer from recording into the sample self.writer.dataQueue.queueOff( self.sample ) self.writer.stop() self.recordingFlag = False # remember that recording has stopped # now, let's calculate duration of recording # get a new time stamp self.endRecordingTimeStamp = jSyn.synth.createTimeStamp() # calculate number of frames in the recorded sample # (i.e., total duration in seconds x framerate) startTime = self.beginRecordingTimeStamp.getTime() # get start time endTime = self.endRecordingTimeStamp.getTime() # get end time recordingTime = endTime - startTime # recording duration (in seconds) # if we have recorded more than we can store, then we will truncate # (that's the least painful solution...) recordingCapacity = self.SampleSize / 1000 # convert to seconds if recordingTime > recordingCapacity: # let them know exceededSeconds = recordingTime-recordingCapacity # calculate overun print "Warning: Recording too long (by", round(exceededSeconds, 2), " secs)... truncating!" # truncate extra recording (by setting sample duration to max recording capacity) sampleDuration = self.SampleSize / 1000 else: # sample duration is within the recording capacity sampleDuration = recordingTime # let's remember duration of recording (convert to frames - an integer) self.recordedSampleSize = int(jSyn.FRAMERATE * sampleDuration) def startMonitoring(self): """ Starts monitoring audio being recorded (through the speakers). """ self.monitoringFlag = True # remember that monitoring is now on # make audio being recorded sound through the speakers. self.lineIn.output.connect(0, self.lineOut.input, 0) self.lineIn.output.connect(0, self.lineOut.input, 1) self.lineOut.start() print "Monitoring..." def stopMonitoring(self): """ Stops monitoring audio being recorded (through the speakers). """ self.monitoringFlag = False # remember that monitoring is now off. # make audio being recorded stop sounding through the speakers. self.lineIn.output.disconnect(0, self.lineOut.input, 0) self.lineIn.output.disconnect(0, self.lineOut.input, 1) print "Stopped monitoring." def isRecording(self): """ Returns True if LiveSample is recording; False otherwise. """ return self.recordingFlag def isMonitoring(self): """ Returns True if monitoring is on; ; False otherwise. """ return self.monitoringFlag def play(self, start = 0, size = -1): """ Play the sample once from the millisecond 'start' until the millisecond 'start' + 'size' (size == -1 means to the end). If 'start' and 'size' are omitted, play complete sample. """ # start playing back from the sample (loop it) # (sample frames get retrieved by the reader) if self.recordedSampleSize == None: print "Sample is empty! You need to record before you can play." else: # for faster response, we restart playing (as opposed to queue at the end) if self.isPlaying(): # is the sample already playing? self.stop() # yes, so stop it self.loop(1, start, size) def loop(self, times = -1 , start = 0, size = -1): """ Repeat the sample indefinitely (times = -1), or the specified piece of the sample from millisecond 'start' until millisecond 'start'+'size' (size == -1 means to the end). If 'start and 'size' are omitted, repeat the complete sample. """ if self.recordedSampleSize == None: # is the sample currently empty? print "Sample is empty! You need to record before you can loop." return -1 sampleTotalDuration = (self.recordedSampleSize / jSyn.FRAMERATE) * 1000 # total time of sample in milliseconds # is specified start time within the total duration of sample? if start < 0 or start > sampleTotalDuration: print "Start time provided (" + str(start) + ") should be between 0 and sample duration (" + str(sampleTotalDuration) + ")." return -1 # does the size specified exceed the total duration of the sample or is size an invalid value? if size == 0 or start + size > sampleTotalDuration: print "Size (" + str(size) + ") exceeds total sample duration (" + str(sampleTotalDuration) + "), given start ("+ str(start) + ")." return -1 # was the size specified less than the lowest value allowed? if size <= -1: size = self.recordedSampleSize # play to the end of the sample else: size = (size/1000) * jSyn.FRAMERATE # convert milliseconds into frames start = (start/1000) * jSyn.FRAMERATE # loop the sample continuously? if times == -1: self.player.dataQueue.queueLoop(self.sample, start, size) if times == 0: print "But, don't you want to play the sample at least once?" return -1 else: # Subtract 1 from number of times a sample should be looped. # 'times' is the number of loops of the sample after the initial playing. self.player.dataQueue.queueLoop(self.sample, start, size, times - 1) self.lineOut.start() # starts playing def stop(self): """ Stops sample from playing any further and restarts the sample from the beginning """ self.player.dataQueue.clear() self.hasPaused = False # remember sample is not paused def isPlaying(self): """ Returns True if the recorded sample is still playing; False otherwise. """ return self.player.dataQueue.hasMore() def isPaused(self): """ Returns True if the sample is paused; False otherwise. """ return self.hasPaused def pause(self): """ Pause playing recorded sample. """ if self.hasPaused: print "Sample is already paused!" else: self.lineOut.stop() # pause playing self.hasPaused = True # remember sample is paused def resume(self): """ Resume Playing the sample from the paused position """ if not self.hasPaused: print "Sample is already playing!" else: self.lineOut.start() # resume playing self.hasPaused = False # remember the sample is not paused def copy(self): """ Creates a copy of the sample. """ # verify we can make a copy if self.isRecording(): print "Cannot make a copy while recording!" else: # create copy with same duration (in seconds), default pitch, and volume (as original sample) copySample = LiveSample(self.SampleSize / 1000, self.defaultPitch, self.volume) copySample.recordedSampleSize = self.recordedSampleSize # also copy the recorded size (not part of the constructor) # copy original audio frames in new sample for i in range(self.sample.getNumFrames()): copySample.sample.writeDouble(i, self.sample.readDouble(i)) # also, copy all other attributes (so the two copies are identical) copySample.setFrequency( self.getFrequency() ) # yes, so make them sound alike copySample.setVolume( self.getVolume() ) copySample.setPanning( self.getPanning() ) # done, so return the copy return copySample def erase(self): """ Erases all contents of the sample. """ # is sample currently recording? if self.isRecording(): print "Cannot erase while recording!" # is sample currently playing, stop it if self.isPlaying(): self.stop() # clear the dataQueue, so recording of the sample will start at the beginning self.writer.dataQueue.clear() # rewrite audio data within sample frame by frame (0.0 means empty frame - no sound) for i in range(self.sample.getNumFrames()): self.sample.writeDouble(i, 0.0) # try to reset defaults self.setPitch( self.defaultPitch ) self.setPanning( 63 ) self.setVolume( 127 ) # set sample size to empty self.recordedSampleSize = None def setFrequency(self, freq): """ Set sample's playback frequency. """ rateChangeFactor = float(freq) / self.frequency # calculate change on playback rate self.frequency = freq # remember new frequency self.pitch = self.__convertFrequencyToPitch__(freq) # and corresponding pitch self.__setPlaybackRate__(self.__getPlaybackRate__() * rateChangeFactor) # and set new playback rate def getFrequency(self): """ Return sample's playback frequency. """ return self.frequency def setPitch(self, pitch): """ Set sample playback pitch. """ self.pitch = pitch # remember new playback pitch self.setFrequency(self.__convertPitchToFrequency__(pitch)) # update playback frequency (this changes the playback rate) def getPitch(self): """ Return sample's current pitch (it may be different from the default pitch). """ return self.pitch def getDefaultPitch(self): """ Return sample's default pitch. """ return self.defaultPitch def setPanning(self, panning): """ Set panning of sample (panning ranges from 0 - 127). """ if panning < 0 or panning > 127: print "Panning (" + str(panning) + ") should range from 0 to 127." else: self.panning = panning # remember it panValue = mapValue(self.panning, 0, 127, -1.0, 1.0) # map panning from 0,127 to -1.0,1.0 self.panLeft.pan.set(panValue) # and set it self.panRight.pan.set(panValue) def getPanning(self): """ Return sample's current panning (panning ranges from 0 - 127). """ return self.panning def setVolume(self, volume): """ Set sample's volume (volume ranges from 0 - 127). """ if volume < 0 or volume > 127: print "Volume (" + str(volume) + ") should range from 0 to 127." else: self.volume = volume # remember new volume amplitude = mapValue(self.volume,0,127,0.0,1.0) # map volume to amplitude self.amplitudeSmoother.input.set( amplitude ) # and set it def getVolume(self): """ Return sample's current volume (volume ranges from 0 - 127). """ return self.volume ######## low-level functions related to FrameRate and PlaybackRate ############################ def getFrameRate(self): """ Return sample's default recording rate (e.g., 44100.0 Hz). """ return jSyn.FRAMERATE def __setPlaybackRate__(self, newRate): """ Set sample's playback rate (e.g., 44100.0 Hz). """ self.player.rate.set(newRate) def __getPlaybackRate__(self): """ Return sample's playback rate (e.g., 44100.0 Hz). """ return self.player.rate.get() def __msToFrames__(self, milliseconds): """ Convert milliseconds to frames based on the frame rate of the sample. """ return int(self.getFrameRate() * (milliseconds / 1000.0) ) ######### Helper Functions for Various Conversions ################################################################## #Calculate the frequency in Hertz based on MIDI pitch (Middle C is 60.0) #Can use fractional pitches such as 60.5 which would give you a pitch half way between Middle C and C# def __convertPitchToFrequency__(self, pitch): """ Convert MIDI pitch to frequency in Hertz. """ concertA = 440.0 return concertA * 2.0 ** ((pitch - 69) / 12.0) def __convertFrequencyToPitch__(self, freq): """ Converts pitch frequency (in Hertz) to MIDI pitch. """ concertA = 440.0 return log(freq / concertA, 2.0) * 12.0 + 69 def __getSemitonesBetweenFrequencies__(self, freq1, freq2): """ Calculate number of semitones between two frequencies. """ semitones = (12.0 / log(2)) * log(freq2 / freq1) return int(semitones) def __getFrequencyChangeBySemitones__(self, freq, semitones): """ Calculates frequency change, given change in semitones, from a frequency. """ freqChange = (exp(semitones * log(2) / 12) * freq) - freq return freqChange
class AudioSample(): """ Encapsulates a sound object created from an external audio file, which can be played once, looped, paused, resumed, and stopped. Also, each sound has a MIDI pitch associated with it (default is A4), so we can play different pitches with it (through pitch shifting). Finally, we can set/get its volume (0-127), panning (0-127), pitch (0-127), and frequency (in Hz). Ideally, an audio object will be created with a specific pitch in mind. Supported data formats are WAV or AIF files (16, 24 and 32 bit PCM, and 32-bit float). """ def __init__(self, filename, pitch=A4, volume=127): # ensure the file exists (jSyn will NOT complain on its own) if not os.path.isfile(filename): raise ValueError("File '" + str(filename) + "' does not exist.") # file exists, so continue self.filename = filename # remember is sample is paused or not - needed for function isPaused() self.hasPaused = False # load and create the audio sample SampleLoader.setJavaSoundPreferred( False ) # use internal jSyn sound processes datafile = File(self.filename) # get sound file self.sample = SampleLoader.loadFloatSample( datafile ) # load it as a a jSyn sample self.channels = self.sample.getChannelsPerFrame() # get number of channels in sample # create lineOut unit (it mixes output to computer's audio (DAC) card) self.lineOut = LineOut() # create panning control (we simulate this using two pan controls, one for the left channel and # another for the right channel) - to pan we adjust their respective pan self.panLeft = Pan() self.panRight = Pan() # NOTE: The two pan controls have only one of their outputs (as their names indicate) # connected to LineOut. This way, we can set their pan value as we would normally, and not worry # about clipping (i.e., doubling the output amplitude). Also, this works for both mono and # stereo samples. # create sample player (mono or stereo, as needed) and connect to lineOut mixer if self.channels == 1: # mono audio? self.player = VariableRateMonoReader() # create mono sample player self.player.output.connect( 0, self.panLeft.input, 0) # connect single channel to pan control self.player.output.connect( 0, self.panRight.input, 0) elif self.channels == 2: # stereo audio? self.player = VariableRateStereoReader() # create stereo sample player self.player.output.connect( 0, self.panLeft.input, 0) # connect both channels to pan control self.player.output.connect( 1, self.panRight.input, 0) else: raise TypeError( "Can only play mono or stereo samples." ) # now that we have a player, set the default and current pitches self.defaultPitch = pitch # the default pitch of the audio sample self.pitch = pitch # remember playback pitch (may be different from default pitch) self.frequency = self.__convertPitchToFrequency__(pitch) # and corresponding frequency # now, connect pan control to mixer self.panLeft.output.connect( 0, self.lineOut.input, 0 ) self.panRight.output.connect( 1, self.lineOut.input, 1 ) # now, that panning is set up, initialize it to center self.panning = 63 # ranges from 0 (left) to 127 (right) - 63 is center self.setPanning( self.panning ) # and initialize # smooth out (linearly ramp) changes in player amplitude (without this, we get clicks) self.amplitudeSmoother = LinearRamp() self.amplitudeSmoother.output.connect( self.player.amplitude ) # connect to player's amplitude self.amplitudeSmoother.input.setup( 0.0, 0.5, 1.0 ) # set minimum, current, and maximum settings for control self.amplitudeSmoother.time.set( 0.0002 ) # and how many seconds to take for smoothing amplitude changes # play at original pitch self.player.rate.set( self.sample.getFrameRate() ) self.volume = volume # holds current volume (0 - 127) self.setVolume( self.volume ) # set the desired volume # NOTE: Adding to global jSyn synthesizer jSyn.add(self) # connect sample unit to the jSyn synthesizer # remember that this AudioSample has been created and is active (so that it can be stopped by JEM, if desired) __ActiveAudioSamples__.append(self) ### functions to control playback and looping ###################### def play(self, start=0, size=-1): """ Play the sample once from the millisecond 'start' until the millisecond 'start'+'size' (size == -1 means to the end). If 'start' and 'size' are omitted, play the complete sample. """ # for faster response, we restart playing (as opposed to queue at the end) if self.isPlaying(): # is another play is on? self.stop() # yes, so stop it self.loop(1, start, size) def loop(self, times = -1, start=0, size=-1): """ Repeat the sample indefinitely (times = -1), or the specified number of times from millisecond 'start' until millisecond 'start'+'size' (size == -1 means to the end). If 'start' and 'size' are omitted, repeat the complete sample. """ startFrames = self.__msToFrames__(start) sizeFrames = self.__msToFrames__(size) self.lineOut.start() # should this be here? if size == -1: # to the end? sizeFrames = self.sample.getNumFrames() - startFrames # calculate number of frames to the end if times == -1: # loop forever? self.player.dataQueue.queueLoop( self.sample, startFrames, sizeFrames ) else: # loop specified number of times self.player.dataQueue.queueLoop( self.sample, startFrames, sizeFrames, times-1 ) def stop(self): """ Stop the sample play. """ self.player.dataQueue.clear() self.hasPaused = False # reset def isPlaying(self): """ Returns True if the sample is still playing. """ return self.player.dataQueue.hasMore() def isPaused(self): """ Returns True if the sample is paused. """ return self.hasPaused def pause(self): """ Pause playing recorded sample. """ if self.hasPaused: print "Sample is already paused!" else: self.lineOut.stop() # pause playing self.hasPaused = True # remember sample is paused def resume(self): """ Resume Playing the sample from the paused position """ if not self.hasPaused: print "Sample is already playing!" else: self.lineOut.start() # resume playing self.hasPaused = False # remember the sample is not paused def setFrequency(self, freq): """ Set sample's playback frequency. """ rateChangeFactor = float(freq) / self.frequency # calculate change on playback rate self.frequency = freq # remember new frequency self.pitch = self.__convertFrequencyToPitch__(freq) # and corresponding pitch self.__setPlaybackRate__(self.__getPlaybackRate__() * rateChangeFactor) # and set new playback rate def getFrequency(self): """ Return sample's playback frequency. """ return self.frequency def setPitch(self, pitch): """ Set sample playback pitch. """ self.pitch = pitch # remember new playback pitch self.setFrequency(self.__convertPitchToFrequency__(pitch)) # update playback frequency (this changes the playback rate) def getPitch(self): """ Return sample's current pitch (it may be different from the default pitch). """ return self.pitch def getDefaultPitch(self): """ Return sample's default pitch. """ return self.defaultPitch def setPanning(self, panning): """ Set panning of sample (panning ranges from 0 - 127). """ if panning < 0 or panning > 127: print "Panning (" + str(panning) + ") should range from 0 to 127." else: self.panning = panning # remember it panValue = mapValue(self.panning, 0, 127, -1.0, 1.0) # map panning from 0,127 to -1.0,1.0 self.panLeft.pan.set(panValue) # and set it self.panRight.pan.set(panValue) def getPanning(self): """ Return sample's current panning (panning ranges from 0 - 127). """ return self.panning def setVolume(self, volume): """ Set sample's volume (volume ranges from 0 - 127). """ if volume < 0 or volume > 127: print "Volume (" + str(volume) + ") should range from 0 to 127." else: self.volume = volume # remember new volume amplitude = mapValue(self.volume,0,127,0.0,1.0) # map volume to amplitude self.amplitudeSmoother.input.set( amplitude ) # and set it def getVolume(self): """ Return sample's current volume (volume ranges from 0 - 127). """ return self.volume ### low-level functions related to FrameRate and PlaybackRate ###################### def getFrameRate(self): """ Return the sample's default recording rate (e.g., 44100.0 Hz). """ return self.sample.getFrameRate() def __setPlaybackRate__(self, newRate): """ Set the sample's playback rate (e.g., 44100.0 Hz). """ self.player.rate.set( newRate ) def __getPlaybackRate__(self): """ Return the sample's playback rate (e.g., 44100.0 Hz). """ return self.player.rate.get() def __msToFrames__(self, milliseconds): """ Converts milliseconds to frames based on the frame rate of the sample """ return int(self.getFrameRate() * (milliseconds / 1000.0)) ### helper functions for various conversions ###################### # Calculate frequency in Hertz based on MIDI pitch. Middle C is 60.0. You # can use fractional pitches so 60.5 would give you a pitch half way # between C and C#. (by Phil Burk (C) 2009 Mobileer Inc) def __convertPitchToFrequency__(self, pitch): """ Convert MIDI pitch to frequency in Hertz. """ concertA = 440.0 return concertA * 2.0 ** ((pitch - 69) / 12.0) def __convertFrequencyToPitch__(self, freq): """ Converts pitch frequency (in Hertz) to MIDI pitch. """ concertA = 440.0 return log(freq / concertA, 2.0) * 12.0 + 69 # following conversions between frequencies and semitones based on code # by J.R. de Pijper, IPO, Eindhoven # see http://users.utu.fi/jyrtuoma/speech/semitone.html def __getSemitonesBetweenFrequencies__(self, freq1, freq2): """ Calculate number of semitones between two frequencies. """ semitones = (12.0 / log(2)) * log(freq2 / freq1) return int(semitones) def __getFrequencyChangeBySemitones__(self, freq, semitones): """ Calculates frequency change, given change in semitones, from a frequency. """ freqChange = (exp(semitones * log(2) / 12) * freq) - freq return freqChange