Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ADSR envelope with velocity sensitivity #1832

Merged
merged 3 commits into from Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions animation_nodes/data_structures/midi/midi_note.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,34 @@ class MIDINote:
timeOff: float = 0
velocity: float = 0

def evaluate(self, time, attackTime, attackInterpolation, releaseTime, releaseInterpolation):
peakTime = self.timeOn + attackTime
endTime = self.timeOff + releaseTime
if self.timeOff >= time >= peakTime:
return 1
elif peakTime > time >= self.timeOn:
if attackTime == 0: return 1
return attackInterpolation((time - self.timeOn) / attackTime)
elif endTime >= time > self.timeOff:
if releaseTime == 0: return 1
return releaseInterpolation(1 - ((time - self.timeOff) / releaseTime))
return 0.0
def evaluate(self, time, attackTime, attackInterpolation, decayTime, decayInterpolation, sustainLevel,
releaseTime, releaseInterpolation, velocitySensitivity):

def valueInEnvelope():
This conversation was marked as resolved.
Show resolved Hide resolved
# find either point in time for envelope or where in envelope the timeOff happened
relativeTime = min(time, self.timeOff) - self.timeOn

if relativeTime <= 0.0:
return 0.0

if relativeTime < attackTime:
return attackInterpolation(relativeTime / attackTime)

relativeTime = relativeTime - attackTime

if relativeTime < decayTime:
decayNormalized = decayInterpolation(1 - relativeTime/ decayTime)
return decayNormalized * (1 - sustainLevel) + sustainLevel

return sustainLevel

value = valueInEnvelope()

if time > self.timeOff:
OmarEmaraDev marked this conversation as resolved.
Show resolved Hide resolved
value = value * releaseInterpolation(1 - ((time - self.timeOff) / releaseTime))

# if velocity sensitivity is 25%, then take 75% of envelope and 25% of envelope with velocity
return (1 - velocitySensitivity) * value + velocitySensitivity * self.velocity * value

def copy(self):
return MIDINote(self.channel, self.noteNumber, self.timeOn, self.timeOff, self.velocity)
19 changes: 13 additions & 6 deletions animation_nodes/data_structures/midi/midi_track.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,29 @@ class MIDITrack:
index: int = 0
notes: List[MIDINote] = field(default_factory = list)

def evaluate(self, time, channel, noteNumber, attackTime, attackInterpolation, releaseTime, releaseInterpolation):
def evaluate(self, time, channel, noteNumber,
attackTime, attackInterpolation, decayTime, decayInterpolation, sustainLevel,
releaseTime, releaseInterpolation, velocitySensitivity):

noteFilter = lambda note: note.channel == channel and note.noteNumber == noteNumber
timeFilter = lambda note: note.timeOff + releaseTime >= time >= note.timeOn
filteredNotes = filter(lambda note: noteFilter(note) and timeFilter(note), self.notes)
arguments = (time, attackTime, attackInterpolation, releaseTime, releaseInterpolation)
return max((note.evaluate(*arguments) for note in filteredNotes), default = 0)
arguments = (time, attackTime, attackInterpolation, decayTime, decayInterpolation,
sustainLevel, releaseTime, releaseInterpolation, velocitySensitivity)
This conversation was marked as resolved.
Show resolved Hide resolved
return max((note.evaluate(*arguments) for note in filteredNotes), default = 0.0)

def evaluateAll(self, time, channel, attackTime, attackInterpolation, releaseTime, releaseInterpolation):
def evaluateAll(self, time, channel,
attackTime, attackInterpolation, decayTime, decayInterpolation, sustainLevel,
releaseTime, releaseInterpolation, velocitySensitivity):
channelFilter = lambda note: note.channel == channel
timeFilter = lambda note: note.timeOff + releaseTime >= time >= note.timeOn
filteredNotes = list(filter(lambda note: channelFilter(note) and timeFilter(note), self.notes))
arguments = (time, attackTime, attackInterpolation, releaseTime, releaseInterpolation)
arguments = (time, attackTime, attackInterpolation, decayTime, decayInterpolation,
sustainLevel, releaseTime, releaseInterpolation, velocitySensitivity)
noteValues = []
for i in range(128):
filteredByNumberNotes = filter(lambda note: note.noteNumber == i, filteredNotes)
value = max((note.evaluate(*arguments) for note in filteredByNumberNotes), default = 0)
value = max((note.evaluate(*arguments) for note in filteredByNumberNotes), default = 0.0)
noteValues.append(value)
return noteValues

Expand Down
11 changes: 9 additions & 2 deletions animation_nodes/nodes/sound/evaluate_midi_track.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ def create(self):
self.newInput("Float", "Attack Time", "attackTime", value = 0.01)
self.newInput("Interpolation", "Attack Interpolation",
"attackInterpolation", defaultDrawType = "PROPERTY_ONLY")
self.newInput("Float", "Decay Time", "decayTime", value = 0.01)
self.newInput("Interpolation", "Decay Interpolation",
"decayInterpolation", defaultDrawType = "PROPERTY_ONLY")
self.newInput("Float", "Sustain Level", "sustainLevel", value = 0.6).setRange(0,1)
This conversation was marked as resolved.
Show resolved Hide resolved
self.newInput("Float", "Release Time", "releaseTime", value = 0.05)
self.newInput("Interpolation", "Release Interpolation",
"releaseInterpolation", defaultDrawType = "PROPERTY_ONLY")
self.newInput("Float", "Velocity Sensitivity", "velocitySensitivity", value = 0.0).setRange(0,1)
self.newInput("Scene", "Scene", "scene", hide = True)

if self.evaluationType == "SINGLE":
Expand All @@ -40,7 +45,9 @@ def getExecutionCode(self, required):
yield "time = frame / AN.utils.scene.getFPS(scene)"
This conversation was marked as resolved.
Show resolved Hide resolved
if self.evaluationType == "SINGLE":
yield ("noteValue = track.evaluate(time, channel, noteNumber,"
"attackTime, attackInterpolation, releaseTime, releaseInterpolation)")
"attackTime, attackInterpolation, decayTime, decayInterpolation, sustainLevel, releaseTime, releaseInterpolation,"
"velocitySensitivity)")
else:
yield ("noteValues = DoubleList.fromValues(track.evaluateAll(time, channel,"
"attackTime, attackInterpolation, releaseTime, releaseInterpolation))")
"attackTime, attackInterpolation, decayTime, decayInterpolation, sustainLevel, releaseTime, releaseInterpolation,"
"velocitySensitivity))")