Open hi-hat closed Hat choke and tempo

I think I need to find a SynthDef that does this. A sample player that loads 2 samples.
And and an envelope that closes the open hat when a closed hat sample is triggered. Also the open hat should close quicker as the tempo increases .
I feel a totally abrupt cut of a sample isn’t the same, I think the old drm machines had a lag there between the cut off . Just like closing a hat with a foot. If anyone has seen anything like this . Please pass it along Thank you

There are a number of different ways to deal with rhythm, velocity, drum patterns etc in SC. It might be worth posting an example of how you’re working. Atm it’s hard to make concrete suggestions because it’s unclear if they would fit into your workflow.

eg. If you’re working with routines it is pretty easy to include setting the gate of the open hat to 0 when triggering the closed hat sample, and mapping the release time of the open hat envelope to the tempo. But if you’re working with Pbinds etc there is probably a better way.

Im using the Pdef / patterns playing SynthDef’s way.

Alright, that’s not really my workflow but if you post an example someone can probably help :slight_smile:

One way of doing this is using a non gated envelope for the longest you want the open hh to ring and multiply by a ‘choke envelope’:

(
SynthDef(\hh, {
	var sig = HPF.ar(WhiteNoise.ar(0.1), 2000);
	var chokeEnv = Env.asr(0.005, 1, \chokeTime.kr(0.05)).ar(2, \gate.kr(1));
	var env = Env.perc(\atk.kr(0.01), \rel.kr(1)).ar(2); 
	Out.ar(\out.kr(0), sig * env * chokeEnv! 2)
}).add
)

Synth(\hh) // no chocking

(
Pdef(\test, Pbind(
	\instrument, \hh,
	\dur, 0.25,
	\legato, Pseq([0.1, 0.1, 0.1, 1], inf) // hh being chocked depending on the legato setting
)).play
)

Pdef(\test).stop

// or using two different synthdefs for open and closed hh, in this case you don't need to use the legato key
(
SynthDef(\closedHh, {
	var sig = HPF.ar(WhiteNoise.ar(0.1), 2000);
	var env = Env.perc(0.01, 0.05).ar(2); 
	Out.ar(\out.kr(0), sig * env ! 2)
}).add
)

(
Pdef(\test, Pbind(
	\instrument, Pseq([Pseq([\closedHh], 3), \hh, Pseq([\closedHh], 3), \hh, \closedHh], inf),
	\dur, Pseq([Pseq([0.25], 7), Pseq([0.125], 2)], inf),
	\chokeTime, 0.05 
	// the chokeTime is expressed in beats, so playing on a clock with a different tempo than 1 pbs will change chokeTime
)).play
)

Pdef(\test).stop
1 Like

Cool, I’ll try this. Thank you

I don’t know if a cross fade Ugen exists like a playbuf that loads 2 samples and has 2 trig inputs for each sample and an argument for the cross fade time. Something like that.

I thought id play around tonight with this idea.
I installed PlayBufCF from the wslib, Its a Playbuf that allows crossfades and has a lag for the crossfade. My inner autechre love always goes for the crunchy stuff.

I made this with my 606

b = Buffer.read(s, "/Users/ss/sounds/606OC.wav");
b.numFrames.postln; // This will post the number of frames in the buffer
b.duration.postln; // This will post the duration of the buffer in seconds

b = Buffer.read(s, "/Users/ss/sounds/606OC.wav"); // remember to free the buffer later.

(
z =SynthDef(\help_PlayBufCF3, { | out = 0, bufnum = 0 |
    var sig, trig, pos,lag;
	trig = Impulse.kr(11);
	pos = Dseq([1323,1494,2091,30000,2518,1793,1878, 8367].stutter(9), inf);
	lag = Dseq([0.002,0.003,0.07,0.1,0.01,0.04,0.07,0.3].stutter(9), inf);
    sig = PlayBufCF.ar(1, bufnum, 1,
        trig,
		startPos: Demand.kr(trig, 0, pos),
        n: 10, lag: Demand.kr(trig, 0, lag)  // because the shortest possible trigger time is 0.01
    ).dup;
    Out.ar(out, sig);
}).play(s, [\bufnum, b]);
)

z.free;

Of course, I had to quickly write up a soundviewer that shows me the sample frame numbers at the click of the mouse. So I whipped this up real quick
(updated)


