Skip to content

Commit

Permalink
Merge pull request #199 from shorepine/combine_args
Browse files Browse the repository at this point in the history
Eq, Reverb, and Chorus args combined into single wire protocol strings
  • Loading branch information
dpwe authored Aug 31, 2024
2 parents 883c8c1 + 60dfe44 commit 8cc422c
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 212 deletions.
14 changes: 3 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,25 +197,19 @@ Here's the full list:
| `f` | `freq` | float[,float...] | frequency of oscillator, set of ControlCoefficients. Default is 0,1,0,0,0,0,1 (from `note` pitch plus `pitch_bend`) |
| `F` | `filter_freq` | float[,float...] | center frequency for biquad filter, set of ControlCoefficients |
| `G` | `filter_type` | 0-4 | 0 = none (default.) 1 = lowpass, 2 = bandpass, 3 = highpass, 4 = double-order lowpass. |
| `H` | `reverb_liveness` | float 0-1 | Reverb decay time, 1 = longest, default = 0.85. |
| `h` | `reverb_level` | float | Level at which reverb is mixed in to final output. Default 0, typically 1. |
| `h` | `reverb` | float[,float,float,float] | Reverb parameters level, liveness, damping, xover: Level is for output mix; liveness controls decay time, 1 = longest, default = 0.85; damping is extra decay of high frequencies, default 0.5; xover is damping crossover frequency, default 3000 Hz. |
| `I` | `ratio` | float | for ALGO types, where the base note frequency controls the modulators, or for the PARTIALS base note, where the ratio controls the speed of the playback |
| `j` | `reverb_damping` | float 0-1 | Reverb extra decay of high frequencies, default = 0.5. |
| `J` | `reverb_xover_hz` | float | Crossover frequency (in Hz) for damping decay, default = 3000. |
| `k` | `chorus_level` | float 0-1 | Gain applied to chorus when mixing into output. Set to 0 to turn off chorus. |
| `k` | `chorus` | float[,float,float,float] | Chorus parameters level, delay, freq, depth: Level is for output mix (0 to turn off); delay is max in samples (320); freq is LFO rate in Hz (0.5); depth is proportion of max delay (0.5). |
| `K` | `load_patch` | uint 0-X | Apply a saved patch to start at the selected oscillator |
| `L` | `mod_source` | 0 to OSCS-1 | Which oscillator is used as an modulation/LFO source for this oscillator. Source oscillator will be silent. |
| `l` | `vel` | float 0-1+ | velocity: > 0 to trigger note on, 0 to trigger note off |
| `M` | `chorus_freq` | float | LFO freq of chorus |
| `m` | `chorus_delay` | uint 1-512 | Maximum delay in chorus delay lines, in samples. Default 320 |
| `N` | `latency_ms` | uint | sets latency in ms. default 0 (see LATENCY) |
| `n` | `note` | uint 0-127 | midi note, sets frequency |
| `o` | `algorithm` | uint 1-32 | DX7 algorithm to use for ALGO type |
| `O` | `algo_source` | string | which oscillators to use for the algorithm. list of six (starting with op 6), use empty for not used, e.g 0,1,2 or 0,1,2,,, |
| `p` | `P-patch` | uint | choose a preloaded PCM sample or partial patch. Not for DX7 or Juno, use load_patch for those |
| `P` | `phase` | float 0-1 | where in the oscillator's cycle to start sampling from (also works on the PCM buffer). default 0 |
| `Q` | `pan` | float[,float...] | panning index ControlCoefficients (for stereo output), 0.0=left, 1.0=right. default 0.5. |
| `q` | `chorus_depth` | float | chorus depth |
| `R` | `resonance` | float | q factor of biquad filter. in practice, 0.5-16.0. default 0.7 |
| `r` | `voices` | int[,int] | String comma separated list of voices to send message to, or load patch into |
| `S` | `reset` | uint | resets given oscillator. set to > OSCS to reset all oscillators, gain and EQ |
Expand All @@ -226,10 +220,8 @@ Here's the full list:
| `v` | `osc` | uint 0 to OSCS-1 | which oscillator to control |
| `V` | `volume` | float 0-10 | volume knob for entire synth, default 1.0 |
| `w` | `wave` | uint 0-11 | waveform: [0=SINE, PULSE, SAW_DOWN, SAW_UP, TRIANGLE, NOISE, KS, PCM, ALGO, PARTIAL, PARTIALS, OFF]. default: 0/SINE |
| `x` | `eq_l` | float | in dB, fc=800Hz amount, -15 to 15. 0 is off. default 0. |
| `x` | `eq` | float,float,float | Equalization in dB low (~800Hz) / med (~2500Hz) / high (~7500Gz) -15 to 15. 0 is off. default 0. |
| `X` | `eg1_type` | uint 0-3 | Type for Envelope Generator 1 - 0: Normal (RC-like) / 1: Linear / 2: DX7-style / 3: True exponential. |
| `y` | `eq_m` | float | in dB, fc=2500Hz amount, -15 to 15. 0 is off. default 0. |
| `z` | `eq_h` | float | in dB, fc=7500Hz amount, -15 to 15. 0 is off. default 0. |