(
SynthDef(\snippetPlayer, { |out=0, bufnum, start=0, dur=0.1|
    var sig = PlayBuf.ar(
        numChannels: 2,  // Assume stereo, adjust if needed
        bufnum: bufnum,
        rate: BufRateScale.kr(bufnum),
        trigger: 1,
        startPos: start,
        loop: 0
    );
    var env = EnvGen.kr(Env.linen(0.005, dur - 0.01, 0.005), doneAction: 2);
    Out.ar(out, sig * env);
}).add;

{
    var window, soundFile, buffer, sfView, frameText, infoText, zoomSlider, playButton, colorButton, synth;
    var playSnippet, updateZoom, changeWaveformColor, updateFramesDisplay;
    var lastOpenedPath;
    var framesCPButton, clearButton, framesDisplay;
    var isRecording = false, recordedFrames = [];

    // Create a window
    window = Window("Enhanced Sample Frame Finder", Rect(200, 200, 800, 500));

    // Function to update frames display and copy to clipboard
    updateFramesDisplay = {
        var framesString = recordedFrames.join(", ");
        framesDisplay.string = framesString;
        framesString.copy;
    };

    // Function to play a snippet of audio
    playSnippet = { |startFrame|
        if(synth.notNil and: { synth.isPlaying }, {
            synth.set(\start, startFrame, \bufnum, buffer.bufnum);
        }, {
            synth = Synth(\snippetPlayer, [
                \bufnum, buffer.bufnum,
                \start, startFrame,
                \dur, 0.1
            ]);
        });
    };

    // Function to update zoom
    updateZoom = { |zoomValue|
        var visibleFrames, start;
        visibleFrames = (soundFile.numFrames * zoomValue).asInteger;
        start = ((soundFile.numFrames - visibleFrames) * sfView.scrollPos).asInteger;
        sfView.zoom(visibleFrames / soundFile.numFrames);
        sfView.setSelection(0, [start, visibleFrames]);
        sfView.refresh;
    };

    // Function to change waveform color
    changeWaveformColor = {
        var newColor = Color.rand;
        sfView.waveColors = [newColor];
        sfView.refresh;
    };

    // Create a button to load a sound file
    Button(window, Rect(10, 10, 100, 20))
    .states_([["Load Sound File"]])
    .action_({
        Dialog.openPanel({ |path|
            lastOpenedPath = path.dirname;
            soundFile = SoundFile.new;
            soundFile.openRead(path);

            // Read the file into a buffer
            buffer = Buffer.read(Server.default, path);

            // Create or update the SoundFileView
            if(sfView.isNil, {
                sfView = SoundFileView(window, Rect(10, 70, 780, 200));
                sfView.gridOn = false;
                sfView.gridResolution = 1;
            });

            sfView.soundfile = soundFile;
            sfView.read(0, soundFile.numFrames);
            sfView.elasticMode_(true);

            // Set up the click action for the SoundFileView
            sfView.mouseDownAction = { |view, x, y|
                var frame = (x / view.bounds.width * soundFile.numFrames * view.xZoom + (soundFile.numFrames * view.scrollPos)).asInteger;
                frameText.string = "Frame: " ++ frame;
                playSnippet.(frame);
                if(isRecording, {
                    recordedFrames = recordedFrames.add(frame);
                    updateFramesDisplay.value;
                });
            };

            // Update info text
            infoText.string = "Duration: " ++ soundFile.duration.round(0.01) ++ " s | " ++
                              "Sample Rate: " ++ soundFile.sampleRate ++ " Hz | " ++
                              "Channels: " ++ soundFile.numChannels;

            soundFile.close;

            // Enable and reset UI elements
            zoomSlider.enabled = true;
            zoomSlider.value = 1;
            updateZoom.(1);
            playButton.enabled = true;
            colorButton.enabled = true;
            framesCPButton.enabled = true;
            clearButton.enabled = true;

            // Set initial waveform color
            changeWaveformColor.value;
        }, path: lastOpenedPath);
    });

    // Create a text view to display the frame number
    frameText = StaticText(window, Rect(120, 10, 200, 20))
    .string_("Frame: ");

    // Create a text view to display file info
    infoText = StaticText(window, Rect(10, 40, 780, 20))
    .string_("Load a sound file to see its information");

    // Create a zoom slider
    zoomSlider = Slider(window, Rect(10, 280, 780, 20))
    .value_(1)
    .enabled_(false)
    .action_({ |slider|
        updateZoom.(slider.value);
    });

    // Create a play button
    playButton = Button(window, Rect(10, 310, 100, 30))
    .states_([["Play", Color.black, Color.green], ["Stop", Color.white, Color.red]])
    .enabled_(false)
    .action_({ |button|
        if(button.value == 1, {
            synth = Synth(\snippetPlayer, [\bufnum, buffer.bufnum, \start, 0, \dur, buffer.duration]);
        }, {
            synth.free;
        });
    });

    // Create a color change button
    colorButton = Button(window, Rect(120, 310, 100, 30))
    .states_([["Change Color"]])
    .enabled_(false)
    .action_({
        changeWaveformColor.value;
    });

    // Create a Frames CP button
    framesCPButton = Button(window, Rect(230, 310, 100, 30))
    .states_([["Frames CP", Color.black, Color.yellow], ["Stop CP", Color.white, Color.red]])
    .enabled_(false)
    .action_({ |button|
        isRecording = button.value == 1;
        if(isRecording, {
            recordedFrames = [];
            updateFramesDisplay.value;
        });
    });

    // Create a clear button
    clearButton = Button(window, Rect(340, 310, 100, 30))
    .states_([["Clear"]])
    .enabled_(false)
    .action_({
        recordedFrames = [];
        updateFramesDisplay.value;
    });

    // Create a display for recorded frames
    framesDisplay = TextView(window, Rect(10, 350, 780, 100))
    .editable_(false)
    .font_(Font("Monaco", 9));

    // Set up the window to free the buffer when closed
    window.onClose = {
        buffer.free;
        synth.free;
    };

    window.front;
}.value;
)