Expand Down
66 changes: 30 additions & 36 deletions amy.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ def to_str(x):
# Construct an AMY message
def message(osc=0, wave=None, patch=None, note=None, vel=None, amp=None, freq=None, duty=None, feedback=None, time=None, reset=None, phase=None, pan=None,
client=None, retries=None, volume=None, pitch_bend=None, filter_freq = None, resonance = None, bp0=None, bp1=None, eg0_type=None, eg1_type=None,
debug=None, chained_osc=None, mod_source=None, clone_osc=None, eq_l = None, eq_m = None, eq_h = None, filter_type= None,
algorithm=None, ratio = None, latency_ms = None, algo_source=None, chorus_level=None, chorus_delay=None, chorus_freq=None, chorus_depth=None,
reverb_level=None, reverb_liveness=None, reverb_damping=None, reverb_xover=None, load_patch=None, store_patch=None, voices=None, external_channel=None):
debug=None, chained_osc=None, mod_source=None, clone_osc=None, eq=None, filter_type= None,
algorithm=None, ratio = None, latency_ms = None, algo_source=None, chorus=None,
reverb=None, load_patch=None, store_patch=None, voices=None, external_channel=None):

m = ""
if(store_patch is not None): return "u" + str(store_patch)
Expand Down Expand Up @@ -182,18 +182,10 @@ def message(osc=0, wave=None, patch=None, note=None, vel=None, amp=None, freq=No
if(mod_source is not None): m = m + "L" + str(mod_source)
if(reset is not None): m = m + "S" + str(reset)
if(debug is not None): m = m + "D" + str(debug)
if(eq_l is not None): m = m + "x" + trunc(eq_l)
if(eq_m is not None): m = m + "y" + trunc(eq_m)
if(eq_h is not None): m = m + "z" + trunc(eq_h)
if(eq is not None): m = m + "x%s" % eq
if(filter_type is not None): m = m + "G" + str(filter_type)
if(chorus_level is not None): m = m + "k" + str(chorus_level)
if(chorus_delay is not None): m = m + "m" + str(chorus_delay)
if(chorus_depth is not None): m = m + 'q' + trunc(chorus_depth)
if(chorus_freq is not None): m =m + 'M' + trunc(chorus_freq)
if(reverb_level is not None): m = m + "h" + str(reverb_level)
if(reverb_liveness is not None): m = m + "H" + str(reverb_liveness)
if(reverb_damping is not None): m = m + "j" + str(reverb_damping)
if(reverb_xover is not None): m = m + "J" + str(reverb_xover)
if(chorus is not None): m = m + 'k%s' % chorus
if(reverb is not None): m = m + "h%s" % reverb
if(load_patch is not None): m = m + 'K' + str(load_patch)
if(voices is not None): m = m + 'r' + str(voices)
if(external_channel is not None): m = m + 'W' + str(external_channel)
Expand Down Expand Up @@ -383,8 +375,8 @@ def eq_test():
reset()
eqs = [ [0,0,0], [15,0,0], [0,0,15], [0,15,0],[-15,-15,15],[-15,-15,30],[-15,30,-15], [30,-15,-15] ]
for eq in eqs:
print("eq_l = %ddB eq_m = %ddB eq_h = %ddB" % (eq[0], eq[1], eq[2]))
send(eq_l=eq[0], eq_m=eq[1], eq_h=eq[2])
print("eq_l = %f dB, eq_m = %f dB, eq_h = %f dB" % (eq[0], eq[1], eq[2]))
send(eq="%.2f,%.2f,%.2f" % (eq[0], eq[1], eq[2]))
drums(loops=2)
time.sleep(1)
reset()
Expand Down Expand Up @@ -449,34 +441,36 @@ def c_major(octave=2,wave=SINE, **kwargs):
Chorus control
"""
def chorus(level=-1, max_delay=-1, freq=-1, amp=-1):
args = {}
if (freq >= 0):
args['chorus_freq'] = freq
if (amp >= 0):
args['chorus_depth'] = amp
chorus_level = ''
chorus_delay = ''
chorus_freq = ''
chorus_depth = ''
if (level >= 0):
args['chorus_level'] = level
chorus_level = str(level)
if (max_delay >= 0):
args['chorus_delay'] = max_delay
send(**args)
chorus_delay = str(max_delay)
if (freq >= 0):
chorus_freq = str(freq)
if (amp >= 0):
chorus_depth = str(amp)
chorus_arg = "%s,%s,%s,%s" % (chorus_level, chorus_delay, chorus_freq, chorus_depth)
send(chorus=chorus_arg)

"""
Reverb control
"""
def reverb(level=-1, liveness=-1, damping=-1, xover_hz=-1):
args = {}
reverb_level = ''
reverb_liveness = ''
reverb_damping = ''
reverb_xover = ''
if (level >= 0):
args['reverb_level'] = level
reverb_level = str(level)
if (liveness >= 0):
args['reverb_liveness'] = liveness
reverb_liveness = str(liveness)
if (damping >= 0):
args['reverb_damping'] = damping
reverb_damping = str(damping)
if (xover_hz >= 0):
args['reverb_xover'] = xover_hz
send(**args)






reverb_xover = str(xover_hz)
reverb_arg = "%s,%s,%s,%s" % (reverb_level, reverb_liveness, reverb_damping, reverb_xover)
send(reverb=reverb_arg)
25 changes: 11 additions & 14 deletions juno.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,22 +407,19 @@ def update_cho(self):
eq_l = -15
eq_m = 8
eq_h = 8
chorus_args = {
'eq_l': eq_l, 'eq_m': eq_m, 'eq_h': eq_h,
'chorus_level': float(self.chorus > 0)
cho_args = {
'eq': '%s,%s,%s' % (str(eq_l), str(eq_m), str(eq_h)),
}
if self.chorus:
chorus_args['chorus_depth'] = 0.5
if self.chorus == 1:
chorus_args['chorus_freq'] = 0.5
elif self.chorus == 2:
chorus_args['chorus_freq'] = 0.83
elif self.chorus == 3:
# We choose juno 60-style I+II. Juno 6-style would be freq=8 depth=0.25
chorus_args['chorus_freq'] = 1
chorus_args['chorus_depth'] = 0.08
if self.chorus == 0:
cho_args['chorus'] = '0'
else:
# We choose juno 60-style I+II for chorus=3. Juno 6-style would be freq=8 depth=0.25
cho_args['chorus'] = '1,,%s,%s' % (
(0, 0.5, 0.83, 1)[self.chorus], # chorus_freq
(0, 0.5, 0.5, 0.08)[self.chorus], # chorus_depth
)
# *Don't* send to oscs, these ones are global.
amy.send(**chorus_args)
amy.send(**cho_args)

# Setters for each Juno UI control
def set_param(self, param, val):
Expand Down
52 changes: 37 additions & 15 deletions src/amy.c
Original file line number Diff line number Diff line change
Expand Up @@ -1654,30 +1654,47 @@ struct event amy_parse_message(char * message) {
case 'F': parse_coef_message(message + start, e.filter_freq_coefs); break;
case 'G': e.filter_type = atoi(message + start); break;
/* g used for Alles for client # */
case 'H': if(AMY_HAS_REVERB) config_reverb(S2F(reverb.level), atoff(message + start), reverb.damping, reverb.xover_hz); break;
case 'h': if(AMY_HAS_REVERB) config_reverb(atoff(message + start), reverb.liveness, reverb.damping, reverb.xover_hz); break;
/* H available */
case 'h': if (AMY_HAS_REVERB) {
float reverb_params[4] = {AMY_UNSET_VALUE(reverb.liveness), AMY_UNSET_VALUE(reverb.liveness),
AMY_UNSET_VALUE(reverb.liveness), AMY_UNSET_VALUE(reverb.liveness)};
parse_float_list_message(message + start, reverb_params, 4, AMY_UNSET_VALUE(reverb.liveness));
// config_reverb doesn't understand UNSET, so copy in the current values.
if (AMY_IS_UNSET(reverb_params[0])) reverb_params[0] = S2F(reverb.level);
if (AMY_IS_UNSET(reverb_params[1])) reverb_params[1] = reverb.liveness;
if (AMY_IS_UNSET(reverb_params[2])) reverb_params[2] = reverb.damping;
if (AMY_IS_UNSET(reverb_params[3])) reverb_params[3] = reverb.xover_hz;
config_reverb(reverb_params[0], reverb_params[1], reverb_params[2], reverb_params[3]);
}
break;
/* i used by alles for sync index */
case 'I': e.ratio = atoff(message + start); break;
case 'j': if(AMY_HAS_REVERB)config_reverb(S2F(reverb.level), reverb.liveness, atoff(message + start), reverb.xover_hz); break;
case 'J': if(AMY_HAS_REVERB)config_reverb(S2F(reverb.level), reverb.liveness, reverb.damping, atoff(message + start)); break;
/* j, J available */
// chorus.level
case 'k': if(AMY_HAS_CHORUS)config_chorus(atoff(message + start), chorus.max_delay, chorus.lfo_freq, chorus.depth); break;
case 'k': if(AMY_HAS_CHORUS) {
float chorus_params[4] = {AMY_UNSET_VALUE(chorus.depth), AMY_UNSET_VALUE(chorus.depth),
AMY_UNSET_VALUE(chorus.depth), AMY_UNSET_VALUE(chorus.depth)};
parse_float_list_message(message + start, chorus_params, 4, AMY_UNSET_VALUE(chorus.depth));
// cpnfig_chorus doesn't understand UNSET, copy existing values.
if (AMY_IS_UNSET(chorus_params[0])) chorus_params[0] = S2F(chorus.level);
if (AMY_IS_UNSET(chorus_params[1])) chorus_params[1] = (float)chorus.max_delay;
if (AMY_IS_UNSET(chorus_params[2])) chorus_params[2] = chorus.lfo_freq;
if (AMY_IS_UNSET(chorus_params[3])) chorus_params[3] = chorus.depth;
config_chorus(chorus_params[0], (int)chorus_params[1], chorus_params[2], chorus_params[3]);
}
break;
case 'K': e.load_patch = atoi(message+start); break;
case 'l': e.velocity=atoff(message + start); break;
case 'L': e.mod_source=atoi(message + start); break;
// chorus.lfo_freq
case 'M': if(AMY_HAS_CHORUS)config_chorus(S2F(chorus.level), chorus.max_delay, atoff(message + start), chorus.depth); break;
// chorus.max_delay
case 'm': if(AMY_HAS_CHORUS)config_chorus(S2F(chorus.level), atoi(message + start), chorus.lfo_freq, chorus.depth); break;
/* m, M unused */
case 'N': e.latency_ms = atoi(message + start); break;
case 'n': e.midi_note=atoi(message + start); break;
case 'o': e.algorithm=atoi(message+start); break;
case 'O': copy_param_list_substring(e.algo_source, message+start); break;
case 'p': e.patch=atoi(message + start); break;
case 'P': e.phase=F2P(atoff(message + start)); break;
/* q unused */
case 'Q': parse_coef_message(message + start, e.pan_coefs); break;
// chorus.depth
case 'q': if(AMY_HAS_CHORUS)config_chorus(S2F(chorus.level), chorus.max_delay, chorus.lfo_freq, atoff(message+start)); break;
case 'R': e.resonance=atoff(message + start); break;
case 'r': copy_param_list_substring(e.voices, message+start); break;
case 'S': e.reset_osc = atoi(message + start); break;
Expand All @@ -1691,11 +1708,16 @@ struct event amy_parse_message(char * message) {
case 'w': e.wave=atoi(message + start); break;
/* W used by Tulip for CV, external_channel */
case 'X': e.eg_type[1] = atoi(message + start); break;
case 'x': e.eq_l = atoff(message+start); break;
/* Y available */
case 'y': e.eq_m = atoff(message+start); break;
case 'x': {
float eq[3] = {AMY_UNSET_VALUE(e.eq_l), AMY_UNSET_VALUE(e.eq_m), AMY_UNSET_VALUE(e.eq_h)};
parse_float_list_message(message + start, eq, 3, AMY_UNSET_VALUE(e.eq_l));
e.eq_l = eq[0];
e.eq_m = eq[1];
e.eq_h = eq[2];
}
break;
/* Y,y,z available */
/* Z used for end of message */
case 'z': e.eq_h = atoff(message+start); break;
default:
break;
}
Expand Down
4 changes: 2 additions & 2 deletions src/miniaudio.h
Original file line number Diff line number Diff line change
Expand Up @@ -32784,7 +32784,7 @@ static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjec

desiredSampleRate = sampleRate;
if (desiredSampleRate == 0) {
desiredSampleRate = pOrigFormat->mSampleRate;
desiredSampleRate = (ma_uint32)(pOrigFormat->mSampleRate);
}

desiredChannelCount = channels;
Expand Down Expand Up @@ -34305,7 +34305,7 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev
}

pData->channelsOut = bestFormat.mChannelsPerFrame;
pData->sampleRateOut = bestFormat.mSampleRate;
pData->sampleRateOut = (ma_uint32)(bestFormat.mSampleRate);
}

/* Clamp the channel count for safety. */
Expand Down
Loading

0 comments on commit 8cc422c

Please sign in to comment.