From 6e0f624a11dfd8960333555d658096f16dff2def Mon Sep 17 00:00:00 2001 From: Domenico Cipriani Date: Sat, 9 Mar 2024 22:38:41 +0100 Subject: [PATCH] changed package name to Coypu. add isNoteOn method to SmallInteger --- src/Coypu/Array.extension.st | 303 ++++ src/Coypu/BlockClosure.extension.st | 7 + src/Coypu/ByteString.extension.st | 646 +++++++++ src/Coypu/ByteSymbol.extension.st | 135 ++ src/Coypu/Character.extension.st | 8 + src/Coypu/Chord.class.st | 212 +++ src/Coypu/ChordTests.class.st | 22 + src/Coypu/CircleVisualization.class.st | 45 + src/Coypu/Clock.class.st | 111 ++ src/Coypu/Cycle.class.st | 84 ++ src/Coypu/CycleTests.class.st | 44 + src/Coypu/DirtSingleEvent.class.st | 137 ++ src/Coypu/ElasticEllipses.class.st | 72 + src/Coypu/FaderWindow.class.st | 15 + src/Coypu/Integer.extension.st | 486 +++++++ src/Coypu/KeyboardToKyma.class.st | 32 + src/Coypu/KeyboardToKymaDrums.class.st | 128 ++ src/Coypu/KeyboardToOSC.class.st | 225 +++ src/Coypu/KeyboardToSuperDirt.class.st | 47 + src/Coypu/LiveCodingTests.class.st | 141 ++ src/Coypu/MIDIReceiver.class.st | 136 ++ src/Coypu/MIDISender.class.st | 230 +++ src/Coypu/ManifestCoypu.class.st | 32 + src/Coypu/MidiMessageParseTests.class.st | 11 + src/Coypu/NoneVisualization.class.st | 21 + src/Coypu/Number.extension.st | 69 + src/Coypu/OrderedCollection.extension.st | 9 + src/Coypu/PLCWindow.class.st | 16 + src/Coypu/PLCWindowPresenter.class.st | 10 + src/Coypu/PacaAddress.class.st | 36 + src/Coypu/ParsingTests.class.st | 42 + src/Coypu/Performance.class.st | 710 +++++++++ src/Coypu/PerformanceTest.class.st | 393 +++++ src/Coypu/Performer.class.st | 34 + src/Coypu/PerformerKyma.class.st | 202 +++ src/Coypu/PerformerLocal.class.st | 66 + src/Coypu/PerformerMIDI.class.st | 129 ++ src/Coypu/PerformerPhaust.class.st | 48 + src/Coypu/PerformerSuperDirt.class.st | 104 ++ .../PerformerSuperDirtAndRoassal.class.st | 186 +++ .../PerformerSuperDirtAndVisual.class.st | 45 + src/Coypu/Playground4LiveCoding.class.st | 35 + src/Coypu/PmDeviceInfo.class.st | 111 ++ src/Coypu/PmError.class.st | 32 + src/Coypu/PmEvent.class.st | 42 + src/Coypu/PortAudio.class.st | 24 + src/Coypu/PortMidi.class.st | 206 +++ src/Coypu/Process.extension.st | 8 + src/Coypu/PtError.class.st | 17 + src/Coypu/Random.extension.st | 22 + src/Coypu/RandomTests.class.st | 13 + src/Coypu/Rhythm.class.st | 137 ++ src/Coypu/Scale.class.st | 211 +++ src/Coypu/SequenceableCollection.extension.st | 16 + src/Coypu/Sequencer.class.st | 1276 +++++++++++++++++ src/Coypu/SequencerMono.class.st | 169 +++ src/Coypu/SequencerPlayAnnouncement.class.st | 8 + src/Coypu/SequencerPoly.class.st | 42 + src/Coypu/SequencerTests.class.st | 148 ++ src/Coypu/SmallInteger.extension.st | 98 ++ src/Coypu/SuperDirt.class.st | 117 ++ src/Coypu/SuperDirtSynth.class.st | 42 + src/Coypu/SuperDirtTests.class.st | 5 + src/Coypu/Transport.class.st | 54 + src/Coypu/VerticalFader.class.st | 195 +++ src/Coypu/VerticalFaderForKyma.class.st | 28 + src/Coypu/VerticalFaderForMIDI.class.st | 126 ++ src/Coypu/VerticalFaderForOSC.class.st | 83 ++ src/Coypu/VerticalFaderForSuperDirt.class.st | 30 + .../VerticalFaderForSuperDirtGlobal.class.st | 49 + src/Coypu/VerticalFaderFreq.class.st | 39 + src/Coypu/package.st | 1 + 72 files changed, 8813 insertions(+) create mode 100644 src/Coypu/Array.extension.st create mode 100644 src/Coypu/BlockClosure.extension.st create mode 100644 src/Coypu/ByteString.extension.st create mode 100644 src/Coypu/ByteSymbol.extension.st create mode 100644 src/Coypu/Character.extension.st create mode 100644 src/Coypu/Chord.class.st create mode 100644 src/Coypu/ChordTests.class.st create mode 100644 src/Coypu/CircleVisualization.class.st create mode 100644 src/Coypu/Clock.class.st create mode 100644 src/Coypu/Cycle.class.st create mode 100644 src/Coypu/CycleTests.class.st create mode 100644 src/Coypu/DirtSingleEvent.class.st create mode 100644 src/Coypu/ElasticEllipses.class.st create mode 100644 src/Coypu/FaderWindow.class.st create mode 100644 src/Coypu/Integer.extension.st create mode 100644 src/Coypu/KeyboardToKyma.class.st create mode 100644 src/Coypu/KeyboardToKymaDrums.class.st create mode 100644 src/Coypu/KeyboardToOSC.class.st create mode 100644 src/Coypu/KeyboardToSuperDirt.class.st create mode 100644 src/Coypu/LiveCodingTests.class.st create mode 100644 src/Coypu/MIDIReceiver.class.st create mode 100644 src/Coypu/MIDISender.class.st create mode 100644 src/Coypu/ManifestCoypu.class.st create mode 100644 src/Coypu/MidiMessageParseTests.class.st create mode 100644 src/Coypu/NoneVisualization.class.st create mode 100644 src/Coypu/Number.extension.st create mode 100644 src/Coypu/OrderedCollection.extension.st create mode 100644 src/Coypu/PLCWindow.class.st create mode 100644 src/Coypu/PLCWindowPresenter.class.st create mode 100644 src/Coypu/PacaAddress.class.st create mode 100644 src/Coypu/ParsingTests.class.st create mode 100644 src/Coypu/Performance.class.st create mode 100644 src/Coypu/PerformanceTest.class.st create mode 100644 src/Coypu/Performer.class.st create mode 100644 src/Coypu/PerformerKyma.class.st create mode 100644 src/Coypu/PerformerLocal.class.st create mode 100644 src/Coypu/PerformerMIDI.class.st create mode 100644 src/Coypu/PerformerPhaust.class.st create mode 100644 src/Coypu/PerformerSuperDirt.class.st create mode 100644 src/Coypu/PerformerSuperDirtAndRoassal.class.st create mode 100644 src/Coypu/PerformerSuperDirtAndVisual.class.st create mode 100644 src/Coypu/Playground4LiveCoding.class.st create mode 100644 src/Coypu/PmDeviceInfo.class.st create mode 100644 src/Coypu/PmError.class.st create mode 100644 src/Coypu/PmEvent.class.st create mode 100644 src/Coypu/PortAudio.class.st create mode 100644 src/Coypu/PortMidi.class.st create mode 100644 src/Coypu/Process.extension.st create mode 100644 src/Coypu/PtError.class.st create mode 100644 src/Coypu/Random.extension.st create mode 100644 src/Coypu/RandomTests.class.st create mode 100644 src/Coypu/Rhythm.class.st create mode 100644 src/Coypu/Scale.class.st create mode 100644 src/Coypu/SequenceableCollection.extension.st create mode 100644 src/Coypu/Sequencer.class.st create mode 100644 src/Coypu/SequencerMono.class.st create mode 100644 src/Coypu/SequencerPlayAnnouncement.class.st create mode 100644 src/Coypu/SequencerPoly.class.st create mode 100644 src/Coypu/SequencerTests.class.st create mode 100644 src/Coypu/SmallInteger.extension.st create mode 100644 src/Coypu/SuperDirt.class.st create mode 100644 src/Coypu/SuperDirtSynth.class.st create mode 100644 src/Coypu/SuperDirtTests.class.st create mode 100644 src/Coypu/Transport.class.st create mode 100644 src/Coypu/VerticalFader.class.st create mode 100644 src/Coypu/VerticalFaderForKyma.class.st create mode 100644 src/Coypu/VerticalFaderForMIDI.class.st create mode 100644 src/Coypu/VerticalFaderForOSC.class.st create mode 100644 src/Coypu/VerticalFaderForSuperDirt.class.st create mode 100644 src/Coypu/VerticalFaderForSuperDirtGlobal.class.st create mode 100644 src/Coypu/VerticalFaderFreq.class.st create mode 100644 src/Coypu/package.st diff --git a/src/Coypu/Array.extension.st b/src/Coypu/Array.extension.st new file mode 100644 index 0000000..51d19d6 --- /dev/null +++ b/src/Coypu/Array.extension.st @@ -0,0 +1,303 @@ +Extension { #name : #Array } + +{ #category : #'*Coypu' } +Array >> * aNumber [ +"returns an Array with all the elements of self multiplicated for aNumber" + +| result | +result := ( 1to: (self size)) collect: [ :i | (self at: i) * aNumber ]. +^ result +] + +{ #category : #'*Coypu' } +Array >> + aNumber [ +" sum aNumber to each element of the array" +| result | +result := (1 to: (self size)) collect: [ :i | (self at: i ) + aNumber ]. +^ result +] + +{ #category : #'*Coypu' } +Array >> - aNumber [ +" sum aNumber to each element of the array" +| result | +result := (1 to: (self size)) collect: [ :i | (self at: i ) - aNumber ]. +^ result +] + +{ #category : #'*Coypu' } +Array >> > aString [ +"experimental" +Transcript show: aString; open. +] + +{ #category : #'*Coypu' } +Array >> @ anArrayWithARootNoteAndAnOctave [ + + | x anIntegerRoot anIntegerOctave | + anIntegerRoot := anArrayWithARootNoteAndAnOctave at: 1. + anIntegerOctave := anArrayWithARootNoteAndAnOctave at: 2. + x := OrderedCollection new. + (0 to: anIntegerOctave) do: [ :i | + x addAll: self + (anIntegerRoot + (i * 12)) ]. + ^ x +] + +{ #category : #'*Coypu' } +Array >> and: anArray [ + +^ Sequencer with: self with: anArray +] + +{ #category : #'*Coypu' } +Array >> and: anArray and: anotherArray [ + +^ Sequencer with: self with: anArray with: anotherArray +] + +{ #category : #'*Coypu' } +Array >> arp: anArrayOfIntervals [ + + "STILL NOT AT ITS BEST" + + "arpeggiate the notes inside the first array " + + | midResult finalResult restsOrGates restsOrGatesArpSize | + restsOrGates := self collect: [ :i | (i ~= 0) asInteger ]. + restsOrGatesArpSize := restsOrGates times: anArrayOfIntervals size. + midResult := OrderedCollection new. + finalResult := OrderedCollection new. + anArrayOfIntervals do: [ :i | midResult addAll: self + i ]. + 1 to: (midResult size) do: [ :i | finalResult add: (midResult at: i) * (restsOrGatesArpSize at: i) ]. + + + ^ finalResult asDirtArray +] + +{ #category : #'*Coypu' } +Array >> asDirtArray [ +"convenience method to send OSC messages to SuperDirt" +^ self +] + +{ #category : #'*Coypu' } +Array >> asMonoSeq [ + + "returns Sequencer with default note 60nn and default durations 1/16th and noteIndex = 0" + + "duration now expressed in number of steps" + + | numberOfNotes | + numberOfNotes := Array new: self trigs withAll: 60. + ^ SequencerMono new + gates: self; + notes: numberOfNotes; + durations: #( 1 ); + noteIndex: 0 +" duration in steps" + +] + +{ #category : #'*Coypu' } +Array >> asRhythm [ +" convert Array into a Rhythm" +| rhythm | +rhythm := Rhythm new: (self size). +(1 to: ( self size)) do: [ :i | rhythm at: i put: (self at: i) ]. +^ rhythm +] + +{ #category : #'*Coypu' } +Array >> asSeq [ + "returns Sequencer with default note 60nn and default durations = self size / numberOfTrigs and noteIndex = 0" + + | numberOfNotes dur seq | + numberOfNotes := self class new: self trigs withAll: 60. + seq := SequencerMono new + gates: self; + notes: numberOfNotes; + noteIndex: 1; + midiChannel: 1. + dur := self size / seq numberOfTrigs. + seq durations: dur asDirtArray. + ^ seq +] + +{ #category : #'*Coypu' } +Array >> euclidean [ + + "generates an euclidean rhythm with (self at: 1) onsets and (self at: 2) pulses. +algorithm based on the Bresenham approach" + + | onsets pulses slope result previous current | + current := 0. + onsets := self at: 1. + pulses := self at: 2. + slope := onsets / pulses. + result := OrderedCollection new. + (0 to: pulses - 1) do: [ :i | + current := (i * slope) asInteger. + current ~= previous + ifTrue: [ result add: 1 ] + ifFalse: [ result add: 0 ]. + previous := current ]. + + ^ result asDirtArray asRhythm asSeq +] + +{ #category : #'*Coypu' } +Array >> fullScale [ +| x numberOfOctaves| +numberOfOctaves := 6. + +x := OrderedCollection new. +1 to: numberOfOctaves do: [ :i | x addAll: (self + (self size * i) ) ]. +^ x +] + +{ #category : #'*Coypu' } +Array >> innestedWithTrigs: anotherArray [ +" add trigs in self if anotherArray has trigs at indexes where self have not" +(1 to: (self size)) do: [ :i | self at: i put: (1 min: ((self at: i) + (anotherArray at: i)))]. + +^ self +] + +{ #category : #'*Coypu' } +Array >> into: anotherArray [ + + "return a new array with anotherArray at the desired indexes; if the index is out of bound, the last element of another array is collected. + if the index is 0 a 0 is collected indicating a rest" + + ^ self collect: [ :i | i~= 0 ifTrue: [ anotherArray at: (i min: anotherArray size) ] ifFalse: [0]] +] + +{ #category : #'*Coypu' } +Array >> isSequencer [ + + ^ false +] + +{ #category : #'*Coypu' } +Array >> notes [ + + "convert an array of MIDI note numbers into a Sequencer where 0 corresponds to rests" + + | seq allTrigs validTrigs validNotes allValues | + allTrigs := OrderedCollection new. + + + allValues := OrderedCollection new. + self do: [ :i | + i isArray + ifTrue: [allTrigs addAll: i . allValues addAll: i ] + ifFalse: [ allTrigs add: i. allValues add: i ] ]. + validTrigs := allTrigs collect: [ :i | i isZero not asInteger ]. + + validNotes := allValues reject: [ :i | i = 0 ]. + seq := validTrigs asDirtArray asSeq notes: validNotes asDirtArray. + seq cycleDurations: self asCycleDurations. + ^ seq +] + +{ #category : #'*Coypu' } +Array >> numberOfGates [ +"return number of elements in the array that are greater than 0" +| result size | +result := self select: [ :i | i > 0 ]. +size := result size. +^ size +] + +{ #category : #'*Coypu' } +Array >> of: anotherArray [ +" economic implementation of self collect: [:i | anotherArray at: i]" +| result | +result := self collect: [ :i | anotherArray at: i ]. +^ result +] + +{ #category : #'*Coypu' } +Array >> offset: aNumber [ +"offset the rhythm by aNumber of 'steps' wrapping it around its size" +| newArray | +newArray := self class new: (self size). +0 to: ((self size) - 1) do: [ :i | newArray at: ((i + aNumber) modulo: (self size)) put: (self at: i + 1)]. +^ newArray . + +] + +{ #category : #'*Coypu' } +Array >> randomOctaves: anInteger [ + + | newArray | + newArray := (1 to: self size) collect: [ :i | + (self at: i) + + (12 * (Random new nextInteger: anInteger - 1)) ]. + ^ newArray +] + +{ #category : #'*Coypu' } +Array >> root: anIntegerRoot octaves: anIntegerOctave [ +| x | +x := OrderedCollection new. +(0 to: anIntegerOctave) do: [ :i | x addAll: (self + (anIntegerRoot + (i * 12) ) )]. +^ x + +] + +{ #category : #'*Coypu' } +Array >> times: aNumber [ +"returns an Array containing aNumber repetition of self" + +| newSize newArray | +newSize := aNumber * self size. +newArray := Array new: newSize. +(1 to: newSize) do: [ :i | newArray at: i put: (self at: ((i ) modulo: (self size )) ) ]. +^ newArray +] + +{ #category : #'*Coypu' } +Array >> to: aPerformance at: aKey [ + +aPerformance add: aKey -> self +] + +{ #category : #'*Coypu' } +Array >> toKyma: aString [ + "sends and array of oscmessages with the values of the array to the vcs labels with array indexes" + + | array pacaAddress sizeMessage| + pacaAddress := NetNameResolver stringFromAddress: (NetNameResolver addressForName:'beslime-691.local'). + sizeMessage := OSCMessage for: { '/vcs/', aString,'Size', '/1' . (self size) asFloat}. + array := ( 1 to: (self size)) collect: [ :i | OSCMessage for: {'/vcs/', aString, (i asString),'/1'. ((self at: i) asFloat) } ]. + (1 to: (array size)) do: [:i | (array at: i) sendToAddressString: pacaAddress port: 8000]. + sizeMessage sendToAddressString: pacaAddress port: 8000. + +"this message alls sends an OSC message called aStringSize" + +] + +{ #category : #'*Coypu' } +Array >> trigs [ + + "return the number of trigs (value = 1), of the array" + + | result | + result := 0. + (1 to: self size) do: [ :i | + (self at: i) = 1 + ifTrue: [ result := result + 1 ] + ifFalse: [ nil ] ]. + ^ result +] + +{ #category : #'*Coypu' } +Array >> wrap: anInteger [ + + "inspire by Cmajor language wrap always wrap the index of the array to the array size" + + | result | + result := self at: anInteger - 1 % self size + 1. + ^ result +] diff --git a/src/Coypu/BlockClosure.extension.st b/src/Coypu/BlockClosure.extension.st new file mode 100644 index 0000000..ac1d2b2 --- /dev/null +++ b/src/Coypu/BlockClosure.extension.st @@ -0,0 +1,7 @@ +Extension { #name : #BlockClosure } + +{ #category : #'*Coypu' } +BlockClosure >> loopFor: aNumber [ + +^ [aNumber timesRepeat: self ] fork. +] diff --git a/src/Coypu/ByteString.extension.st b/src/Coypu/ByteString.extension.st new file mode 100644 index 0000000..f41c78b --- /dev/null +++ b/src/Coypu/ByteString.extension.st @@ -0,0 +1,646 @@ +Extension { #name : #ByteString } + +{ #category : #'*Coypu' } +ByteString >> * aNumber [ + +" returns a string with aNumber repetiotion of self separated by space" +| result | + result := self. + (1 to: aNumber - 1) do: [ :i | result := result , ' ' , self ]. + + +^ result +] + +{ #category : #'*Coypu' } +ByteString >> / anInteger [ + + + +" return a string with self and anInteger minus one number of breaks - to be used to construct patterns for SuperDirt" + | result | + result := self. + + (anInteger - 1) timesRepeat: [ result := result , ' ' , '~'. ]. + ^ result + +] + +{ #category : #'*Coypu' } +ByteString >> asDirtArray [ +"convenience method to send OSC messages to SuperDirt" +^ Array with: self +] + +{ #category : #'*Coypu' } +ByteString >> asDirtNotes [ +" returns a sequncer containing a trig for each integer and a rest for whatever other character and with the notes coresponding to the integeres inside the string - tokens must be separated by string" +| seq tokens gates notesAStrings notesAsNumbers | + +gates := ''. +tokens := self splitOn: ','. +tokens do: [ :i | (i includes: $* ) ifTrue: [gates := gates, i multiplyStringsInString ] ifFalse: [(i includes: $/) ifTrue: [gates := gates, i withNRests ] + ifFalse: [gates := gates, i] + ]]. + +notesAStrings := (gates copyReplaceAll: '~' with: '') findBetweenSubstrings: ' '. + +notesAsNumbers := notesAStrings collect: [ :i | i ~= ' ' ifTrue: [i asInteger] ]. + + + +seq := gates asSeqGates asSeq. +seq dirtNotes: notesAsNumbers asArray . +seq notes: notesAsNumbers asArray. + +^ seq + +] + +{ #category : #'*Coypu' } +ByteString >> asDirtSounds [ + + " parse a String into a Sequencer containing information to end to the SuperDirt synth for SuperCollider" + + + + + +| seq tokens gates soundsAsStrings | + +gates := ''. +tokens := self splitOn: ','. +tokens do: [ :i | (i includes: $* ) ifTrue: [gates := gates, i multiplyStringsInString ] ifFalse: [(i includes: $/) ifTrue: [gates := gates, i withNRests ] + ifFalse: [gates := gates, i] + ]]. + +soundsAsStrings := (gates copyReplaceAll: '~' with: '') . + + + + +seq := gates asSeqGates asSeq. +seq sound: soundsAsStrings . + +^ seq +] + +{ #category : #'*Coypu' } +ByteString >> asLocalGate: gateTimeInSeconds note: aNoteNumber [ +" gate-like OSC messsage with multiple arguments" + " send an OSC message that mimics the behaviour oa MIDI note on message. + gateTime is expressed in seconds" + | label localAddress| + localAddress := '127.0.0.1'. + + label := '/', self. + + +^ [(OSCMessage for: { label . aNoteNumber asFloat }) sendToAddressString: localAddress port: 8000. + (OSCMessage for: { label . 1.0 }) sendToAddressString: localAddress port: 8000. + (Delay forSeconds: gateTimeInSeconds ) wait. + (OSCMessage for: { label . 0.0 }) sendToAddressString: localAddress port: 8000.] fork +] + +{ #category : #'*Coypu' } +ByteString >> asPacaAddress [ + + + "set the pacaAddress globally from the corresponding Paca(rana) serial number" +| serialNumber | +serialNumber := 'beslime-51.local'. + Smalltalk at: #pacaAddress put: (NetNameResolver stringFromAddress: + (NetNameResolver addressForName: serialNumber )). + ^ NetNameResolver stringFromAddress: + (NetNameResolver addressForName: serialNumber ) +] + +{ #category : #'*Coypu' } +ByteString >> asSeqGates [ + +| gates tokens| + +" return an array of gates and rests, rests corrresponding to the ~ characters in the string" +gates := OrderedCollection new. +tokens := self findBetweenSubstrings: ' '. +tokens do: [ :i | i = '~' ifTrue: [ gates add: 0] ifFalse: [ gates add: 1 ] ]. + +^ gates asArray. + +] + +{ #category : #'*Coypu' } +ByteString >> asVerticalFaderLocal: aRange [ + + | fader | + " the fader label is also the OSC address" + + fader := VerticalFaderForOSC newWithAddress: self range: aRange . + fader openInWindow + +] + +{ #category : #'*Coypu' } +ByteString >> chordsToArrays [ + +| chordsAsStrings chordAsArrays| +chordsAsStrings := self findBetweenSubstrings: ' '. +chordAsArrays := chordsAsStrings collect: [ :i| i parseChord ]. +^ chordAsArrays + +] + +{ #category : #'*Coypu' } +ByteString >> every: anInteger [ + +" return a string with self and anInteger minus one number of breaks - to be used to construct patterns for SuperDirt" + | result | + result := OrderedCollection new. + result add: self. + (2 to: anInteger) do: [ :i | result add: ' '; add: '~' ]. + ^ result + +] + +{ #category : #'*Coypu' } +ByteString >> hexBeat [ + +" convert a string with an Hexadecimal number to the Kyma array correspondong to the expression BinaryArray ofSize: (self size) fromInteger: (self asHex)" +| strings flat rhythm| + +strings := (1 to: (self size)) collect: [ :i | +((self at: i) digitValue printStringBase: 2 length: 4 padded: true ) ]. +flat := strings flatCollect: [ :each | each ]. +rhythm := ((( 1 to: (flat size)) collect: [ :i | (flat at: i ) digitValue ]) asRhythm ). +^ rhythm asSeq. +] + +{ #category : #'*Coypu' } +ByteString >> hexBeat: anArrayOfNoteNumbers [ +" convert a string with an Hexadecimal number to the Kyma array corresponding to the expression BinaryArray ofSize: (self size) fromInteger: (self asHex) , and with anArrayOfNoteNumbers as Notes" +| strings flat | + +strings := (1 to: (self size)) collect: [ :i | +((self at: i) digitValue printStringBase: 2 length: 4 padded: true ) ]. +flat := strings flatCollect: [ :each | each ]. +^ ((( 1 to: (flat size)) collect: [ :i | (flat at: i ) digitValue ]) asRhythm ) and: anArrayOfNoteNumbers . +] + +{ #category : #'*Coypu' } +ByteString >> hexBeat: anArrayOfNoteNumbers dur: anArrayOfDurations [ +" convert a string with an Hexadecimal number to the Kyma array corresponding to the expression BinaryArray ofSize: (self size) fromInteger: (self asHex) , and with anArrayOfNoteNumbers as Notes and anArrayOfDurations as Durations" +| strings flat | + +strings := (1 to: (self size)) collect: [ :i | +((self at: i) digitValue printStringBase: 2 length: 4 padded: true ) ]. +flat := strings flatCollect: [ :each | each ]. +^ ((( 1 to: (flat size)) collect: [ :i | (flat at: i ) digitValue ]) asRhythm ) and: anArrayOfNoteNumbers and: anArrayOfDurations . +] + +{ #category : #'*Coypu' } +ByteString >> inWords [ +" return an Ordered collection separating all the words inside the string" + ^ self findBetweenSubstrings: ' ' +] + +{ #category : #'*Coypu' } +ByteString >> index: anArrayOfIndexes to: aKeyInAPerformance [ + +"convenience method to avoid extra parenthesis when sending an HEX rhythm pattern to aKeyInPerformance" + +self pattern index: anArrayOfIndexes to: aKeyInAPerformance +] + +{ #category : #'*Coypu' } +ByteString >> midiCh: anInteger to: aKeyInPerformance [ + "conveneience method to avoid extra parenthesis" +(self pattern midiChannel: anInteger) to: aKeyInPerformance +] + +{ #category : #'*Coypu' } +ByteString >> multiplyStringsInString [ +"multiply the string before the * fro the integer after the *" +| tokens | + + tokens := self splitOn: '*'. + +^ tokens first * tokens second asInteger +] + +{ #category : #'*Coypu' } +ByteString >> notes: anArrayOfNotes index: anArrayOfIndexes to: aKeyInAPerformance [ + +"convenience method to avoid extra parenthesis when sending an HEX rhythm pattern to aKeyInPerformance" + +self pattern notes: anArrayOfNotes index: anArrayOfIndexes to: aKeyInAPerformance +] + +{ #category : #'*Coypu' } +ByteString >> notes: anArrayOfNotes to: aKeyInAPerformance [ + +"convenience method to avoid extra parenthesis when sending an HEX rhythm pattern to aKeyInPerformance" + +self pattern notes: anArrayOfNotes to: aKeyInAPerformance . +] + +{ #category : #'*Coypu' } +ByteString >> once [ +"play the sound from the SuperDirt audio engine once at the desired index if any - +for exampe cp:4 sound the 3rd sample of the /Library/Application\ Support/SuperCollider/downloaded-quarks/Dirt-Samples/cp folder" +| sound note message parsedString| +parsedString := (self findBetweenSubstrings: ':') . +sound := parsedString at: 1. +parsedString size = 2 ifTrue: [ note:= parsedString at: 2 ] ifFalse: [note := 0]. +message := OSCMessage for: { '/dirt/play' . 's' . sound . 'n' . note}. +(OSCBundle for: { message } )sendToAddressString: '127.0.0.1' port: 57120. +] + +{ #category : #'*Coypu' } +ByteString >> onceAtSpeed: aFloat [ +"play the soundfrom the SuperDirt audio engine once at the desired speed" +| sound note message parsedString condition| +note := 0. +parsedString := (self findBetweenSubstrings: ':') . +sound := parsedString at: 1. +condition := parsedString size = 2. +condition ifTrue: [ note:= parsedString at: 2 ] ifFalse: [note := 0]. +message := OSCMessage for: { '/dirt/play' . 's' . sound . 'n' . note . 'speed' . aFloat}. +(OSCBundle for: { message } )sendToAddressString: '127.0.0.1' port: 57120. + + +] + +{ #category : #'*Coypu' } +ByteString >> onceAtSpeed: aFloat withNote: aNote [ +"play the soundfrom the SuperDirt audio engine once at the desired speed" +| sound message parsedString condition| + +parsedString := (self findBetweenSubstrings: ':') . +sound := parsedString at: 1. +condition := parsedString size = 2. + +message := OSCMessage for: { '/dirt/play' . 's' . sound . 'n' . aNote . 'speed' . aFloat}. +(OSCBundle for: { message } )sendToAddressString: '127.0.0.1' port: 57120. + + +] + +{ #category : #'*Coypu' } +ByteString >> parseChord [ + |root chord notes| + root := (self findBetweenSubstrings: '-') at: 1. + chord :=(self findBetweenSubstrings: '-') at: 2. + notes := Array new: 11. + notes := #('c' 'c#' 'd' 'd#' 'e' 'f' 'f#' 'g#' 'a' 'a#' 'b'). + ^ (Chord list at: chord asLowercase asSymbol) + (notes indexOf: root asLowercase ) - 1. +] + +{ #category : #'*Coypu' } +ByteString >> pattern [ +" convert a string with an Hexadecimal number to the Kyma array correspondong to the expression BinaryArray ofSize: (self size) fromInteger: (self asHex)" +| strings flat | + +strings := (1 to: (self size)) collect: [ :i | +((self at: i) digitValue printStringBase: 2 length: 4 padded: true ) ]. +flat := strings flatCollect: [ :each | each ]. +^ ((( 1 to: (flat size)) collect: [ :i | (flat at: i ) digitValue ]) asRhythm ) asSeq. +] + +{ #category : #'*Coypu' } +ByteString >> pattern: anArrayOfNoteNumbers [ +" convert a string with an Hexadecimal number to the Kyma array corresponding to the expression BinaryArray ofSize: (self size) fromInteger: (self asHex) , and with anArrayOfNoteNumbers as Notes" +| strings flat | + +strings := (1 to: (self size)) collect: [ :i | +((self at: i) digitValue printStringBase: 2 length: 4 padded: true ) ]. +flat := strings flatCollect: [ :each | each ]. +^ ((( 1 to: (flat size)) collect: [ :i | (flat at: i ) digitValue ]) asRhythm ) and: anArrayOfNoteNumbers . +] + +{ #category : #'*Coypu' } +ByteString >> pattern: anArrayOfNoteNumbers and: anArrayOfDurations [ +" convert a string with an Hexadecimal number to the Kyma array corresponding to the expression BinaryArray ofSize: (self size) fromInteger: (self asHex) , and with anArrayOfNoteNumbers as Notes and anArrayOfDurations as Durations" +| strings flat | + +strings := (1 to: (self size)) collect: [ :i | +((self at: i) digitValue printStringBase: 2 length: 4 padded: true ) ]. +flat := strings flatCollect: [ :each | each ]. +^ ((( 1 to: (flat size)) collect: [ :i | (flat at: i ) digitValue ]) asRhythm ) and: anArrayOfNoteNumbers and: anArrayOfDurations . +] + +{ #category : #'*Coypu' } +ByteString >> randomNotesFromFolder [ +"returns a random number (between 2 and 512) of indexes from the folder wit self name" +| randIndex folder folderSize| + randIndex := Random new nextIntegerBetween: 2 and: 512. + folder := SuperDirt samplesFolder / self . + folderSize := folder asFileReference allChildren size. + ^ randIndex randomInts: folderSize . +] + +{ #category : #'*Coypu' } +ByteString >> to: aKeyInPerformance [ + "convenience method to insert at aKeyInPerformance a pattern from the hex string" + +| p | + +p := Performance uniqueInstance . +self pattern to: aKeyInPerformance +] + +{ #category : #'*Coypu' } +ByteString >> toKymaAsGate [ + " send an OSC message that mimics the behaviour oa MIDI note on message, note is held for a default duration of 2 seconds" + | vcslabel | +" pacaAddress := NetNameResolver stringFromAddress: (NetNameResolver addressForName:'beslime-691.local'). " + + vcslabel := '/vcs/', self , 'Gate', '/1'. + (OSCMessage for: { vcslabel . 1.0 }) sendToAddressString: PacaAddress address port: 8000. + (Delay forSeconds: 2) wait. + (OSCMessage for: { vcslabel . 0.0 }) sendToAddressString: PacaAddress address port: 8000. +] + +{ #category : #'*Coypu' } +ByteString >> toKymaAsGate: gateTimeInSeconds [ + " send an OSC message that mimics the behaviour oa MIDI note on message. + gateTime is expressed in seconds" + | vcslabel | +" pacaAddress := NetNameResolver stringFromAddress: (NetNameResolver addressForName:'beslime-691.local'). " + + vcslabel := '/vcs/', self , 'Gate', '/1'. + ^ [(OSCMessage for: { vcslabel . 1.0 }) sendToAddressString: PacaAddress address port: 8000. + (Delay forSeconds: gateTimeInSeconds ) wait. + (OSCMessage for: { vcslabel . 0.0 }) sendToAddressString: PacaAddress address port: 8000. + ] fork +] + +{ #category : #'*Coypu' } +ByteString >> toKymaAsGate: gateTimeInSeconds note: aNoteNumber [ + + " send an OSC message that mimics the behaviour oa MIDI note on message. + gateTime is expressed in seconds" + + | vcsGatelabel vcsNoteLabel sendNoteNumber sendNoteOn sendNoteOff | + " pacaAddress := NetNameResolver stringFromAddress: (NetNameResolver addressForName:'beslime-691.local'). " + vcsGatelabel := '/vcs/' , self , 'Gate' , '/1'. + vcsNoteLabel := '/vcs/' , self , 'Note' , '/1'. + sendNoteNumber := (OSCMessage for: { + vcsNoteLabel. + aNoteNumber asFloat }) + sendToAddressString: pacaAddress + port: 8000. + [ "first NoteOff for VoiceStealing" + (OSCMessage for: { + vcsGatelabel. + 0.0 }) sendToAddressString: pacaAddress port: 8000."then note On" + (OSCMessage for: { + vcsGatelabel. + 1.0 }) sendToAddressString: pacaAddress port: 8000. + (Delay forSeconds: gateTimeInSeconds) wait. + "then note Off" + (OSCMessage for: { + vcsGatelabel. + 0.0 }) sendToAddressString: pacaAddress port: 8000 ] fork +] + +{ #category : #'*Coypu' } +ByteString >> toKymaAsGateOld: gateTimeInSeconds note: aNoteNumber [ + + " send an OSC message that mimics the behaviour oa MIDI note on message. + gateTime is expressed in seconds" + + | vcsGatelabel vcsNoteLabel | + " pacaAddress := NetNameResolver stringFromAddress: (NetNameResolver addressForName:'beslime-691.local'). " + vcsGatelabel := '/vcs/' , self , 'Gate' , '/1'. + vcsNoteLabel := '/vcs/' , self , 'Note' , '/1'. + + ^ [ + (OSCMessage for: { + vcsNoteLabel. + aNoteNumber asFloat }) + sendToAddressString: PacaAddress address + port: 8000. + (OSCMessage for: { + vcsGatelabel. + 1.0 }) sendToAddressString: PacaAddress address port: 8000. + (Delay forSeconds: gateTimeInSeconds) wait. + (OSCMessage for: { + vcsGatelabel. + 0.0 }) sendToAddressString: PacaAddress address port: 8000 ] fork +] + +{ #category : #'*Coypu' } +ByteString >> toKymaAsNoteOff: aNoteNumber [ + " send an OSC message that mimics the behaviour oa MIDI note on message." + | vcsGatelabel vcsNoteLabel| +" pacaAddress := NetNameResolver stringFromAddress: (NetNameResolver addressForName:'beslime-691.local'). " + + vcsGatelabel := '/vcs/', self , 'Gate', '/1'. + vcsNoteLabel := '/vcs/', self , 'Note', '/1'. + +(OSCMessage for: { vcsNoteLabel . aNoteNumber asFloat }) sendToAddressString: PacaAddress address port: 8000. +(OSCMessage for: { vcsGatelabel . 0.0 }) sendToAddressString: PacaAddress address port: 8000. + +] + +{ #category : #'*Coypu' } +ByteString >> toKymaAsNoteOn: aNoteNumber [ + " send an OSC message that mimics the behaviour oa MIDI note on message. + gateTime is expressed in seconds" + | vcsGatelabel vcsNoteLabel| +" pacaAddress := NetNameResolver stringFromAddress: (NetNameResolver addressForName:'beslime-691.local'). " + + vcsGatelabel := '/vcs/', self , 'Gate', '/1'. + vcsNoteLabel := '/vcs/', self , 'Note', '/1'. + +(OSCMessage for: { vcsNoteLabel . aNoteNumber asFloat }) sendToAddressString: PacaAddress address port: 8000. +(OSCMessage for: { vcsGatelabel . 1.0 }) sendToAddressString: PacaAddress address port: 8000. + +] + +{ #category : #'*Coypu' } +ByteString >> toKymaAsTrig [ + + " send an OSC message acting as a 10ms dutyCycle trigger" + + | vcslabel | + " pacaAddress := NetNameResolver stringFromAddress: (NetNameResolver addressForName:'beslime-691.local'). " + vcslabel := '/vcs/' , self , '/1'. + ^ [ + (OSCMessage for: { + vcslabel. + 1.0 }) sendToAddressString: PacaAddress address port: 8000. + (Delay forSeconds: 0.01) wait. + (OSCMessage for: { + vcslabel. + 0.0 }) sendToAddressString: PacaAddress address port: 8000 ] fork +] + +{ #category : #'*Coypu' } +ByteString >> toLocalAsGate [ + + " send an OSC message that mimics the behaviour of MIDI note on message, note is held for a default duration of 0.25 second and midi NN is 60" +self toLocalAsGate: 0.25 note: 60 +] + +{ #category : #'*Coypu' } +ByteString >> toLocalAsGate: gateTimeInSeconds [ + + " send an OSC message that mimics the behaviour oa MIDI note on message. + gateTime is expressed in seconds, MIDI noteNumber is 60" + +self toLocalAsGate: gateTimeInSeconds note: 60. +] + +{ #category : #'*Coypu' } +ByteString >> toLocalAsGate: gateTimeInSeconds note: aNoteNumber [ + + " send an OSC message that mimics the behaviour oa MIDI note on message. + gateTime is expressed in seconds" + + | oscGate oscNote localAddress | + localAddress := '127.0.0.1'. + + oscGate := '/' , self , 'Gate'. + oscNote := '/' , self , 'Note'. + (OSCMessage for: { + oscNote. + aNoteNumber asFloat }) + sendToAddressString: localAddress + port: 57120. + (OSCMessage for: { + oscGate. + 1.0 }) sendToAddressString: localAddress port: 57120. + (Delay forSeconds: gateTimeInSeconds) wait. + (OSCMessage for: { + oscGate. + 0.0 }) sendToAddressString: localAddress port: 57120 +] + +{ #category : #'*Coypu' } +ByteString >> toLocalFor: gateTimeInSeconds note: aNoteNumber [ + + " gate-like OSC messsage with multiple arguments" + + " send an OSC message that mimics the behaviour oa MIDI note on message. + gateTime is expressed in seconds" + + | label localAddress | + localAddress := '127.0.0.1'. + + label := '/' , self. + + + ^ [ + (OSCMessage for: { + label. + aNoteNumber asFloat. + 1.0 }) sendToAddressString: localAddress port: 8000. + + (Delay forSeconds: gateTimeInSeconds) wait. + (OSCMessage for: { + label. + aNoteNumber asFloat. + 0.0 }) sendToAddressString: localAddress port: 8000 ] fork +] + +{ #category : #'*Coypu' } +ByteString >> toSCAsGate [ + + " send an OSC message that mimics the behaviour oa MIDI note on message, note is held for a default duration of 2 seconds" + + | vcslabel localAddress | + localAddress := '127.0.0.1'. + + vcslabel := self , 'Gate'. + (OSCMessage for: { + vcslabel. + 1.0 }) sendToAddressString: localAddress port: 57110. + (Delay forSeconds: 2) wait. + (OSCMessage for: { + vcslabel. + 0.0 }) sendToAddressString: localAddress port: 57110 +] + +{ #category : #'*Coypu' } +ByteString >> toSCAsGate: gateTimeInSeconds [ + + " send an OSC message that mimics the behaviour oa MIDI note on message. + gateTime is expressed in seconds" + + | vcslabel localAddress | + localAddress := '127.0.0.1'. + vcslabel := self , 'Gate'. + ^ [ + (OSCMessage for: { + vcslabel. + 1.0 }) sendToAddressString: localAddress port: 57110. + (Delay forSeconds: gateTimeInSeconds) wait. + (OSCMessage for: { + vcslabel. + 0.0 }) sendToAddressString: localAddress port: 57110 ] fork +] + +{ #category : #'*Coypu' } +ByteString >> toSCAsGate: gateTimeInSeconds note: aNoteNumber [ + + " send an OSC message that mimics the behaviour oa MIDI note on message. + gateTime is expressed in seconds" + + | vcsGatelabel vcsNoteLabel localAddress | + localAddress := '127.0.0.1'. + + vcsGatelabel := self , 'Gate'. + vcsNoteLabel := self , 'Note'. + + ^ [ + (OSCMessage for: { + vcsNoteLabel. + aNoteNumber asFloat }) + sendToAddressString: localAddress + port: 8000. + (OSCMessage for: { + vcsGatelabel. + 1.0 }) sendToAddressString: localAddress port: 57110. + (Delay forSeconds: gateTimeInSeconds) wait. + (OSCMessage for: { + vcsGatelabel. + 0.0 }) sendToAddressString: localAddress port: 57110 ] fork +] + +{ #category : #'*Coypu' } +ByteString >> toSCFor: gateTimeInSeconds note: aNoteNumber [ + + " gate-like OSC messsage with multiple arguments" + + " send an OSC message that mimics the behaviour oa MIDI note on message. + gateTime is expressed in seconds" + + | label localAddress | + localAddress := '127.0.0.1'. + + label := self. + + + ^ [ + (OSCMessage for: { + label. + aNoteNumber asFloat. + 1.0 }) sendToAddressString: localAddress port: 57110. + + (Delay forSeconds: gateTimeInSeconds) wait. + (OSCMessage for: { + label. + aNoteNumber asFloat. + 0.0 }) sendToAddressString: localAddress port: 57110 ] fork +] + +{ #category : #'*Coypu' } +ByteString >> withNRests [ + " add rests to a string divided by an Integer inside a String" + +| tokens | +tokens := self splitOn: '/'. + +^ tokens first / tokens second asString asInteger +] diff --git a/src/Coypu/ByteSymbol.extension.st b/src/Coypu/ByteSymbol.extension.st new file mode 100644 index 0000000..57c5a45 --- /dev/null +++ b/src/Coypu/ByteSymbol.extension.st @@ -0,0 +1,135 @@ +Extension { #name : #ByteSymbol } + +{ #category : #'*Coypu' } +ByteSymbol >> asDirtArray [ +" convenience method to send messages to SuperDirt" + +^ Array with: self. +] + +{ #category : #'*Coypu' } +ByteSymbol >> bw: anArrayOfBw [ + + "set anArrayOfBw as extra1 of the sequencer in the performance at self" + + | p | + p := Performance uniqueInstance. + (p at: self) extra1: { + #Bw. + anArrayOfBw } +] + +{ #category : #'*Coypu' } +ByteSymbol >> extra1: anArrayWithASymbolAndAnArray [ + + " change the value of extra1 in the Performance at self key" + + | p | + p := Performance uniqueInstance. + + (p at: self) extra1: anArrayWithASymbolAndAnArray +] + +{ #category : #'*Coypu' } +ByteSymbol >> extra2: anArrayWithASymbolAndAnArray [ + + " change the value of extra1 in the Performance at self key" + + | p | + p := Performance uniqueInstance. + + (p at: self) extra2: anArrayWithASymbolAndAnArray +] + +{ #category : #'*Coypu' } +ByteSymbol >> for: anArray [ + +"creates a Dictionary with the symbol as key and the array as value" +| d | +d := Array with: (self asString) with: anArray. +^ d + + +" +d := Dictionary new. +d add: self -> anArray. +^ d +" +] + +{ #category : #'*Coypu' } +ByteSymbol >> indexes: anArrayOfIndexes [ + + "change the sample indexes of a seuquencer in a performance at self key" + + | p | + p := Performance uniqueInstance. + (p at: self) extra1: { + #Index. + anArrayOfIndexes } +] + +{ #category : #'*Coypu' } +ByteSymbol >> mute [ + + " remove the key from the Performance, if any active key in the Performance correspond to the symbol" + + | perf | + perf := Performance uniqueInstance. + perf removeKey: self ifAbsent: [ nil ] +] + +{ #category : #'*Coypu' } +ByteSymbol >> notes: anArrayOfNotes [ + + " change the notes of a Sequencer at self key in a Performance" + + | p | + p := Performance uniqueInstance. + (p at: self) notes: anArrayOfNotes +] + +{ #category : #'*Coypu' } +ByteSymbol >> number: anArrayOfNumbers [ + + " set the extra2 slot of the sequencer in the performance at self key with the values of anArrayOfNumbers and the string #Number - to be used with muultikits" + + | p | + p := Performance uniqueInstance. + (p at: self) extra2: { + #Number. + anArrayOfNumbers } +] + +{ #category : #'*Coypu' } +ByteSymbol >> solo [ + + "shorthand for p solo: aKeyInperformance" + + | p | + p := Performance uniqueInstance. + p solo: self +] + +{ #category : #'*Coypu' } +ByteSymbol >> unsolo [ + + "shorthand for p unsolo: aKeyInPerformance" + + | p | + p := Performance uniqueInstance. + p unsolo: self +] + +{ #category : #'*Coypu' } +ByteSymbol >> x: anArrayOfXs [ + + "set anArrayOfX as extra1 of the sequencer in the performance at self + intended to use for a Modal Bar strike position" + + | p | + p := Performance uniqueInstance. + (p at: self) extra1: { + #x. + anArrayOfXs } +] diff --git a/src/Coypu/Character.extension.st b/src/Coypu/Character.extension.st new file mode 100644 index 0000000..7d0c35f --- /dev/null +++ b/src/Coypu/Character.extension.st @@ -0,0 +1,8 @@ +Extension { #name : #Character } + +{ #category : #'*Coypu' } +Character >> * anInteger [ + | result | + result := self asString * anInteger. + ^ result +] diff --git a/src/Coypu/Chord.class.st b/src/Coypu/Chord.class.st new file mode 100644 index 0000000..cebb33c --- /dev/null +++ b/src/Coypu/Chord.class.st @@ -0,0 +1,212 @@ +" +""Creates a Chord as an array of MIDI note numbers"" +" +Class { + #name : #Chord, + #superclass : #Object, + #type : #variable, + #instVars : [ + 'list', + 'ChordLists' + ], + #classInstVars : [ + 'ChordLists' + ], + #category : #'Coypu-ScalesAndChords' +} + +{ #category : #'as yet unclassified' } +Chord class >> list [ + + "retrurn the list of all Chords" + | list | +list := Dictionary new. +list at: #major put: #(0 4 7); at: #minor put: #(0 3 7); at: #aug put: #( 0 4 8); at: #six put: #( 0 4 7 9); at: #six9 put: #( 0 4 7 9 14 ); at: #major7 put: #( 0 4 7 11); at: #major9 put: #( 0 4 7 11 14); at: #add9 put: #( 0 4 7 14); at: #major11 put: #( 0 4 7 11 14 17); at: #add11 put: #( 0 4 7 17); at: #major13 put: #( 0 4 7 11 14 17 21); at: #minor7 put: #( 0 3 7 10) ; at: #diminished put: #( 0 3 6) ; at: #minorsharp5 put: #( 0 3 8) ; at: #minor5 put: #( 0 3 7 9) ; at: #minoraixnine put: #( 0 3 7 9 14) ; at: #minor7flat5 put: #( 0 3 6 10) ; at: #minor7sharp5 put: #( 0 3 8 10); at: #diminished7 put: #( 0 3 6 9); at: #minor9 put: #( 0 3 7 10 14); at: #minor11 put: #( 0 3 7 10 14 17); at: #minor13 put: #( 0 3 7 10 14 17 21); at: #minorMajor7 put: #( 0 3 7 11); at: #five put: #( 0 5); at: #sus2 put: #( 0 2 7 ); at: #sus4 put: #( 0 5 7 ); at: #sevensus2 put: #( 0 2 7 10 ); at: #sevensus4 put: #( 0 5 7 10 ); at: #nineSus2 put: #( 0 2 7 10 14 ); at: #ninesus4 put: #( 0 5 7 10 14 );at: #sevenflat10 put: #( 0 4 7 10 15); at: #ninesharp5 put: #( 0 1 13); at: #minor9sharp5 put: #( 0 1 14) ; at: #sevensharp5flat9 put: #( 0 4 8 10 13) ; at: #minor7sharp5flat9 put: #( 0 3 8 10 13) ; at: #elevensharp put: #( 0 4 7 10 14 18) ;at: #minor11sharp put: #( 0 3 7 10 14 18); at: #dom7 put: #(0 4 7 10); at: #dom7 put: #(0 4 7 10) ; at: #dom9 put: #(0 4 7 14) ; at: #dom11 put: #(0 4 7 17) ;at: #dom13 put: #(0 4 7 21) ; at: #sevenflat5 put: #(0 4 6 10) ; at: #sevensharp5 put: #(0 4 8 10) ; at: #sevenflat9 put: #(0 4 7 10 13) ; at: #nine put: #(0 4 7 10 14) ; at: #eleven put: #(0 4 7 10 14 17); at: #thirteen put: #(0 4 7 10 14 17 21). + + + + + + + + + ^ list +] + +{ #category : #creating } +Chord class >> major [ + | notes | + notes := Scale major. +^ self with: (notes at: 1) with: (notes at: 3) with: (notes at: 5). + + +] + +{ #category : #'as yet unclassified' } +Chord class >> major11 [ + | notes | + notes := Scale major. +^ self with: (notes at: 1) with: (notes at: 3) with: (notes at: 5) with: (notes at: 7) with: (notes at: 9) with: (notes at: 10). + + +] + +{ #category : #'as yet unclassified' } +Chord class >> major13 [ + + | notes chord| + notes := Scale major. + chord := self new: 7. + chord at: 1 put: (notes at: 1); at: 2 put: (notes at: 3); at: 3 put: (notes at: 5); at: 4 put: (notes at: 7); at: 5 put: (notes at: 9); at: 6 put: (notes at: 10); at: 7 put: (notes at: 11). +^ chord + + + +] + +{ #category : #creating } +Chord class >> major7 [ + | notes | + notes := Scale major. +^ self with: (notes at: 1) with: (notes at: 3) with: (notes at: 5) with: (notes at: 7). + + +] + +{ #category : #'as yet unclassified' } +Chord class >> major9 [ + | notes | + notes := Scale major. +^ self with: (notes at: 1) with: (notes at: 3) with: (notes at: 5) with: (notes at: 7) with: (notes at: 9). + + +] + +{ #category : #creating } +Chord class >> minor [ + | notes | + notes := Scale minor. +^ self with: (notes at: 1) with: (notes at: 3) with: (notes at: 5). + + +] + +{ #category : #'as yet unclassified' } +Chord class >> minor11 [ + + | notes | + notes := Scale major. +^ self with: (notes at: 1) with: (notes at: 3) with: (notes at: 5) with: (notes at: 7) with: (notes at: 9) with: (notes at: 10). + + +] + +{ #category : #'as yet unclassified' } +Chord class >> minor13 [ + + | notes chord| + notes := Scale minor. + chord := self new: 7. + chord at: 1 put: (notes at: 1); at: 2 put: (notes at: 3); at: 3 put: (notes at: 5); at: 4 put: (notes at: 7); at: 5 put: (notes at: 9); at: 6 put: (notes at: 10); at: 7 put: (notes at: 11). +^ chord + + + +] + +{ #category : #creating } +Chord class >> minor7 [ + | notes | + notes := Scale minor. +^ self with: (notes at: 1) with: (notes at: 3) with: (notes at: 5) with: (notes at: 7) + + +] + +{ #category : #'as yet unclassified' } +Chord class >> minor9 [ + + | notes | + notes := Scale major. +^ self with: (notes at: 1) with: (notes at: 3) with: (notes at: 5) with: (notes at: 7) with: (notes at: 9). + + +] + +{ #category : #'as yet unclassified' } +Chord class >> traceList [ + +"open a Transcript and show a list of all available chords". +Transcript clear. +self list keysDo: [ :i | i traceCr ]. +Transcript open. +] + +{ #category : #initialization } +Chord >> initialize [ +super initialize . + + +] + +{ #category : #generating } +Chord >> randomize: aNumber [ + + | r s| + r := Random new. + s := (self size) - 1. + + ^ (1 to: aNumber) collect: [ :i | self at: (( (r next) * s) rounded) + 1 ]. +] + +{ #category : #generating } +Chord >> randomize: aNumber range: aRange [ + + | r s| + r := Random new. + s := self size. + + + + ^ (1 to: aNumber) collect: [ :i | (self at: (r nextInt: s)) + ((r nextInt: aRange) * ((r nextInt: 2) -2) )]. +] + +{ #category : #generating } +Chord >> randomize: aNumber range: aRange root: aRoot [ + + | r s| + r := Random new. + s := self size. + + + + ^ (1 to: aNumber) collect: [ :i | ((self at: (r nextInt: s)) + ((r nextInt: aRange) * ((r nextInt: 2) -2) )) + aRoot]. +] + +{ #category : #generating } +Chord >> randomize: aNumber range: aRange withRoot: aRoot [ + + | r s| + r := Random new. + s := self size. + + + + ^ (1 to: aNumber) collect: [ :i | ((self at: (r nextInt: s)) + ((r nextInt: aRange) * ((r nextInt: 2) -2) )) + aRoot]. +] + +{ #category : #generating } +Chord >> randomize: aNumber withRoot: aRoot [ + + | r s| + r := Random new. + s := (self size) - 1. + + ^ (1 to: aNumber) collect: [ :i | (self at: (( (r next) * s) rounded) + 1) + aRoot ]. +] + +{ #category : #accessing } +Chord >> root: aNumber [ +" return a Chord with aNumber nn as root" +^ (self + aNumber). +] diff --git a/src/Coypu/ChordTests.class.st b/src/Coypu/ChordTests.class.st new file mode 100644 index 0000000..f241ce7 --- /dev/null +++ b/src/Coypu/ChordTests.class.st @@ -0,0 +1,22 @@ +Class { + #name : #ChordTests, + #superclass : #TestCase, + #category : #'Coypu-Tests' +} + +{ #category : #tests } +ChordTests >> testChordsToArrays [ + +| result | +result := #( #(0 3 7) #(2 6 9) ). + +self assert: 'C-minor D-major' chordsToArrays asArray equals: result + + +] + +{ #category : #tests } +ChordTests >> testParseChord [ + +self assert: 'D-Major' parseChord equals: #(2 6 9) +] diff --git a/src/Coypu/CircleVisualization.class.st b/src/Coypu/CircleVisualization.class.st new file mode 100644 index 0000000..e709572 --- /dev/null +++ b/src/Coypu/CircleVisualization.class.st @@ -0,0 +1,45 @@ +Class { + #name : #CircleVisualization, + #superclass : #Object, + #instVars : [ + 'clock', + 'ellipses' + ], + #category : #'Coypu-Graph' +} + +{ #category : #accessing } +CircleVisualization >> clock [ + + ^ clock +] + +{ #category : #accessing } +CircleVisualization >> ellipses [ + + ellipses ifNotNil: [ ^ ellipses ]. + + ^ ellipses := ElasticEllipses new + start; + yourself +] + +{ #category : #rendering } +CircleVisualization >> showSound: soundName withNote: note [ + + | color | + color := Color r: note * 2 g: 0 b: note / 2. +" clock radius: note; update." + self ellipses color: color; update +] + +{ #category : #initialize } +CircleVisualization >> startWith: bpm [ + + clock := Clock new + bpm: bpm; + radius: 30; + yourself. + + clock start +] diff --git a/src/Coypu/Clock.class.st b/src/Coypu/Clock.class.st new file mode 100644 index 0000000..0521c60 --- /dev/null +++ b/src/Coypu/Clock.class.st @@ -0,0 +1,111 @@ +Class { + #name : #Clock, + #superclass : #Object, + #instVars : [ + 'bpm', + 'radius', + 'canvas', + 'characters' + ], + #category : #'Coypu-Graph' +} + +{ #category : #accessing } +Clock >> bpm [ + + ^ bpm +] + +{ #category : #accessing } +Clock >> bpm: anObject [ + + bpm := anObject +] + +{ #category : #accessing } +Clock >> radius [ + + ^ radius +] + +{ #category : #accessing } +Clock >> radius: aNumber [ + + radius := aNumber +] + +{ #category : #rendering } +Clock >> slice [ + + ^ Float twoPi / 16 +] + +{ #category : #rendering } +Clock >> start [ + + | slice animation ios | + canvas := RSCanvas new. + canvas color: Color black. + slice := self slice. + + ios := (1 to: 16) collect: [ :i | + i even + ifTrue: [ 'I' ] + ifFalse: [ 'O' ] ]. + characters := ios collect: [ :m | + RSLabel new + color: Color gray; + text: m; + yourself ]. + + self update. + + canvas add: (RSComposite new + shapes: characters; + yourself). + + canvas addAll: ('TICK' asDirtArray collectWithIndex: [ :m :i | + | s | + s := RSLabel new + color: Color white; + text: m asString; + yourself. + s position: 0 @ (-60 + (i * 15)). + s ]). + (canvas nodes at: 3) remove. + animation := [ :a :b :k | + canvas transitionAnimation + duration: (bpm bpm * 4) seconds; + easing: RSEasingInterpolator bounceOut; + from: a; + to: b; + onStepDo: [ :t | | s | + canvas nodes first matrix + loadIdentity; + rotateByRadians: t. + s := canvas nodes first shapes. + (s at: k key) color: Color lightGray. + (s at: k value) color: Color white. + ] ]. + (canvas animationFrom: { + (animation value: 0 value: slice value: 10 -> 11). + (animation value: slice value: slice * 2 value: 11 -> 10) }) + repeat. + + canvas + when: RSExtentChangedEvent + do: [ canvas camera zoomToFit: canvas extent extent: 120 asPoint ]. + + canvas open +] + +{ #category : #rendering } +Clock >> update [ + + characters doWithIndex: [ :s :i | + | ang | + ang := i * self slice. + s matrix rotateByRadians: ang + Float halfPi. + s position: ang cos @ ang sin * radius ]. + +] diff --git a/src/Coypu/Cycle.class.st b/src/Coypu/Cycle.class.st new file mode 100644 index 0000000..b5af194 --- /dev/null +++ b/src/Coypu/Cycle.class.st @@ -0,0 +1,84 @@ +" +Cycle represents a convenience class to hold information about cyclep playing, inspired by Tidal Cycles +" +Class { + #name : #Cycle, + #superclass : #Object, + #classVars : [ + 'duration' + ], + #category : #'Coypu-Sequencers' +} + +{ #category : #accessing } +Cycle class >> duration [ + + ^ duration +] + +{ #category : #initialization } +Cycle class >> initialize [ + + + "default speed for a Cycle inside the LiveCoding Package for Pharo is 132 bpm" + self setDuration: 60 / 132 * 4 +] + +{ #category : #'as yet unclassified' } +Cycle class >> play: aSequencer on: aMIDISender for: aNumberOfCycles [ + + "playMIDISequenceAt: aRateInSeconds steps: aNumberOfSteps on: aMIDISender" + + "test" + + " iterate over a process for self times, at a rate of aRateInMilliSeconds for step, through athe Performance containing arrays of numbers as values +keys in the Performance represents MIDI channels and must be written as #ch1 #ch2 #ch3 and so on if you want to send noteOn/off or + +if you want to send out ccs +" + + | step gateTime | + "as in early hardware sequencers, default gatetime is 80% of the step duration" + gateTime := 0. "we dont use it for now" + step := 0. + + + ^ [ + aNumberOfCycles timesRepeat: [ + (Delay forSeconds: + Cycle duration * (aSequencer cycleDurations at: + (step modulo: aSequencer cycleDurations size))) wait. + + + + (aSequencer gates at: (step modulo: aSequencer gatesSize)) = 1 + ifTrue: [ + aMIDISender + playDrum: (aSequencer notes at: + (aSequencer noteIndex modulo: aSequencer notesSize)) + onChannel: aSequencer midiChannel. + "advance" + aSequencer noteIndex: aSequencer noteIndex + 1 ]. + + + step := step + 1 ] ] forkAt: Processor timingPriority +] + +{ #category : #accessing } +Cycle class >> setDuration: aDurationInseconds [ + +duration := aDurationInseconds +] + +{ #category : #initialization } +Cycle >> initialize [ + + super initialize. + self setDuration: 60 / 132 * 4 +] + +{ #category : #initialization } +Cycle >> setDuration: aDurationInSeconds [ + +duration := aDurationInSeconds +] diff --git a/src/Coypu/CycleTests.class.st b/src/Coypu/CycleTests.class.st new file mode 100644 index 0000000..f32cada --- /dev/null +++ b/src/Coypu/CycleTests.class.st @@ -0,0 +1,44 @@ +Class { + #name : #CycleTests, + #superclass : #TestCase, + #category : #'Coypu-Tests' +} + +{ #category : #tests } +CycleTests >> testArrayAsCycleDuration [ + + | array expectedResult | + array := #( 60 60 60 #( 60 60 ) ). + expectedResult := Array with: 1/4 with: 1/4 with: 1/4 with: 1/8 with: 1/8. + self assert: array asCycleDurations equals: expectedResult +] + +{ #category : #tests } +CycleTests >> testCycleOf3QuarterNotesAnd2Quavers [ + + | s expectedResult | + s := #( 60 60 60 #( 60 60 ) ). + expectedResult := Array + with: 1 / 4 + with: 1 / 4 + with: 1 / 4 + + with: 1 / 8 + with: 1 / 8. + + self assert: s asCycleDurations equals: expectedResult +] + +{ #category : #tests } +CycleTests >> testCycleOfQuarterNotes [ + + | s expectedResult | + s := #( 60 60 60 60 ) asCycle. + expectedResult := Array + with: 1 / 4 + with: 1 / 4 + with: 1 / 4 + with: 1 / 4. + + self assert: s cycleDurations equals: expectedResult +] diff --git a/src/Coypu/DirtSingleEvent.class.st b/src/Coypu/DirtSingleEvent.class.st new file mode 100644 index 0000000..1ed595c --- /dev/null +++ b/src/Coypu/DirtSingleEvent.class.st @@ -0,0 +1,137 @@ +" +use this class to send out single evnts to the SuperDirt audio engine +" +Class { + #name : #DirtSingleEvent, + #superclass : #Object, + #instVars : [ + 'dirtMessage' + ], + #category : #'Coypu-SuperDirt' +} + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> add: anAssociationParameterValue [ +"add a string and a number to the dirtMessage, corresponding to a parameter for SuperDirt and its value ". +self dirtMessage add: anAssociationParameterValue key asString ; add: anAssociationParameterValue value +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> delay: anInteger [ +"change dry/wet delay for the sound ". +self dirtMessage add: 'delay'; add: anInteger +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> delta: anInteger [ +"change deltaduration for the sound ". +self dirtMessage add: 'delta'; add: anInteger +] + +{ #category : #accessing } +DirtSingleEvent >> dirtMessage [ + + ^ dirtMessage +] + +{ #category : #accessing } +DirtSingleEvent >> dirtMessage: anObject [ + + dirtMessage := anObject +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> fb: anInteger [ +"change deleay feedback for the sound ". +self dirtMessage add: 'delayfb'; add: anInteger +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> gain: anInteger [ +"change gain for the sound ". +self dirtMessage add: 'gain'; add: anInteger +] + +{ #category : #initialization } +DirtSingleEvent >> initialize [ +super initialize . +dirtMessage := OrderedCollection new add: '/dirt/play'; yourself. + +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> note: anInteger [ +"select the note or the folder index for the single event with the name of a folder in the SuperDirt sample folder". +self dirtMessage add: 'n'; add: anInteger +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> pan: aFloat [ +"change stereo panorama for the sound - 0 hard left, 1 hard righ". +self dirtMessage add: 'pan'; add: aFloat +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> phaserAmount: anInteger [ +"change phaserRate for the sound ". +self dirtMessage add: 'phaserdepth'; add: anInteger +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> phaserRate: anInteger [ +"change phaserRate for the sound ". +self dirtMessage add: 'phaserrate'; add: anInteger +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> room: anIntegerFromZeroToOne [ +"addReverb to the sound - it controls the dry/wet balance, for 0 is dry and 1 is wet". +self dirtMessage add: 'room'; add: anIntegerFromZeroToOne +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> send [ +"sends the event to SuperDirt" +(OSCMessage for: self dirtMessage ) sendToAddressString: '127.0.0.1' port: 57120 +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> size: anInteger [ +"change reverb size for the sound ". +self dirtMessage add: 'size'; add: anInteger +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> sound: aString [ +"select a sound for the single event with the name of a folder in the SuperDirt sample folder" +"for DirtSingleEventss folder inex after colons is discarded - use note: to set noteIndex" +| parsedString sound | + +parsedString := (aString findBetweenSubstrings: ':') . +sound := parsedString at: 1. +self dirtMessage add: 's'; add: sound. +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> speed: anInteger [ +"select the pitch of the sample". +self dirtMessage add: 'speed'; add: anInteger +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> squiz: anInteger [ +"change squiz amount for the sound ". +self dirtMessage add: 'size'; add: anInteger +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> time: aFloat [ +"change deleay time for the sound ". +self dirtMessage add: 'delaytime'; add: aFloat +] + +{ #category : #'LiveCoding - SuperDirt' } +DirtSingleEvent >> voice: aFloat [ +"change voice parameter for synthesizer". +self dirtMessage add: 'voice'; add: aFloat +] diff --git a/src/Coypu/ElasticEllipses.class.st b/src/Coypu/ElasticEllipses.class.st new file mode 100644 index 0000000..fcbf973 --- /dev/null +++ b/src/Coypu/ElasticEllipses.class.st @@ -0,0 +1,72 @@ +Class { + #name : #ElasticEllipses, + #superclass : #Object, + #instVars : [ + 'color', + 'canvas' + ], + #category : #'Coypu-Graph' +} + +{ #category : #accessing } +ElasticEllipses >> color [ + + ^ color +] + +{ #category : #accessing } +ElasticEllipses >> color: anObject [ + + color := anObject +] + +{ #category : #accessing } +ElasticEllipses >> initialize [ + + color := Color black +] + +{ #category : #accessing } +ElasticEllipses >> start [ + + canvas := RSCanvas new. + self update. + canvas when: RSExtentChangedEvent do: [ :evt | canvas zoomToFit ]. + canvas open +] + +{ #category : #accessing } +ElasticEllipses >> update [ + + | c1 c2 random realColor | + random := Random new. + canvas nodes copy do: #remove. + c1 := color. + c2 := color muchDarker. + realColor := NSScale linear range: { c1. c2 }. + + 1 to: 10000 do: [ :i | + | shape radius ang overlapped | + shape := RSEllipse new. + shape radius: random next * 30 + 2. + radius := random next * 250. + ang := random next * Float pi * 2. + shape position: radius * (ang cos @ ang sin). + overlapped := canvas nodes anySatisfy: [ :other | + (other position distanceTo: shape position) + < (shape radius + other radius * 1.4) ]. + overlapped ifFalse: [ + canvas add: shape. + shape propertyAt: #radius put: shape radius. + shape color: + (realColor scale: (shape position distanceTo: 0 @ 0) / 250) ] ]. + canvas newAnimation + easing: RSEasingInterpolator elasticOut; + onStepDo: [ :t | + canvas nodes do: [ :shape | + | scale | + scale := NSScale linear range: { + 1. + (shape propertyAt: #radius) }. + shape radius: (scale scale: t) ] ] +] diff --git a/src/Coypu/FaderWindow.class.st b/src/Coypu/FaderWindow.class.st new file mode 100644 index 0000000..a9cfa35 --- /dev/null +++ b/src/Coypu/FaderWindow.class.st @@ -0,0 +1,15 @@ +Class { + #name : #FaderWindow, + #superclass : #SystemWindow, + #category : #'Coypu-GUI' +} + +{ #category : #'as yet unclassified' } +FaderWindow class >> countFadersInWorld [ + + | fadersInWorld | + + fadersInWorld := self currentWorld submorphs select: [ + :i | (i class = FaderWindow)]. + ^ fadersInWorld size +] diff --git a/src/Coypu/Integer.extension.st b/src/Coypu/Integer.extension.st new file mode 100644 index 0000000..a8ce8a4 --- /dev/null +++ b/src/Coypu/Integer.extension.st @@ -0,0 +1,486 @@ +Extension { #name : #Integer } + +{ #category : #'*Coypu' } +Integer >> adowa [ +" create an array of self size of sikyi (by Ashanti people of Ghana) rhytm pulses. Better if the receiever is a multiple of 8." +| pattern | +pattern := #(0 0 0 1 0 1 0 1). + +^ ((0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]) asRhythm asSeq. + +] + +{ #category : #'*Coypu' } +Integer >> aksak [ +" create an array of self size of Balcan pseudo aksak rhytm pulses. Better if used with self = 8 x anInteger " +| pattern | +pattern := #(1 0 1 0 0 0 1 0 0 0). + +^ ((0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]) asRhythm asSeq. + +"implement shifting to the left" +] + +{ #category : #'*Coypu' } +Integer >> banda [ +" create an array of self size of Banda (Haiti/Vodoo) rhytm pulses. Better if used with self = 8 x anInteger . it is the same rhythm played in the moribayasa rhythm among the Malinke people of Guinea and in Cuba it is called cinquillo" +| pattern | +pattern := #(1 0 1 1 0 1 1 0). + +^ ((0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]) asRhythm asSeq. + +"implement shifting to the left" +] + +{ #category : #'*Coypu' } +Integer >> bomba [ +" create an array of self size of puertorican Bomba rhytm pulses. Better if the receiever is a multiple of eight." +| pattern | +pattern := #(1 0 0 1 1 1 1 0 ). + +^ ((0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]) asRhythm asSeq. + +] + +{ #category : #'*Coypu' } +Integer >> bossa [ +" create an array of self size of bossanova rhytm pulses. Better if used with self = 16 x anInteger " +| pattern | +pattern := #(1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 ). + +^ ((0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]) asRhythm asSeq . + +"implement shifting to the left" +] + +{ #category : #'*Coypu' } +Integer >> bpm [ +"return the duration of 1/16th at self bpm" +^ ((60/ self) /4). +] + +{ #category : #'*Coypu' } +Integer >> breves [ +" returns self number of brevesin the first position of the bars" +| result | +result := (('80000000' pattern ) times: self). +result durations: 32. +^ result +] + +{ #category : #'*Coypu' } +Integer >> claveSon [ +" create an array of self size of Cuban clave Son rhytm pulses. Better if the receiever is a multiple of 16." +| pattern | +pattern := #(1 0 0 1 0 0 1 0 0 0 1 0 1 0 0 0). + +^ ((0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]) asRhythm asSeq. + +] + +{ #category : #'*Coypu' } +Integer >> copiesOf: anInteger [ +" retuns an array of self size with all anInteger" +^ Array new: self withAll: anInteger. +] + +{ #category : #'*Coypu' } +Integer >> copiesOfEach: anArray [ +" retuns an array of self repetitions of each element of anArray" +| result resultSize| +result := Array new: ((anArray size) * self). +resultSize := result size. +( 0 to: (resultSize -1)) do: +[ :i | result at: (i + 1) put: (anArray at: (i // (resultSize /anArray size)) + 1)]. + +^ result +] + +{ #category : #'*Coypu' } +Integer >> dirtNoteToSpeed [ +" for SUperDirt, convert a note into speed, for sample playback" +^ 2 raisedTo: (self /12) + +] + +{ #category : #'*Coypu' } +Integer >> downbeats [ + + ^ ((1 to: self) collect: [ :i | (((i rem: 4) = 1)) asBit ]) asRhythm asSeq. +] + +{ #category : #'*Coypu' } +Integer >> gahu [ +" create an array of self size of gahu (from Ewe people) rhytm pulses. Better if used with self = 8 x anInteger " +| pattern | +pattern := #(1 0 0 1 0 0 1 0 0 0 1 0 0 0 1 0 ). + +^ ((0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]) asRhythm asSeq. + +"implement shifting to the left" +] + +{ #category : #'*Coypu' } +Integer >> into: anArray [ + +^ anArray fullScale asArray wrap: self +] + +{ #category : #'*Coypu' } +Integer >> melodyFrom: aScale [ + + | array | + array := self randomNotesFrom: aScale. + array at: 1 put: (aScale at: 1). + 2 to: array size do: [ :i | + ((array at: i) rem: 12)= 0 ifTrue: [ array at: i - 1 put: (aScale at: 6) ]. + ((array at: i) rem: 12)= 2 ifTrue: [ array at: i - 1 put: (aScale at: 5) ] ]. + "last note is mediante or dominante" + ^ array +] + +{ #category : #'*Coypu' } +Integer >> modulo: anInteger [ + + "convenience method to index arrays after incrementing" + + ^ self - 1 % anInteger + 1 +] + +{ #category : #'*Coypu' } +Integer >> pattern [ + | arrayOfCharacters s| + arrayOfCharacters := (self printStringBase: 2) asArray. + s := arrayOfCharacters size. + ^ (1 to: s) collect: [ :i | (arrayOfCharacters at: i) digitValue ] + +] + +{ #category : #'*Coypu' } +Integer >> quavers [ +"creates an Array of aNumber of values (0,1) with 1 to the position corresponding to a rhtyhm of quavers (downbeat + upbeat)" + ^ ((1 to: self) collect: [ :i | ((((i rem: 4) = 1)) | (((i rem: 4) = 3)) ) asBit ]) asRhythm asSeq . +] + +{ #category : #'*Coypu' } +Integer >> randomDiv: aDivision [ +"return an array of size=self random values in the range of 0,1 , with a subdivision of 1/aDivision steps with 2 decimal places" + | r array| + r := Random new. + array := ( 0 to: 1 by: (( 1/ aDivision) asFloat) ) asArray. + ^ (1 to: self) collect: [ :i | (array at: (r nextInt: (array size))) printShowingDecimalPlaces: 2 ]. +] + +{ #category : #'*Coypu' } +Integer >> randomFrom: anArray [ + + "return an array of self size of random notes from anArray" + + ^ (1 to: self) collect: [ :i | + anArray at: (Random new nextInteger: anArray size) ] +] + +{ #category : #'*Coypu' } +Integer >> randomInts: aNumber [ +"return an array of self size with random integers with range [0, aNumber]" +| floatsArray result | +floatsArray := (self randoms) * aNumber. +result := floatsArray collect: [ :i | i rounded ]. +^ result +] + +{ #category : #'*Coypu' } +Integer >> randomNotes: anArray [ + + "answer an Array of size=self with random note numbers between " + + | max min range | + min := anArray at: 1. + max := anArray at: 2. + range := 1 + max - min. + ^ (1 to: self) collect: [ :i | + (Random new nextInteger: range) + min - 1 ] +] + +{ #category : #'*Coypu' } +Integer >> randomNotesFrom: anArray [ + + "return an array of self size of random notes from anArray" + + ^ (1 to: self) collect: [ :i | + anArray at: (Random new nextInteger: anArray size) ] +] + +{ #category : #'*Coypu' } +Integer >> randomNotesFrom: anArray octaves: aNumber [ + + "return an array of self size of a random note from anArray + a random octave between 0 and aNumber" + + ^ (1 to: self) collect: [ :i | + (anArray at: (Random new nextInteger: anArray size)) + + ((Random new nextInteger: aNumber + 1) - 1 * 12) ] +] + +{ #category : #'*Coypu' } +Integer >> randomSamplesFromFolder: aStringForAFolderOfSamples [ +"return a Sequencer witha random number (between 2 and 512) of trigs and of samples fro the selected folder" +| randIndex folder folderSize| + randIndex := Random new nextIntegerBetween: 2 and: 512. + folder := SuperDirt samplesFolder / aStringForAFolderOfSamples . + folderSize := folder asFileReference allChildren size. + ^ self randomTrigs sound: aStringForAFolderOfSamples ; dirtNotes: (randIndex randomInts: folderSize) . +] + +{ #category : #'*Coypu' } +Integer >> randomSamplesFromFolder: aStringForAFolderOfSamples withProbability: aProbability [ +"return a Sequencer witha random number (between 2 and 512) of trigs and of samples fro the selected folder" +| randIndex folder folderSize| + randIndex := Random new nextIntegerBetween: 2 and: 512. + folder := SuperDirt samplesFolder / aStringForAFolderOfSamples . + folderSize := folder asFileReference allChildren size. + ^ (self randomTrigsWithProbability: aProbability ) sound: aStringForAFolderOfSamples ; dirtNotes: (randIndex randomInts: folderSize) . +] + +{ #category : #'*Coypu' } +Integer >> randomTrigs [ +"generates an Array of random 0s and 1s of size: aNumber" + | r | + r := Random new. + ^ ((1 to: self) collect: [ :i | (r next ) rounded]) asRhythm asSeq . +] + +{ #category : #'*Coypu' } +Integer >> randomTrigsWithProbability: anIntegerProbability [ +"generates an Array of random 0s and 1s of size: aNumber with a probability of having a trig of anIntegerProbability " + | r rytm| + rytm:= Array new: self. + r := Random new. + ( 1 to: self) do: [ :i| (r next) < (anIntegerProbability / 100) + ifTrue: [ rytm at: i put: 1 ] ifFalse: [ rytm at: i put: 0 ] + ]. + +^ rytm asRhythm asSeq +] + +{ #category : #'*Coypu' } +Integer >> randomWalksOn: anArrayOfNotes [ + + "random walk inside an array of notes" + + | start nix randomWalker r | + start := anArrayOfNotes at: + ((anArrayOfNotes size / 2) rounded). + nix := anArrayOfNotes indexOf: start. + randomWalker := (1 to: self) collect: [ :i | + r := Random new next. + r > 0.5 + ifTrue: [ + nix := (nix + 1 ) min: (anArrayOfNotes size) ] + ifFalse: [ + nix := (nix -1) max: 1 ]. + + + anArrayOfNotes at: nix ]. + ^ randomWalker +] + +{ #category : #'*Coypu' } +Integer >> randoms [ +"generates an Array of random values in the range (0,1) of size: aNumber, whith only 2 decimal places" + | r | + r := Random new. + ^ (1 to: self) collect: [ :i | ((r next ) printShowingDecimalPlaces: 2) asNumber]. +] + +{ #category : #'*Coypu' } +Integer >> rumba [ +" create an array of self size of Cuban rumba rhytm pulses. Better if used with self = 16 x anInteger " +| pattern | +pattern := #(1 0 0 1 0 0 0 1 0 0 1 0 1 0 0 0 ). + +^ ((0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]) asRhythm asSeq. + +"implement shifting to the left" +] + +{ #category : #'*Coypu' } +Integer >> semibreves [ +" returns self number of semibrevesin the first position of the bars" +| result | +result := (('8000' hexBeat ) times: self) . +result durations: 16. +^ result +] + +{ #category : #'*Coypu' } +Integer >> semiquavers [ + +^ (Array new: self withAll: 1) asRhythm asSeq. +] + +{ #category : #'*Coypu' } +Integer >> semitonesToSpeed [ + + "Convert semitones to playback speed factor" + | baseFactor semitoneFactor | + + baseFactor := (2 raisedTo: (1 / 12)). "Twelfth root of 2" + + "Calculate the factor for the given number of semitones" + semitoneFactor := baseFactor raisedTo: self. + + ^semitoneFactor. +] + +{ #category : #'*Coypu' } +Integer >> shiko [ +" create an array of self size of shiko (West-African / Caribbean) rhytm pulses. Better if used with self = 16 x anInteger " +| pattern | +pattern := #(1 0 0 0 1 0 1 0 0 0 1 0 1 0 0 0). + +^ ((0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]) asRhythm asSeq. + +"implement shifting to the left" +] + +{ #category : #'*Coypu' } +Integer >> sikyi [ +" create an array of self size of sikyi (by Ashanti people of Ghana) rhytm pulses. Better if the receiever is a multiple of 8." +| pattern | +pattern := #(0 0 0 1 0 1 0 1). + +^ ((0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]) asRhythm asSeq. + +] + +{ #category : #'*Coypu' } +Integer >> soukous [ +" create an array of self size of Central Africa soukous rhytm pulses. Better if used with self = 8 x anInteger " +| pattern | +pattern := #(1 0 0 1 0 0 1 0 0 0 1 1 0 0 0 0 ). + +^ ((0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]) asRhythm asSeq. + +"implement shifting to the left" +] + +{ #category : #'*Coypu' } +Integer >> spreadOver: anInteger [ + +| result | +result := (0 to: self by: self/anInteger) asArray collect: [:i | i asFloat]. +^ result +] + +{ #category : #'*Coypu' } +Integer >> toSCAsGate [ + + " send an OSC message to superCollider that mimics the behaviour oa MIDI note on message, note is held for a default duration of 2 seconds" + + | localAddress | + localAddress := '127.0.0.1'. + + + (OSCMessage for: { + 15. + self. + 'gate'. + 1 }) sendToAddressString: '127.0.0.1' port: 57110. + (Delay forSeconds: 1) wait. + +"note-Off" + (OSCMessage for: { + 15. + self. + 'gate'. + 0 }) sendToAddressString: '127.0.0.1' port: 57110 +] + +{ #category : #'*Coypu' } +Integer >> toSCAsGate: aDurationInSeconds note: aNoteNumber [ + + "send a note-on MIDI-like message to supercollider via OSC" + +[ (OSCMessage for: { + 15. + self. + 'note'. + aNoteNumber }) sendToAddressString: '127.0.0.1' port: 57110. + (OSCMessage for: { + 15. + self. + 'gate'. + 1 }) sendToAddressString: '127.0.0.1' port: 57110. + + (Delay forSeconds: aDurationInSeconds) wait. + + "note-Off" + (OSCMessage for: { + 15. + self. + 'gate'. + 0 }) sendToAddressString: '127.0.0.1' port: 57110. + ] fork +] + +{ #category : #'*Coypu' } +Integer >> tresillo [ +" create an array of self size of tresillo rhytm pulses. Better if used with self = 8 x anInteger " +| pattern newArray rhythm | + +pattern := #(1 0 0 1 0 0 1 0 ). + +newArray := (0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]. +rhythm := Rhythm new: (newArray size). +(1 to: (rhythm size)) do: [ :i | rhythm at: i put: (newArray at: i) ]. +^ rhythm asSeq. + +" +| pattern | +pattern := #(1 0 0 1 0 0 1 0 ). + +^ (0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]. +" +"implement shifting to the left" +] + +{ #category : #'*Coypu' } +Integer >> trigs [ + + " retuns a Sequencer of gates size = self with all ones - same as semiquavers" +^ (Array new: self withAll: 1) asRhythm asSeq. +] + +{ #category : #'*Coypu' } +Integer >> trueAksak [ +" create an array of self size of Balcan aksak rhytm pulses. Better if used with self = 13 x anInteger " +| pattern | +pattern := #(1 0 1 0 1 0 1 0 0 1 0 1 0). + +^ ((0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]) asRhythm asSeq. + +"implement shifting to the left" +] + +{ #category : #'*Coypu' } +Integer >> tumbao [ +" create an array of self size of habanera/tumbao rhytm pulses. Better if the receiever is a multiple of eight." +| pattern rhythm | +pattern := #(1 0 0 1 1 0 1 0 ). + +rhythm := ((0 to: (self -1) ) collect: [ :i | pattern at: ((i rem: pattern size) + 1) ]) asRhythm asSeq. +^ rhythm. + +] + +{ #category : #'*Coypu' } +Integer >> upbeats [ +"creates an Array of size=self of values (0,1) with 1 to the position corresponding to a 4/4 upbeat rhythm" + ^ ((1 to: self) collect: [ :i | (((i rem: 4) = 3)) asBit ]) asRhythm asSeq. +] + +{ #category : #'*Coypu' } +Integer >> zeros [ + " retuns a Sequencer of gates size = self with all zeros / to create long rests " +^ (Array new: self withAll: 0) asRhythm asSeq asSeq. +] diff --git a/src/Coypu/KeyboardToKyma.class.st b/src/Coypu/KeyboardToKyma.class.st new file mode 100644 index 0000000..8861e5f --- /dev/null +++ b/src/Coypu/KeyboardToKyma.class.st @@ -0,0 +1,32 @@ +Class { + #name : #KeyboardToKyma, + #superclass : #KeyboardToOSC, + #category : #'Coypu-KeyboardControl' +} + +{ #category : #initialization } +KeyboardToKyma >> initialize [ +super initialize. +] + +{ #category : #'event handling' } +KeyboardToKyma >> keyDown: anEvent [ + + +key := self keyAsMIDINoteNumber: anEvent. +"key = 60 ifTrue: [ self color: Color yellow ]." +" if the key pressed corresponds to a MIDI note" + +((key ~= -1) & isHandlingKeyDown) ifTrue: [ address toKymaAsNoteOn: key + (octave * 12)] ifFalse:[nil] +] + +{ #category : #'event handling' } +KeyboardToKyma >> keyUp: anEvent [ + + +key := self keyAsMIDINoteNumber: anEvent. +"key = 60 ifTrue: [ self color: Color yellow ]." +" if the key pressed corresponds to a MIDI note" + +((key ~= -1) & isHandlingKeyDown) ifTrue: [ address toKymaAsNoteOff: key + (octave * 12)] ifFalse:[nil] +] diff --git a/src/Coypu/KeyboardToKymaDrums.class.st b/src/Coypu/KeyboardToKymaDrums.class.st new file mode 100644 index 0000000..6effd11 --- /dev/null +++ b/src/Coypu/KeyboardToKymaDrums.class.st @@ -0,0 +1,128 @@ +" +Experimental class to control Kyma gates from tehe Keyboard, typing inside a window +" +Class { + #name : #KeyboardToKymaDrums, + #superclass : #SimpleSwitchMorph, + #instVars : [ + 'arrayOfAddresses', + 'isHandlingKeyDown', + 'keysDictionary' + ], + #category : #'Coypu-KeyboardControl' +} + +{ #category : #'instance creation' } +KeyboardToKymaDrums class >> newWithAddresses: anArrayofAddresses [ + +| labels | + +labels := String new. +(1 to: anArrayofAddresses size) do: [:i | labels := labels, (anArrayofAddresses at: i), ' ' ]. + ^ (self new) + createKeyDictionary; + label: labels; + arrayOfAddresses: anArrayofAddresses ; + yourself +] + +{ #category : #accessing } +KeyboardToKymaDrums >> arrayOfAddresses [ +^ arrayOfAddresses +] + +{ #category : #accessing } +KeyboardToKymaDrums >> arrayOfAddresses: anArrayOfAddresses [ +"set the array of addresses to control drums via OSC" +arrayOfAddresses := anArrayOfAddresses +] + +{ #category : #'as yet unclassified' } +KeyboardToKymaDrums >> createKeyDictionary [ + +keysDictionary := Dictionary new. +keysDictionary at: $a put: 1. +keysDictionary at: $s put: 2. +keysDictionary at: $d put: 3. +keysDictionary at: $f put: 4. +keysDictionary at: $g put: 5. +keysDictionary at: $h put: 6. +keysDictionary at: $j put: 7. +keysDictionary at: $k put: 8. + + +^ keysDictionary +] + +{ #category : #'event handling' } +KeyboardToKymaDrums >> handlesKeyDown: anEvent [ + +^ true + +] + +{ #category : #'event handling' } +KeyboardToKymaDrums >> handlesKeyUp: anEvent [ + +^ true + +] + +{ #category : #'event handling' } +KeyboardToKymaDrums >> handlesMouseDown: anEvent [ +^ true +] + +{ #category : #initialization } +KeyboardToKymaDrums >> initialize [ +super initialize . +"not handling keydon if not clicked" +isHandlingKeyDown := false. +"create basic keysDictionary for US keyboardkeyboardFocusChange" +self createKeyDictionary . +] + +{ #category : #'event handling' } +KeyboardToKymaDrums >> keyDown: anEvent [ + + + +| key index| +key := anEvent keyCharacter. +index := (keysDictionary at: key) min: arrayOfAddresses size. + +( (keysDictionary includesKey: key) & isHandlingKeyDown) ifTrue: +[ Transcript show: (arrayOfAddresses at: index). (arrayOfAddresses at: index) toKymaAsNoteOn: 60] +ifFalse:[nil] + +] + +{ #category : #'event handling' } +KeyboardToKymaDrums >> keyUp: anEvent [ + + + +| key | +key := anEvent keyCharacter. + + +( (keysDictionary includesKey: key) & isHandlingKeyDown) ifTrue: +[ Transcript show: (arrayOfAddresses at: (keysDictionary at: key)). (arrayOfAddresses at: (keysDictionary at: key)) toKymaAsNoteOff: 60] +ifFalse:[nil] + +] + +{ #category : #accessing } +KeyboardToKymaDrums >> keysDictionary [ +^ keysDictionary . +] + +{ #category : #'event handling' } +KeyboardToKymaDrums >> mouseDown: anEvent [ +super mouseDown: anEvent. + +isHandlingKeyDown := isHandlingKeyDown not. +isHandlingKeyDown ifTrue: [ anEvent hand newKeyboardFocus: self. ] ifFalse: [ anEvent hand releaseKeyboardFocus: self ]. + + +] diff --git a/src/Coypu/KeyboardToOSC.class.st b/src/Coypu/KeyboardToOSC.class.st new file mode 100644 index 0000000..b3310b7 --- /dev/null +++ b/src/Coypu/KeyboardToOSC.class.st @@ -0,0 +1,225 @@ +" +A bodered morph thats when mouse is over sends OSC messages in a MIDI keyboard fashion to the desired OSC adddress. +" +Class { + #name : #KeyboardToOSC, + #superclass : #SimpleSwitchMorph, + #instVars : [ + 'noteArray', + 'noteDictionary', + 'address', + 'octave', + 'isHandlingKeyDown', + 'key', + 'mouseWheelValue' + ], + #category : #'Coypu-KeyboardControl' +} + +{ #category : #'instance creation' } +KeyboardToOSC class >> newWithAddress: anAddress [ + + ^ (self new) + label: anAddress ; + address: anAddress ; + yourself +] + +{ #category : #accessing } +KeyboardToOSC >> address [ +^ address. +] + +{ #category : #accessing } +KeyboardToOSC >> address: aStringAddress [ +address := aStringAddress. +^ aStringAddress. +] + +{ #category : #'as yet unclassified' } +KeyboardToOSC >> fillNoteListDictionary [ + + +noteDictionary := Dictionary new. +noteDictionary at: #a put: 60. +noteDictionary at: #w put: 61. +noteDictionary at: #s put: 62. +noteDictionary at: #e put: 63. +noteDictionary at: #d put: 64. +noteDictionary at: #r put: 65. +noteDictionary at: #f put: 66. +noteDictionary at: #t put: 67. +noteDictionary at: #g put: 68. +noteDictionary at: #y put: 69. +noteDictionary at: #h put: 70. +noteDictionary at: #j put: 71. +noteDictionary at: #k put: 72. + +"the note Array, 2 octaves chromatic keyboard" +noteArray := Array new: 128. +"lower octave" +noteArray at: $a asciiValue put: 35. "B" +noteArray at: $z asciiValue put: 36. "C" +noteArray at: $s asciiValue put: 37. +noteArray at: $x asciiValue put: 38. "D" +noteArray at: $d asciiValue put: 39. +noteArray at: $c asciiValue put: 40. "E" +noteArray at: $v asciiValue put: 41. "F" +noteArray at: $g asciiValue put: 42. +noteArray at: $b asciiValue put: 43. +noteArray at: $h asciiValue put: 44. +noteArray at: $n asciiValue put: 45. +noteArray at: $j asciiValue put: 46. +noteArray at: $m asciiValue put: 47. +noteArray at: $, asciiValue put: 48. +noteArray at: $k asciiValue put: 49. +noteArray at: $. asciiValue put: 50. +noteArray at: $l asciiValue put: 51. +noteArray at: $. asciiValue put: 52. +noteArray at: $/ asciiValue put: 53. +"upper octave" +noteArray at: $q asciiValue put: 48. "C" +noteArray at: $2 asciiValue put: 49. +noteArray at: $w asciiValue put: 50. "D" +noteArray at: $3 asciiValue put: 51. +noteArray at: $e asciiValue put: 52. "E" +noteArray at: $r asciiValue put: 53. "F" +noteArray at: $5 asciiValue put: 54. +noteArray at: $t asciiValue put: 55. "G" +noteArray at: $6 asciiValue put: 56. +noteArray at: $y asciiValue put: 57. "A" +noteArray at: $7 asciiValue put: 58. +noteArray at: $u asciiValue put: 59. +noteArray at: $i asciiValue put: 60. "C" +noteArray at: $9 asciiValue put: 61. +noteArray at: $o asciiValue put: 62. "D" +noteArray at: $0 asciiValue put: 63. +noteArray at: $p asciiValue put: 64. "E" +noteArray at: $[ asciiValue put: 65. "F" +noteArray at: $= asciiValue put: 65. "F" +noteArray at: $] asciiValue put: 67. +] + +{ #category : #'event handling' } +KeyboardToOSC >> handlesKeyDown: anEvent [ +| debug kv | +"keyboard up arrow = 30, keyboard down arrow = 31" +debug := self keyAsMIDINoteNumber: anEvent. +key := self keyAsMIDINoteNumber: anEvent. + . +kv := anEvent keyValue. +(kv = 30) ifTrue: [ octave := octave + 1] ifFalse: [(kv = 31) ifTrue: [octave := octave -1] ifFalse: [ nil]]. + Transcript show: (key + (octave * 12)) asString, ' '. +^ true +] + +{ #category : #'event handling' } +KeyboardToOSC >> handlesKeyUp: anEvent [ + +^ true +] + +{ #category : #'event handling' } +KeyboardToOSC >> handlesMouseDown: anEvent [ +^ true +] + +{ #category : #'event handling' } +KeyboardToOSC >> handlesMouseOver: anEvent [ +^ true +] + +{ #category : #'event handling' } +KeyboardToOSC >> handlesMouseWheel: anEvent [ +^ true +] + +{ #category : #adding } +KeyboardToOSC >> initialize [ +super initialize. + +"key is 60 at the begiing / middle C" +key := 60. +"not handling keydon if not clicked" +isHandlingKeyDown := false. +"dummy OSC address" +address := 'nothing'. +" octave" +octave := 0. + +"initial mouseWheelValue" +mouseWheelValue := 0. + +self fillNoteListDictionary . +] + +{ #category : #'as yet unclassified' } +KeyboardToOSC >> keyAsMIDINoteNumber: anEvent [ +"if the key pressed corresponds to a MIDI note, returns the MIDI note otherwise returns -1" +| keyPressed result | +"implementation with noteDictionary +keyPressed := anEvent keyCharacter asSymbol. +(noteDictionary includesKey: keyPressed) ifTrue: [ result := noteDictionary at: keyPressed ] ifFalse: [ result := -1]. +" + +"implementation with noteArray" +keyPressed := anEvent keyCharacter asciiValue . + ((keyPressed ~= 0) & (keyPressed < 128)) ifTrue: [ ((noteArray at: keyPressed ) isNotNil ) ifTrue: [ result := noteArray at: keyPressed ] ifFalse: [ result := -1 ]] ifFalse: [result := -1]. +^ result + + +] + +{ #category : #'event handling' } +KeyboardToOSC >> keyDown: anEvent [ + + +key := self keyAsMIDINoteNumber: anEvent. +"key = 60 ifTrue: [ self color: Color yellow ]." +" if the key pressed corresponds to a MIDI note" +((key ~= -1) & isHandlingKeyDown) ifTrue: [ address toLocalAsGate: 0.1 note: key + (octave * 12)] ifFalse:[nil] +] + +{ #category : #'event handling' } +KeyboardToOSC >> mouseDown: anEvent [ +super mouseDown: anEvent. + +isHandlingKeyDown := isHandlingKeyDown not. +isHandlingKeyDown ifTrue: [ anEvent hand newKeyboardFocus: self. ] ifFalse: [ anEvent hand releaseKeyboardFocus: self ] +] + +{ #category : #'event handling' } +KeyboardToOSC >> mouseEnter: anEvent [ +"anEvent hand newKeyboardFocus: self" +] + +{ #category : #'event handling' } +KeyboardToOSC >> mouseLeave: anEvent [ +anEvent hand newKeyboardFocus: self +] + +{ #category : #'event handling' } +KeyboardToOSC >> mouseWheel: anEvent [ + +anEvent isUp ifTrue: [ mouseWheelValue := mouseWheelValue -1. mouseWheelValue toKyma: 'SawDetune'] ifFalse: [ nil ]. +anEvent isDown ifTrue: [ mouseWheelValue := mouseWheelValue +1. mouseWheelValue toKyma: 'SawDetune'] ifFalse: [ nil ]. +] + +{ #category : #accessing } +KeyboardToOSC >> noteDictionary [ +^ noteDictionary +] + +{ #category : #'as yet unclassified' } +KeyboardToOSC >> openInWindow [ + + | buttonWidth buttonHeight positionLeft positionTop | + + buttonWidth := 200. + buttonHeight := 200. + positionLeft := 627. + positionTop := 354. + + ^ ((self openInWindowLabeled: self sound) position: + positionLeft @ positionTop) extent: buttonWidth @ buttonHeight +] diff --git a/src/Coypu/KeyboardToSuperDirt.class.st b/src/Coypu/KeyboardToSuperDirt.class.st new file mode 100644 index 0000000..c351f90 --- /dev/null +++ b/src/Coypu/KeyboardToSuperDirt.class.st @@ -0,0 +1,47 @@ +" +Play the slound on SuperDirt with the computer keyboard +" +Class { + #name : #KeyboardToSuperDirt, + #superclass : #KeyboardToOSC, + #instVars : [ + 'sound' + ], + #category : #'Coypu-KeyboardControl' +} + +{ #category : #'instance creation' } +KeyboardToSuperDirt class >> new: aStringWithASound [ + + ^ (self new) + sound: aStringWithASound ; + color: Color darkGray ; + onColor: Color white; + offColor: Color darkGray ; + label: aStringWithASound ; + yourself +] + +{ #category : #'event handling' } +KeyboardToSuperDirt >> keyDown: anEvent [ + +| bundle | +key := (self keyAsMIDINoteNumber: anEvent) -48. +"key = 60 ifTrue: [ self color: Color yellow ]." +" if the key pressed corresponds to a MIDI note" +bundle := OSCBundle for: {OSCMessage for: { '/dirt/play' . 's' . self sound . 'n' . key}}. + +((key ~= -1) & isHandlingKeyDown) ifTrue: [ bundle sendToAddressString: '127.0.0.1' port: 57120] ifFalse:[nil] +] + +{ #category : #accessing } +KeyboardToSuperDirt >> sound [ + + ^ sound +] + +{ #category : #accessing } +KeyboardToSuperDirt >> sound: anObject [ + + sound := anObject +] diff --git a/src/Coypu/LiveCodingTests.class.st b/src/Coypu/LiveCodingTests.class.st new file mode 100644 index 0000000..99964d4 --- /dev/null +++ b/src/Coypu/LiveCodingTests.class.st @@ -0,0 +1,141 @@ +Class { + #name : #LiveCodingTests, + #superclass : #TestCase, + #category : #'Coypu-Tests' +} + +{ #category : #tests } +LiveCodingTests >> testArrayIntoScale [ + + | indexes newNotes | + indexes := #( -1 0 1 2 3 4 5 6 7 8 9 10 20 ). + + newNotes := indexes inScale: (Scale chromatic: 60). + self + assert: newNotes + equals: #( 59 0 60 61 62 63 64 65 66 67 68 69 79) +] + +{ #category : #tests } +LiveCodingTests >> testArrayWrap [ + + | array | + array := (1 to: 10) asDirtArray. + self assert: (array wrap: 11) equals: 1 +] + +{ #category : #tests } +LiveCodingTests >> testFullScale [ + + | result | + result := (Scale chromatic: 60) fullScale. + self assert: result equals: (0 to: 127) asDirtArray +] + +{ #category : #tests } +LiveCodingTests >> testIntegerAsArray [ + + self assert: 16 asArrayOfInts equals: (1 to: 16) asDirtArray +] + +{ #category : #tests } +LiveCodingTests >> testMultiplyCharacter [ + +self assert: $h*3 equals: 'h h h' +] + +{ #category : #tests } +LiveCodingTests >> testMultiplyString [ + +self assert: 'h'*3 equals: 'h h h' +] + +{ #category : #tests } +LiveCodingTests >> testSawtoothTable [ + +| array result increment|. + +array := (0 to: 1 by: 1/63). +result := 4 bars saw. + +self assert: array equals: result. +] + +{ #category : #tests } +LiveCodingTests >> testStringAsDirtNotes [ + + + +| case | + +case := '0 2*3 ~ [5 ~*2]' asDirtNotes. + +self assert: (case dirtMessage at: 'n') equals: #(0 2 2 2 5 5). +self assert: case gates equals: #(1 1 1 1 0 1 0 1 0) + + + + + + + +] + +{ #category : #tests } +LiveCodingTests >> testStringEveryInt [ + +self assert: 'bd' / 8 equals: 'bd ~ ~ ~ ~ ~ ~ ~' +] + +{ #category : #tests } +LiveCodingTests >> testStringForDirt [ + + | string gates seq soundsPattern samplesIndex | + string := 'bd:1 sd cp:2 ~'. + gates := #(1 1 1 0). + soundsPattern := #( 'bd' 'sd' 'cp' ). + samplesIndex := #( 1 0 2 ). + seq := string asDirtSounds. + +self assert: seq gates equals: gates. + self assert: seq soundPattern equals: soundsPattern. + self assert: seq samplesIndex equals: samplesIndex +] + +{ #category : #tests } +LiveCodingTests >> testStringOfSoundsForDirt [ + + + +| case | + +case := 'sd cp*3 ~ [bd ~*2]' asDirtSounds. + +self assert: (case dirtMessage at: 's') equals: #(sd cp cp cp bd bd). +self assert: case gates equals: #(1 1 1 1 0 1 0 1 0) + + + + + + + +] + +{ #category : #tests } +LiveCodingTests >> testStringWInWords [ + +self assert: 'the needs of the many' inWords equals: #('the' 'needs' 'of' 'the' 'many') +] + +{ #category : #tests } +LiveCodingTests >> testTriangleTable [ + + | array up down result | + up := 0 to: 1 by: 1 / 31. + down := up reverse. + array := up , down. + result := 4 bars triangle. + + self assert: array equals: result +] diff --git a/src/Coypu/MIDIReceiver.class.st b/src/Coypu/MIDIReceiver.class.st new file mode 100644 index 0000000..c7f1676 --- /dev/null +++ b/src/Coypu/MIDIReceiver.class.st @@ -0,0 +1,136 @@ +" +MIDIReceiver +Use the MIDIReceiever to receive MIDI events. +" +Class { + #name : #MIDIReceiver, + #superclass : #Object, + #instVars : [ + 'midiIn', + 'defaultInput' + ], + #category : #'Coypu-MIDI' +} + +{ #category : #accessing } +MIDIReceiver >> defaultInput [ +^ defaultInput +] + +{ #category : #accessing } +MIDIReceiver >> defaultInput: anInput [ +defaultInput := anInput. +] + +{ #category : #'as yet unclassified' } +MIDIReceiver >> initialize [ + | instance | + instance := PortMidiLibrary uniqueInstance. + self defaultInput: instance getDefaultOutputDeviceID. + self midiInput: (instance createNewPointerForStreamWithType: 'void**'). + +] + +{ #category : #'as yet unclassified' } +MIDIReceiver >> midiInput [ +^ midiIn +] + +{ #category : #'as yet unclassified' } +MIDIReceiver >> midiInput: anInputStream [ + "Sets a new MidiInput stream that is used to read incoming notes." + midiIn := anInputStream +] + +{ #category : #outputManagement } +MIDIReceiver >> openMidiInputWithDevice: aDeviceID [ + "Opens a new MidiOUT Stream with output device id aDeviceID to play notes." + ^ (aDeviceID = -1) ifFalse: + [PortMidiLibrary uniqueInstance openInputForStream: midiIn + withDevice: aDeviceID + withInputDriverInfos: nil + withBufferSize: 512 + withTimeProcedure: nil + andTimeInfos: nil + ] ifTrue: [ ^ -1 ] + + +] + +{ #category : #outputManagement } +MIDIReceiver >> openWithDevice: aDeviceID [ + "Opens a new MidiOUT Stream with output device id aDeviceID to play notes." + + ^ aDeviceID = -1 + ifFalse: [ + PortMidiLibrary uniqueInstance + openInputForStream: midiIn + withDevice: aDeviceID + withInputDriverInfos: nil + withBufferSize: 512 + withTimeProcedure: nil + andTimeInfos: nil +] + ifTrue: [ ^ -1 ] +] + +{ #category : #'as yet unclassified' } +MIDIReceiver >> readInBuffer: buffer withSize: bufferSize [ + "Calls the PortMidi C library method that read bufferSize MIDI message on aStream and stores it in buffer." +] + +{ #category : #'as yet unclassified' } +MIDIReceiver >> readIncomingMessageSize: aNumberOfEvents [ + + | instance buffer | + instance := PortMidiLibrary uniqueInstance. + buffer := PortMidiEvent new. + + [ + aNumberOfEvents timesRepeat: [ + instance advancedReadOn: self midiInput value inBuffer: buffer withSize: 1. + (instance portMidiGetStatusFromMessage: buffer message) traceCr. + (instance portMidiGetFirstDataFromMessage: buffer message) traceCr. + (instance portMidiGetSecondDataFromMessage: buffer message) traceCr ] ] + fork +] + +{ #category : #'as yet unclassified' } +MIDIReceiver >> traceIncomingMessage [ + + | instance buffer proc | + instance := PortMidiLibrary uniqueInstance. + buffer := PortMidiEvent new. + proc := [ [ + instance + advancedReadOn: self midiInput value + inBuffer: buffer + withSize: 512. + (instance portMidiGetStatusFromMessage: buffer message) + traceCr. + (instance portMidiGetFirstDataFromMessage: buffer message) + traceCr. + (instance portMidiGetSecondDataFromMessage: buffer message) + traceCr ] repeat ] fork. + ^ proc +] + +{ #category : #'as yet unclassified' } +MIDIReceiver >> trig: aDsp [ + + | instance buffer proc | + instance := PortMidiLibrary uniqueInstance. + buffer := PortMidiEvent new. + proc := [ + [ 1 > 0 ] whileTrue: [ + instance + advancedReadOn: self midiInput value + inBuffer: buffer + withSize: 512. + + aDsp setValue: ((instance portMidiGetFirstDataFromMessage: buffer message) /100) + parameter: 'TrigMe'. + (instance portMidiGetSecondDataFromMessage: buffer message) + traceCr ] ] fork. + ^ proc +] diff --git a/src/Coypu/MIDISender.class.st b/src/Coypu/MIDISender.class.st new file mode 100644 index 0000000..0522711 --- /dev/null +++ b/src/Coypu/MIDISender.class.st @@ -0,0 +1,230 @@ +" +MIDISender +Use MIDISendere to send out MIDI events + +example +mout := MIDISender new. +mout openMidiOutWithDevice: 5. +" +Class { + #name : #MIDISender, + #superclass : #Object, + #instVars : [ + 'midiOut', + 'defaultOutput', + 'instance' + ], + #category : #'Coypu-MIDI' +} + +{ #category : #'as yet unclassified' } +MIDISender >> allNotesOff [ + +1 to: 16 do: [ :i | +self sendCC: 123 withValue: 0 onChannel: i] +] + +{ #category : #initialization } +MIDISender >> closeMidiOut [ + "Close the MidiOUT stream (where the score sends notes)." + ^ PortMidiLibrary uniqueInstance portMidiCloseIOStream: midiOut value +] + +{ #category : #initialization } +MIDISender >> defaultOutput [ + "Returns the defaultOutDeviceID of the user machine if it is online." + ^ defaultOutput +] + +{ #category : #initialization } +MIDISender >> defaultOutput: aDevice [ + "Sets the new default output for the score." + defaultOutput := aDevice +] + +{ #category : #initialization } +MIDISender >> initialize [ + + self instance: PortMidiLibrary uniqueInstance. + self defaultOutput: instance getDefaultOutputDeviceID. + self midiOut: (instance createNewPointerForStreamWithType: 'void**'). + + +] + +{ #category : #accessing } +MIDISender >> instance [ +^ instance +] + +{ #category : #accessing } +MIDISender >> instance: aPortMIDIInstance [ +instance := PortMidiLibrary uniqueInstance. +] + +{ #category : #initialization } +MIDISender >> midiOut [ + "Gets the MidiOUT stream that is used to write the melody." + ^ midiOut +] + +{ #category : #initialization } +MIDISender >> midiOut: anOutputStream [ + "Sets a new MidiOUT stream that is used to write the melody." + midiOut := anOutputStream +] + +{ #category : #initialization } +MIDISender >> openWithDevice: aDeviceID [ + "Opens a new MidiOUT Stream with output device id aDeviceID to play notes. + aDeviceID is an integer. PortMidi traceAllDevices / displays all available devices + " + + self instance: PortMidiLibrary uniqueInstance. + + "assign the MIDISender to the PerformerMIDI" + PerformerMIDI midiOut: self. + + self midiOut: (instance createNewPointerForStreamWithType: 'void**'). + ^ (aDeviceID = -1) ifFalse: + [instance openOutputForStream: midiOut + withDevice: aDeviceID + withOutputDriverInfos: nil + withBufferSize: 512 + withTimeProcedure: nil + withTimeInfos: nil + andLatency: 0 ] ifTrue: [ ^ -1 ] +] + +{ #category : #initialization } +MIDISender >> playChord: aNoteNumber type: aChordType onChannel: aMIDIChannel duration: aDurationInSeconds [ + + " play aChord of aChordType with root aNoteNumber on the selected channel with a fixed velocity of 100" + + | noteOn noteOff chordOn chordOff mout | + instance := self instance. + + noteOn := 144 + (aMIDIChannel - 1). + noteOff := 128 + (aMIDIChannel - 1). + chordOn := Array new: aChordType size. + chordOff := Array new: aChordType size. + + mout := self. + " create the chord" + + + (1 to: aChordType size) do: [ :i | + chordOn at: i put: (PortMidi + message: noteOn + and: aNoteNumber + (aChordType at: i) + and: 100). + chordOff at: i put: (PortMidi + message: noteOff + and: aNoteNumber + (aChordType at: i) + and: 100) ]. + + [ + (1 to: aChordType size) do: [ :i | + (aNoteNumber + (aChordType at: i)) traceCr. + instance + writeShortOn: mout midiOut value + when: 0 + theMessage: (chordOn at: i) ]. + + (Delay forSeconds: aDurationInSeconds) wait. + + (1 to: aChordType size) do: [ :i | + instance + writeShortOn: mout midiOut value + when: 0 + theMessage: (chordOff at: i) ] ] fork +] + +{ #category : #'as yet unclassified' } +MIDISender >> playDrum: aNoteNumber onChannel: aMIDIChannel [ +" play aNoteNumber on channel aChannel with no corresponding noteOff - intended for drums" +| instance noteOn channel | +instance := PortMidiLibrary uniqueInstance . +channel := aMIDIChannel -1. +noteOn := PortMidi message: 144 + channel and: aNoteNumber and: 100. + +instance writeShortOn: self midiOut value when: 0 theMessage: noteOn. + +^ 'Drum was played' + + +] + +{ #category : #initialization } +MIDISender >> playNote: aNoteNumber onChannel: aMIDIChannel [ +" play aNoteNumber on channel aChannel for a standard duration of 250 ms" +| instance noteOn noteOff channel | +instance := PortMidiLibrary uniqueInstance . +channel := aMIDIChannel -1. +noteOn := PortMidi message: 144 + aMIDIChannel and: aNoteNumber and: 100. +noteOff := PortMidi message: 128 + aMIDIChannel and: aNoteNumber and: 100. +instance writeShortOn: self midiOut value when: 0 theMessage: noteOn. +(Delay forSeconds: 0.05) wait. +instance writeShortOn: self midiOut value when: 0 theMessage: noteOff. +^ 'note was played' + + +] + +{ #category : #initialization } +MIDISender >> playNote: aNoteNumber onChannel: aMIDIChannel duration: aDurationInSeconds [ + + " play aNoteNumber on channel aChannel for aDurationInSeconds" + + | noteOn noteOff channel | + channel := aMIDIChannel - 1. + noteOn := PortMidi + message: 144 + channel + and: aNoteNumber + and: 100. + noteOff := PortMidi + message: 128 + channel + and: aNoteNumber + and: 100. + [ + instance writeShortOn: self midiOut value when: 0 theMessage: noteOn. + (Delay forSeconds: aDurationInSeconds) wait. + instance writeShortOn: self midiOut value when: 0 theMessage: noteOff ] + fork +] + +{ #category : #'as yet unclassified' } +MIDISender >> sendCC: aCCNumber withValue: aValue onChannel: aMIDIChannel [ +" play aNoteNumber on channel aChannel with no corresponding noteOff - intended for drums" +| instance cChange channel | +instance := PortMidiLibrary uniqueInstance . +channel := aMIDIChannel -1. +cChange := PortMidi message: 176 + channel and: aCCNumber and: aValue. + +instance writeShortOn: self midiOut value when: 0 theMessage: cChange. + + + + +] + +{ #category : #'as yet unclassified' } +MIDISender >> sendStart [ + + " send Transport start message to exteral MIDI device" + + | startMsg clockMsg | + startMsg := PortMidi message: 250 and: 0 and: 0. + clockMsg := PortMidi message: 248 and: 0 and: 0. + + instance + writeShortOn: self midiOut value + when: 0 + theMessage: startMsg. + + instance + writeShortOn: self midiOut value + when: 0 + theMessage: clockMsg. + +] diff --git a/src/Coypu/ManifestCoypu.class.st b/src/Coypu/ManifestCoypu.class.st new file mode 100644 index 0000000..5ad2055 --- /dev/null +++ b/src/Coypu/ManifestCoypu.class.st @@ -0,0 +1,32 @@ +Class { + #name : #ManifestCoypu, + #superclass : #PackageManifest, + #category : #'Coypu-Manifest' +} + +{ #category : #'code-critics' } +ManifestCoypu class >> ruleCollectionProtocolRuleV1FalsePositive [ + ^ #(#(#(#RGMethodDefinition #(#EsugVisual #visualizeAlanSpeech #false)) #'2022-08-05T10:59:38.794301+02:00') #(#(#RGClassDefinition #(#EsugVisual)) #'2022-08-05T10:59:46.630182+02:00') ) +] + +{ #category : #'code-critics' } +ManifestCoypu class >> ruleGuardingClauseRuleV1FalsePositive [ + ^ #(#(#(#RGMethodDefinition #(#Performance #mute: #false)) #'2022-08-06T10:51:34.471387+02:00') ) +] + +{ #category : #'code-critics' } +ManifestCoypu class >> ruleReGuardClauseRuleV1FalsePositive [ + ^ #(#(#(#RGMethodDefinition #(#Performance #mute: #false)) #'2022-08-06T10:51:48.618469+02:00') ) +] + +{ #category : #'code-critics' } +ManifestCoypu class >> ruleRefersToClassRuleV1FalsePositive [ + + + ^ #(#(#(#RGMetaclassDefinition #(#'FaderWindow class' #FaderWindow)) #'2023-08-23T14:36:52.925452+02:00') ) +] + +{ #category : #'code-critics' } +ManifestCoypu class >> ruleUnoptimizedToDoRuleV1FalsePositive [ + ^ #(#(#(#RGMethodDefinition #(#MIDISender #playChord:type:onChannel:duration: #false)) #'2022-06-27T11:28:11.059804+02:00') ) +] diff --git a/src/Coypu/MidiMessageParseTests.class.st b/src/Coypu/MidiMessageParseTests.class.st new file mode 100644 index 0000000..264ba97 --- /dev/null +++ b/src/Coypu/MidiMessageParseTests.class.st @@ -0,0 +1,11 @@ +Class { + #name : #MidiMessageParseTests, + #superclass : #TestCase, + #category : #'Coypu-Tests' +} + +{ #category : #tests } +MidiMessageParseTests >> testIsVoiceMessage [ + +self assert: 16r90 isVoiceMessage equals: true +] diff --git a/src/Coypu/NoneVisualization.class.st b/src/Coypu/NoneVisualization.class.st new file mode 100644 index 0000000..9e1bbca --- /dev/null +++ b/src/Coypu/NoneVisualization.class.st @@ -0,0 +1,21 @@ +Class { + #name : #NoneVisualization, + #superclass : #Object, + #category : #'Coypu-Graph' +} + +{ #category : #rendering } +NoneVisualization >> showSound: soundName withNote: note [ + + "Do nothing" + + +] + +{ #category : #initialize } +NoneVisualization >> startWith: bpm [ + + "Do nothing" + + +] diff --git a/src/Coypu/Number.extension.st b/src/Coypu/Number.extension.st new file mode 100644 index 0000000..25c99bb --- /dev/null +++ b/src/Coypu/Number.extension.st @@ -0,0 +1,69 @@ +Extension { #name : #Number } + +{ #category : #'*Coypu' } +Number >> asDirtArray [ + "convenience method to create an array from an interger. used to send osc messages to SuperDirt" + ^ Array with: self +] + +{ #category : #'*Coypu' } +Number >> bars: aNumber [ +"use it with anInteger bpm to have a cycle of the Iteration of a duration of aNumber of bars(or a fraction)" +^ (Delay forSeconds: ((self * aNumber) /100)) wait. +] + +{ #category : #'*Coypu' } +Number >> bpm [ +"returns time in milliseconds of one bar at self bpm" +^ (60 / self) / 4 +] + +{ #category : #'*Coypu' } +Number >> delay [ +] + +{ #category : #'*Coypu' } +Number >> midiNoteToFreq [ +" convert Midi note number to frequency" + | baseFreq | + baseFreq := 440.0. "Standard tuning frequency for A4" + ^ (baseFreq * (2 raisedTo: self - 69 / 12.0)) asFloat +] + +{ #category : #'*Coypu' } +Number >> secondsInStepAt: aDuration [ +"return the number of steps at aDuration time as 1/16th of a bar which corresponding to self seconds" +^ (self / aDuration) asInteger. +] + +{ #category : #'*Coypu' } +Number >> toKyma: aString [ + "comment stating purpose of instance-side message" + "scope: class-variables & instance-variables" + | vcslabel "pacaAddress" | +" pacaAddress := NetNameResolver stringFromAddress: (NetNameResolver addressForName:'beslime-691.local'). " + + vcslabel := '/vcs/', aString, '/1'. + (OSCMessage for: { vcslabel . (self asFloat) }) sendToAddressString: pacaAddress port: 8000. +] + +{ #category : #'*Coypu' } +Number >> toLocal: aString [ + + "comment stating purpose of instance-side message" + + "scope: class-variables & instance-variables" + + | localAddress | + localAddress := '127.0.0.1'. + + + (OSCMessage for: { + ('/' , aString). + self asFloat }) sendToAddressString: localAddress port: 57120 +] + +{ #category : #'*Coypu' } +Number >> wait [ +^ (Delay forSeconds: self) wait. +] diff --git a/src/Coypu/OrderedCollection.extension.st b/src/Coypu/OrderedCollection.extension.st new file mode 100644 index 0000000..dd6bce2 --- /dev/null +++ b/src/Coypu/OrderedCollection.extension.st @@ -0,0 +1,9 @@ +Extension { #name : #OrderedCollection } + +{ #category : #'*Coypu' } +OrderedCollection >> asDirtArray [ + + +"convenience method to send OSC messages to SuperDirt" +^ self asArray +] diff --git a/src/Coypu/PLCWindow.class.st b/src/Coypu/PLCWindow.class.st new file mode 100644 index 0000000..7bcad10 --- /dev/null +++ b/src/Coypu/PLCWindow.class.st @@ -0,0 +1,16 @@ +Class { + #name : #PLCWindow, + #superclass : #SpWindow, + #category : #'Coypu-Playground' +} + +{ #category : #initialization } +PLCWindow >> defaultColor [ + + ^ Color red +] + +{ #category : #initialization } +PLCWindow >> initialize [ +super initialize +] diff --git a/src/Coypu/PLCWindowPresenter.class.st b/src/Coypu/PLCWindowPresenter.class.st new file mode 100644 index 0000000..6744b72 --- /dev/null +++ b/src/Coypu/PLCWindowPresenter.class.st @@ -0,0 +1,10 @@ +Class { + #name : #PLCWindowPresenter, + #superclass : #SpWindowPresenter, + #category : #'Coypu-Playground' +} + +{ #category : #accessing } +PLCWindowPresenter >> color [ +^ Color red +] diff --git a/src/Coypu/PacaAddress.class.st b/src/Coypu/PacaAddress.class.st new file mode 100644 index 0000000..931a7b1 --- /dev/null +++ b/src/Coypu/PacaAddress.class.st @@ -0,0 +1,36 @@ +" +Dummy class to use to look for the Paca(rana) address +" +Class { + #name : #PacaAddress, + #superclass : #Object, + #classInstVars : [ + 'address' + ], + #category : #'Coypu-Performance' +} + +{ #category : #accessing } +PacaAddress class >> address [ + + ^ address +] + +{ #category : #accessing } +PacaAddress class >> address: anObject [ + + address := anObject +] + +{ #category : #accessing } +PacaAddress class >> search [ + + + | serialNumber | + serialNumber := 'beslime-51.local'. + Smalltalk at: #pacaAddress put: (NetNameResolver stringFromAddress: + (NetNameResolver addressForName: serialNumber)). + address := NetNameResolver stringFromAddress: + (NetNameResolver addressForName: serialNumber). + ^ address +] diff --git a/src/Coypu/ParsingTests.class.st b/src/Coypu/ParsingTests.class.st new file mode 100644 index 0000000..f05655e --- /dev/null +++ b/src/Coypu/ParsingTests.class.st @@ -0,0 +1,42 @@ +Class { + #name : #ParsingTests, + #superclass : #TestCase, + #category : #'Coypu-Tests' +} + +{ #category : #tests } +ParsingTests >> testCharacterMultiplication [ + + +self assert: 'a' * 3 equals: 'a a a'. +] + +{ #category : #tests } +ParsingTests >> testMultiplyStringsInString [ + +self assert: 'bd cp:4 ~ * 2' multiplyStringsInString equals: 'bd cp:4 ~ bd cp:4 ~ ' +] + +{ #category : #tests } +ParsingTests >> testNotesFromNotatedString [ + +| seq | +seq := 8 semiquavers. + self + assert: (seq dirtNotes: '40*4 , 88*4') notes + equals: #(40 40 40 40 88 88 88 88) +] + +{ #category : #tests } +ParsingTests >> testStringsWitNRests [ + +self assert: 'bd / 4' withNRests equals: 'bd ~ ~ ~' +] + +{ #category : #tests } +ParsingTests >> testasSeqGates [ + +self assert: 'bd cp:4 ~ sd' asSeqGates equals: #(1 1 0 1). + +self assert: '0 3 4 ~' asSeqGates equals: #(1 1 1 0). +] diff --git a/src/Coypu/Performance.class.st b/src/Coypu/Performance.class.st new file mode 100644 index 0000000..b561bca --- /dev/null +++ b/src/Coypu/Performance.class.st @@ -0,0 +1,710 @@ +" +A performance is a Dictionary subclass used for live coding package. + + +" +Class { + #name : #Performance, + #superclass : #Dictionary, + #type : #variable, + #instVars : [ + 'visualization', + 'performer', + 'canvas', + 'rsGroupDictionary', + 'freq', + 'backup', + 'bpm', + 'transportStep', + 'activeProcess', + 'dirtMIDIDevice', + 'activeDSP' + ], + #classInstVars : [ + 'uniqueInstance', + 'orbit' + ], + #category : #'Coypu-Performance' +} + +{ #category : #'as yet unclassified' } +Performance class >> basic [ + + ^ self new + visualization: NoneVisualization new; + yourself +] + +{ #category : #'instance creation' } +Performance class >> new [ + +^ Error new signal: 'Performance is a singleton -- Use unique instance instead' +] + +{ #category : #accessing } +Performance class >> orbit [ + + ^ orbit +] + +{ #category : #accessing } +Performance class >> orbit: anInteger [ + + orbit := anInteger % 12 +] + +{ #category : #'instance creation' } +Performance class >> uniqueInstance [ + + self orbit: 0. + ^ uniqueInstance ifNil: [ uniqueInstance := super new ] +] + +{ #category : #'as yet unclassified' } +Performance class >> withCanvas: aRSCanvas [ + + ^ self new + canvas: aRSCanvas; + freq: 120 bpm; + rsGroupDictionary: Dictionary new +] + +{ #category : #'as yet unclassified' } +Performance class >> withCircles [ + + ^ self new + visualization: CircleVisualization new; + yourself +] + +{ #category : #'as yet unclassified' } +Performance >> Dictionary [ Variable or expression expected -><< #Performance + layout: VariableLayout; + slots: { + #visualization . + #performer . + #canvas . + #rsGroupDictionary . + #freq . + #backup . + #bpm . + #transportStep . + #activeProcess. + }; + tag: 'Performance'; + package: 'LiveCoding' +] + +{ #category : #accessing } +Performance >> activeDSP [ +^ activeDSP +] + +{ #category : #accessing } +Performance >> activeDSP: aDSP [ +activeDSP := aDSP +] + +{ #category : #accessing } +Performance >> activeProcess [ + + ^ activeProcess +] + +{ #category : #accessing } +Performance >> activeProcess: anObject [ + + activeProcess := anObject +] + +{ #category : #'performance - adding' } +Performance >> add: aSequencer channel: anIntegerBetween1And16 [ + +aSequencer midiChannel: anIntegerBetween1And16. +super add: aSequencer. +] + +{ #category : #accessing } +Performance >> at: key put: anObject [ + + "Set the value at key to be anObject. If key is not found, create a + new entry for key and set is value to anObject. Answer anObject. + If a RsCanvas have been associated to the Performance add the visualisation of the sequencer to the RSCanvas" + + + super at: key put: anObject. + + canvas isNotNil & anObject isSequencer ifTrue: [ + (self rsGroupDictionary includesKey: key) + ifTrue: [ + canvas removeShape: (rsGroupDictionary at: key) asShape. + anObject visualizeOn: canvas. + rsGroupDictionary at: key put: anObject visualGroup ] + ifFalse: [ + anObject visualizeOn: canvas. + rsGroupDictionary at: key put: anObject visualGroup ] ] +] + +{ #category : #'LiveCoding - Performance' } +Performance >> backup [ + + + ^ backup +] + +{ #category : #accessing } +Performance >> backup: anObject [ + + backup := anObject +] + +{ #category : #accessing } +Performance >> bpm [ + + ^ bpm +] + +{ #category : #accessing } +Performance >> bpm: aRateInSeconds [ + + bpm := 60 / 4 / (aRateInSeconds ). +] + +{ #category : #accessing } +Performance >> canvas [ +^ canvas +] + +{ #category : #accessing } +Performance >> canvas: aRSCanvas [ +canvas := aRSCanvas +] + +{ #category : #accessing } +Performance >> dirtMIDIDevice [ + + ^ dirtMIDIDevice +] + +{ #category : #accessing } +Performance >> dirtMIDIDevice: aString [ +"select a SuperDirt MIDI device for the Performance" + dirtMIDIDevice := aString +] + +{ #category : #accessing } +Performance >> freq [ +^ freq +] + +{ #category : #accessing } +Performance >> freq1: anObject [ + + freq := anObject +] + +{ #category : #accessing } +Performance >> freq: aDurationInSeconds [ + + "use for playback speed" + + freq := aDurationInSeconds asFloat +] + +{ #category : #'LiveCoding - Performance' } +Performance >> incrementTransportStep [ + transportStep := transportStep + 1. +] + +{ #category : #initialization } +Performance >> initialize: aSize [ +super initialize: aSize. +activeProcess := Process new terminate. +freq := 132 bpm. +] + +{ #category : #testing } +Performance >> isPerformance [ + +^ true +] + +{ #category : #accessing } +Performance >> length [ +"return the number of steps of the largest array contained in the performance values" +| valuesSizes maxLength | +valuesSizes := (1 to: (self values size)) collect: [ :i | ((self values at: i) at: 1 )size ]. +maxLength := valuesSizes inject: 0 into: [ :a :c | (a > c) ifTrue: [ a ] ifFalse: [ c ] ]. +^ maxLength +] + +{ #category : #'muting and soloing' } +Performance >> mute: aKeyOrAnArrayOfKeys [ + + "remove all the keys in the arrayOfKeys from the Performance or remove just a single key" + + aKeyOrAnArrayOfKeys isArray + ifTrue: [ + aKeyOrAnArrayOfKeys do: [ :i | self removeKey: i ifAbsent: [ ] ] ] + ifFalse: [ self removeKey: aKeyOrAnArrayOfKeys ]. + "if the Performance has an associated canvas, it also removes the shapes group visualizing the key sequencer" + canvas isNotNil ifTrue: [ + aKeyOrAnArrayOfKeys isArray + ifTrue: [ + aKeyOrAnArrayOfKeys do: [ :i | + canvas removeShape: (rsGroupDictionary at: i) asShape ] ] + ifFalse: [ + canvas removeShape: + (rsGroupDictionary at: aKeyOrAnArrayOfKeys) asShape ]. canvas signalUpdate ] +] + +{ #category : #'muting and soloing' } +Performance >> muteAll [ + + " remove all keys from the Performance" + + | anArrayOfKeys | + backup := self asDictionary . + self keysDo: [ :i | self removeKey: i ifAbsent: [ nil ] ]. + + [ + canvas isNotNil ifTrue: [ + anArrayOfKeys do: [ :i | + canvas removeShape: (rsGroupDictionary at: i) asShape ] ] ] fork +] + +{ #category : #'LiveCoding - satelliteEvent' } +Performance >> pacaDance [ + + "convenience method created for the ICLC2023 performance - it just play the performance at 132 bpm for 4096 bars" + + + | aRateInSeconds aNumberOfSteps step bpm beatInSeconds gateTime | + aRateInSeconds := 132 bpm. + aNumberOfSteps := 4096 bars. + 'x' asPacaAddress. "if PacaAddress is not stored yet" + self resetAllSequencers. "reset all the sequencers" + "as in early hardware sequencers, default gatetime is 80% of the step duration" + + bpm := 60 / (aRateInSeconds * 4). + + gateTime := aRateInSeconds * 0.8. + beatInSeconds := aRateInSeconds * 4. + step := 0. + + + ^ [ + bpm toKyma: 'BPM'. + + + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: aRateInSeconds) wait. + " test if dictionary values is an Array of two arrays or as defined, a sequence" + + self keysAndValuesDo: [ :key :value | + value isSequencer + ifTrue: [ + (value gates at: (step modulo: value gatesSize)) = 1 + ifTrue: [ + | note | + note := value notes at: + (value noteIndex modulo: value notesSize). + key asString + toKymaAsGate: gateTime + * + (value durations at: + (value noteIndex modulo: value durationsSize)) + note: note. + + "maybe this part is not useful anymore" + value durations isArray + ifTrue: [ + (value durations at: + (value noteIndex modulo: value durationsSize)) toKyma: + key asString , 'Duration' ] + ifFalse: [ nil ]. + + "experimental, for extra control parameter" + value extra1 notNil + ifTrue: [ + ((value extra1 at: 2) at: + (value noteIndex modulo: value extra1Size)) toKyma: + key asString , (value extra1 at: 1) ] + ifFalse: [ nil ]. + + "new experimental, for second extra control" + value extra2 notNil + ifTrue: [ + ((value extra2 at: 2) at: + (value noteIndex modulo: value extra2Size)) toKyma: + key asString , (value extra2 at: 1) ] + ifFalse: [ nil ]. + + + value noteIndex: value noteIndex + 1 + + "noteIndex := noteIndex + 1." ] + ifFalse: [ nil ] ] + ifFalse: [ + (value at: (step modulo: value size)) toKyma: key asString ] ]. + + "step is incrementing in any case" + step := step + 1 ] ] forkAt: Processor timingPriority +] + +{ #category : #accessing } +Performance >> performer [ + + ^ performer +] + +{ #category : #accessing } +Performance >> performer: aPerformer [ +"choose a Performer for the Performance" +" to be safe, it clear the performance" +Performance uniqueInstance muteAll. +performer := aPerformer. +Performance uniqueInstance muteAll. +] + +{ #category : #playing } +Performance >> play [ +" reset all the Sequencers" +self resetAllSequencers . + +"just on performance at once" +self activeProcess ifNil: [ self performer play ] ifNotNil: [(self activeProcess isTerminated )ifTrue: [ self performer play ] ifFalse: [ ] ] +. + + + +] + +{ #category : #playing } +Performance >> playFor: aNumberOfSteps [ + + +" reset all the Sequencers" +self resetAllSequencers . + +"just on performance at once" +( self activeProcess isTerminated) ifTrue: [ self performer playFor: aNumberOfSteps ] ifFalse:[]. + + +] + +{ #category : #'as yet unclassified' } +Performance >> playKymaSequencFor: aNumberOfSteps [ + +"with this method you can chane the Performance speed in real time! " + " iterate over a process for self times, at a rate of aRateInMilliSeconds for step, through a dictionary of patterns, containing arrays of numbers as values and symbols represnting the OSC address without slash that will be sent out. +an OSC with the address /vcs/BPM/1 with the rate converted in BeatsPerMinutes" + + | step bpm beatInSeconds gateTime | + 'x' asPacaAddress. "if PacaAddress is not stored yet" + self resetAllSequencers. "reset all the sequencers" + "as in early hardware sequencers, default gatetime is 80% of the step duration" + + + step := 0. + + + ^ [ + bpm := 60 / (self freq * 4). + + gateTime := self freq * 0.8. + beatInSeconds := self freq * 4. + bpm toKyma: 'BPM'. + + + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: self freq) wait. + " test if dictionary values is an Array of two arrays or as defined, a sequence" + + self keysAndValuesDo: [ :key :value | + value isSequencer + ifTrue: [ + (value gates at: (step modulo: value gatesSize)) = 1 + ifTrue: [ + | note | + note := value notes at: + (value noteIndex modulo: value notesSize). + key asString + toKymaAsGate: gateTime + * + (value durations at: + (value noteIndex modulo: value durationsSize)) + note: note. + + "maybe this part is not useful anymore" + value durations isArray + ifTrue: [ + (value durations at: + (value noteIndex modulo: value durationsSize)) toKyma: + key asString , 'Duration' ] + ifFalse: [ nil ]. + + "experimental, for extra control parameter" + value extra1 notNil + ifTrue: [ + ((value extra1 at: 2) at: + (value noteIndex modulo: value extra1Size)) toKyma: + key asString , (value extra1 at: 1) ] + ifFalse: [ nil ]. + + "new experimental, for second extra control" + value extra2 notNil + ifTrue: [ + ((value extra2 at: 2) at: + (value noteIndex modulo: value extra2Size)) toKyma: + key asString , (value extra2 at: 1) ] + ifFalse: [ nil ]. + + + value noteIndex: value noteIndex + 1 + + "noteIndex := noteIndex + 1." ] + ifFalse: [ nil ] ] + ifFalse: [ + (value at: (step modulo: value size)) toKyma: key asString ] ]. + + "step is incrementing in any case" + step := step + 1 ] ] forkAt: Processor timingPriority +] + +{ #category : #playing } +Performance >> playKymaSequenceAt: aRateInSeconds for: aNumberOfSteps [ + + " iterate over a process for self times, at a rate of aRateInMilliSeconds for step, through a dictionary of patterns, containing arrays of numbers as values and symbols represnting the OSC address without slash that will be sent out. +an OSC with the address /vcs/BPM/1 with the rate converted in BeatsPerMinutes" + + | step bpm beatInSeconds gateTime | + 'x' asPacaAddress. "if PacaAddress is not stored yet" + self resetAllSequencers. "reset all the sequencers" + "as in early hardware sequencers, default gatetime is 80% of the step duration" + + bpm := 60 / (aRateInSeconds * 4). + + gateTime := aRateInSeconds * 0.8. + beatInSeconds := aRateInSeconds * 4. + step := 0. + + + ^ [ + + bpm toKyma: 'BPM'. + + + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: aRateInSeconds) wait. + " test if dictionary values is an Array of two arrays or as defined, a sequence" + + self keysAndValuesDo: [ :key :value | + value isSequencer + ifTrue: [ + (value gates at: (step modulo: value gatesSize)) = 1 + ifTrue: [ + | note | + note := value notes at: + (value noteIndex modulo: value notesSize). + key asString + toKymaAsGate: gateTime + * + (value durations at: + (value noteIndex modulo: value durationsSize)) + note: note. + + "maybe this part is not useful anymore" + value durations isArray + ifTrue: [ + (value durations at: + (value noteIndex modulo: value durationsSize)) toKyma: + key asString , 'Duration' ] + ifFalse: [ nil ]. + + "experimental, for extra control parameter" + value extra1 notNil + ifTrue: [ + ((value extra1 at: 2) at: + (value noteIndex modulo: value extra1Size)) toKyma: + key asString , (value extra1 at: 1) ] + ifFalse: [ nil ]. + + "new experimental, for second extra control" + value extra2 notNil + ifTrue: [ + ((value extra2 at: 2) at: + (value noteIndex modulo: value extra2Size)) toKyma: + key asString , (value extra2 at: 1) ] + ifFalse: [ nil ]. + + + value noteIndex: value noteIndex + 1 + + "noteIndex := noteIndex + 1." ] + ifFalse: [ nil ] ] + ifFalse: [ + (value at: (step modulo: value size)) toKyma: key asString ] ]. + + "step is incrementing in any case" + step := step + 1 ] ] forkAt: Processor timingPriority +] + +{ #category : #'as yet unclassified' } +Performance >> playLocalSequenceAt: aRateInSeconds for: aNumberOfSteps [ + + " iterate over a process for self times, at a rate of aRateInMilliSeconds for step, through a dictionary of patterns, containing arrays of numbers as values and symbols represnting the OSC address without slash that will be sent out. +an OSC with the address /vcs/BPM/1 with the rate converted in BeatsPerMinutes" + + "if the value in the dictionay is a Sequencer, an OSC message is sent to the key as OSC address with two arguments, one for the gate, and one for the note. the gate stays at 1 for the duration selected." + + | step bpm beatInSeconds gateTime | + bpm := 60 / (aRateInSeconds * 4). + gateTime := aRateInSeconds * 0.8. + beatInSeconds := aRateInSeconds * 4. + step := 0. + + ^ [ + bpm toLocal: 'BPM/'. + + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: aRateInSeconds) wait. + " test if dictionary values is an Array of two arrays or as defined, a sequence" + + self keysAndValuesDo: [ :key :value | + value isSequencer + ifTrue: [ + (value gates at: (step modulo: value gatesSize)) = 1 + ifTrue: [ + | note | + note := value notes at: + (value noteIndex modulo: value notesSize). + key asString + toLocalAsGate: gateTime + * + (value durations at: + (value noteIndex modulo: value durationsSize)) + note: note. + + + "experimental, for extra control parameter" + value extra1 notNil + ifTrue: [ + ((value extra1 at: 2) at: + (value noteIndex modulo: value extra1Size)) toLocal: + key asString , (value extra1 at: 1) ] + ifFalse: [ nil ]. + value noteIndex: value noteIndex + 1 ] + ifFalse: [ nil ] ] + ifFalse: [ + (value at: (step modulo: value size)) toLocal: key asString ] ]. + + "step is incrementing in any case" + step := step + 1 ] ] forkAt: Processor timingPriority +] + +{ #category : #removing } +Performance >> remove: aKey [ +"shorter version of removeKey:" + +self removeKey: aKey ifAbsent: [ ] +] + +{ #category : #initialization } +Performance >> resetAllSequencers [ + "reset noteIndex fro all the sequencers in the Performance. " + + self keysAndValuesDo: [ :key :value | + value isSequencer + ifTrue: [ value noteIndex: 1 ] + ifFalse: [ nil ] ] +] + +{ #category : #'LiveCoding - Performance' } +Performance >> restore [ + + "restore what the performance was before receiveing messages muteAll or solo" + + | p | + p := Performance uniqueInstance. + self backup keysAndValuesDo: [ :k :v | + p at: k put: v]. + p keysAndValuesRemove: [ :kp :vp | (p backup includesKey: kp) not ] +] + +{ #category : #accessing } +Performance >> rsGroupDictionary [ + + ^ rsGroupDictionary +] + +{ #category : #accessing } +Performance >> rsGroupDictionary: anRSGroupDictionary [ + + rsGroupDictionary := anRSGroupDictionary +] + +{ #category : #'muting and soloing' } +Performance >> solo: aKeyOrAnArrayOfKeys [ + + " remove all keys from the Performance except aKey or anArrayOfKeys" + + | anArrayOfKeys | + backup := self asDictionary. "do the backup" + anArrayOfKeys := self keys. + aKeyOrAnArrayOfKeys isArray + ifTrue: [ + self keysAndValuesRemove: [ :keys :values | + (aKeyOrAnArrayOfKeys includes: keys) not ] ] + ifFalse: [ + self keysAndValuesRemove: [ :keys :values | " + [canvas isNotNil ifTrue: [ + anArrayOfKeys do: [ :i | (i ~= aKey) ifTrue: [ + canvas removeShape: (rsGroupDictionary at: i) asShape ] ]] ] fork +" + keys ~= aKeyOrAnArrayOfKeys ] ] +] + +{ #category : #accessing } +Performance >> stop [ + self activeProcess terminate +] + +{ #category : #modifying } +Performance >> swap: firstKey with: secondKey [ +"swap the values of two perfromance keys" + | tempValue | + tempValue := self at: firstKey. + self at: firstKey put: (self at: secondKey). + self at: secondKey put: tempValue +] + +{ #category : #accessing } +Performance >> transportStep [ + ^ transportStep +] + +{ #category : #accessing } +Performance >> transportStep: anInteger [ + + transportStep := anInteger +] + +{ #category : #'LiveCoding - Performance' } +Performance >> unsolo: aKeyInPerformance [ + "convenience method to unsolo a key in the performance" + +self restore +] + +{ #category : #accessing } +Performance >> visualization [ + + ^ visualization +] + +{ #category : #accessing } +Performance >> visualization: anObject [ + + visualization := anObject +] diff --git a/src/Coypu/PerformanceTest.class.st b/src/Coypu/PerformanceTest.class.st new file mode 100644 index 0000000..48d168d --- /dev/null +++ b/src/Coypu/PerformanceTest.class.st @@ -0,0 +1,393 @@ +Class { + #name : #PerformanceTest, + #superclass : #TestCase, + #instVars : [ + 'performance', + 'performer' + ], + #category : #'Coypu-Performance' +} + +{ #category : #tests } +PerformanceTest >> playPerformance [ + + performance at: #gate put: ((15 rumba notes: #( 10 20 30 )) + and: #( 'extra1' #( 40 60 90 ) ) + and: #( 'extra2' #( 45 65 95 ) )). + + performance play: 60 bpm for: 1 bars. + + self waitASecond +] + +{ #category : #tests } +PerformanceTest >> setUp [ + + performance := Performance uniqueInstance . + + performance performer: performer + + + "self playPerformance" +] + +{ #category : #tests } +PerformanceTest >> testAddSequencerAndReplace [ + + | p seq1 seq2 | + p := Performance uniqueInstance . + seq1 := 16 downbeats. + seq2 := 16 tumbao. + p add: seq1. + p add: seq2. + + self assert: (p includes: seq2) & (p includes: seq1) not equals: true +] + +{ #category : #tests } +PerformanceTest >> testAssignMIDIChannelToSequencerAutomatically [ + + | perf seq | + perf := Performance new. + seq := Sequencer uniqueInstance. + + perf at: #midi7 put: seq. + + self assert: seq midiChannel equals: 7 +] + +{ #category : #tests } +PerformanceTest >> testBwToKey [ + + | p | + p := Performance uniqueInstance. + 16 downbeats to: #synth. + #synth bw: #( 1 2 3 4 ). + self assert: (p at: #synth) extra1 equals: #( #Bw #( 1 2 3 4 ) ) +] + +{ #category : #tests } +PerformanceTest >> testDirtNotesToKey [ + +| seq | +seq := 16 downbeats sound: 'arpy' dirtNotes: #(60 61 62 63) to: #aKeyInPerf. + +self assert: seq samplesIndex equals: #(0 1 2 3) +] + +{ #category : #tests } +PerformanceTest >> testHexPatternAndDirtSoundsToKey [ + + | p | + p := Performance uniqueInstance. + '8888' sound: #( 'cp' ) to: #synth. + + self + assert: (p at: #synth) gates + equals: #( 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 ) asRhythm. + self assert: (p at: #synth) soundPattern equals: #('cp'). + +] + +{ #category : #tests } +PerformanceTest >> testHexPatternAndIndexesToKey [ + + | p | + p := Performance uniqueInstance. + '8888' index: #( 1 2 3 4 ) to: #synth. + + self + assert: (p at: #synth) gates + equals: #( 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 ) asRhythm. + self assert: (p at: #synth) extra1 equals: #( #Index #( 1 2 3 4 ) ). + +] + +{ #category : #tests } +PerformanceTest >> testHexPatternAndNotesAbdIndexesToKey [ + + | p | + p := Performance uniqueInstance. + '8888' notes: #( 48 48 48 48 ) index: #( 1 2 3 4 ) to: #synth. + + self + assert: (p at: #synth) gates + equals: #(1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0) asRhythm . + self assert: (p at: #synth) extra1 equals: #( #Index #( 1 2 3 4 ) ). + self assert: (p at: #synth) notes equals: #( 48 48 48 48 ) +] + +{ #category : #tests } +PerformanceTest >> testHexPatternAndNotesAndMidiChToKey [ + + | p | + p := Performance uniqueInstance. + '000F' notes: #( 48 48 48 48 ) midiCh: 2 to: #synth. + self assert: (p at: #synth) notes equals: #( 48 48 48 48 ). + self assert: (p at: #synth) midiChannel equals: 2 +] + +{ #category : #tests } +PerformanceTest >> testHexPatternAndNotesToKey [ + + | p | + p := Performance uniqueInstance. + '8888' notes: #( 48 48 48 48 ) to: #synth. + + self + assert: (p at: #synth) gates + equals: #( 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 ) asRhythm. + + self assert: (p at: #synth) notes equals: #( 48 48 48 48 ) +] + +{ #category : #tests } +PerformanceTest >> testHexPatternMidiChToKey [ + + | p | + p := Performance uniqueInstance. + '0808' midiCh: 2 to: #synth. + self assert: (p at: #synth) midiChannel equals: 2 +] + +{ #category : #tests } +PerformanceTest >> testIndexesToKey [ + + | p | + p := Performance uniqueInstance. + 16 downbeats to: #synth. + #synth indexes: #( 1 2 3 4 ). + self assert: (p at: #synth) extra1 equals: #(#Index #(1 2 3 4)). +] + +{ #category : #tests } +PerformanceTest >> testMidiChToKey [ + + | p | + p := Performance uniqueInstance. + 16 downbeats midiCh: 2 to: #synth. + . + self assert: (p at: #synth) midiChannel equals: 2 +] + +{ #category : #tests } +PerformanceTest >> testMuteAll [ +| p | +p := Performance uniqueInstance . +p at: #kick put: 16 downbeats . +p at: #snare put: 16 upbeats. +p at: #ch put: 16 quavers. +p at: #oh put: 16 semiquavers. + +p muteAll. +self assert: p isEmpty equals: true +] + +{ #category : #tests } +PerformanceTest >> testMuteKey [ + + performance at: #kick put: 16 downbeats. + #kick mute. + self assert: performance isEmpty equals: true +] + +{ #category : #tests } +PerformanceTest >> testNotesAndMidiChToKey [ + + | p | + p := Performance uniqueInstance. + 16 downbeats notes: #(48 48 48 48) midiCh: 2 to: #synth. + self assert: (p at: #synth) notes equals: #(48 48 48 48 ). + self assert: (p at: #synth) midiChannel equals: 2 +] + +{ #category : #tests } +PerformanceTest >> testNotesAndModeToKey [ + + | p | + p := Performance uniqueInstance. + 4 breves notes: #( 48 48 48 48 ) mode: #(0 1 2 3) to: #synth. + self assert: (p at: #synth) notes equals: #( 48 48 48 48 ). + self assert: (p at: #synth) extra1 equals: #(#Mode #( 0 1 2 3)). +] + +{ #category : #tests } +PerformanceTest >> testNotesToKey [ + + | p | + p := Performance uniqueInstance. + 16 downbeats to: #synth. + #synth notes: #( 48 48 48 48 ). + self assert: ((p at: #synth) notes) equals: #( 48 48 48 48 ) +] + +{ #category : #tests } +PerformanceTest >> testNumberToKey [ + + | p | + p := Performance uniqueInstance. + 16 downbeats to: #synth. + #synth number: #( 1 2 3 4 ). + self assert: (p at: #synth) extra2 equals: #( #Number #( 1 2 3 4 ) ) +] + +{ #category : #tests } +PerformanceTest >> testPerformanceBackup [ + +| p | +p := Performance uniqueInstance . +16 downbeats to: #kick. +16 upbeats to: #ch. + +self assert: p backup equals: p asDictionary +] + +{ #category : #tests } +PerformanceTest >> testPerformanceKeySolo [ + + | p | + p := Performance uniqueInstance. + p at: #kick put: 16 downbeats. + p at: #tom put: 32 shiko. + p at: #oh put: 16 upbeats. + p at: #snare put: '0240' pattern. + + #tom solo. + self assert: p keys equals: #( #tom ) +] + +{ #category : #tests } +PerformanceTest >> testPerformanceKeysSwap [ + + | seq1 seq2 p | + p := Performance new. + seq1 := 4 downbeats. + seq2 := 4 semiquavers. + p at: #a put: seq1. + p at: #b put: seq2. + + p swap: #a with: #b. + self assert: ( p at: #b) equals: seq1 + + +] + +{ #category : #tests } +PerformanceTest >> testPerformanceSolo [ + + | p | + p := Performance uniqueInstance . + p at: #kick put: 16 downbeats. + p at: #tom put: 32 shiko. + p at: #oh put: 16 upbeats. + p at: #snare put: '0240' pattern. + + self assert: (p solo: #tom) keys equals: #(#tom) +] + +{ #category : #tests } +PerformanceTest >> testRestorePerformance [ + +| p | +p := Performance uniqueInstance . +16 downbeats to: #kick. +16 upbeats to: #ch. +p muteAll. +p restore. +self assert: (p at: #kick) equals: 16 downbeats. + self assert: (p at: #ch) equals: 16 upbeats. +] + +{ #category : #tests } +PerformanceTest >> testRhythmAndIndexesToKey [ + + | p | + p := Performance uniqueInstance. + 16 downbeats index: #( 1 2 3 4 ) to: #synth. + + self + assert: (p at: #synth) gates + equals: #( 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 ) asRhythm. + self assert: (p at: #synth) extra1 equals: #( #Index #( 1 2 3 4 ) ) +] + +{ #category : #tests } +PerformanceTest >> testRhythmAndNotesAbdIndexesToKey [ + + | p | + p := Performance uniqueInstance. + 16 downbeats notes: #(48 48 48 48) index: #( 1 2 3 4 ) to: #synth. + + self + assert: (p at: #synth) gates + equals: #( 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 ) asRhythm. + self assert: (p at: #synth) extra1 equals: #( #Index #( 1 2 3 4 ) ). + self assert: (p at: #synth) notes equals: #(48 48 48 48). + +] + +{ #category : #tests } +PerformanceTest >> testRhythmAndNotesToKey [ + + | p | + p := Performance uniqueInstance. + 16 downbeats notes: #( 48 48 48 48 ) to: #synth. + #synth notes: #( 48 48 48 48 ). + self + assert: (p at: #synth) gates + equals: #( 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 ) asRhythm . + self assert: (p at: #synth) notes equals: #( 48 48 48 48 ) +] + +{ #category : #tests } +PerformanceTest >> testSequencerPlaysImmediately [ + + | perf | + perf := Performance new. + 16 downbeats to: #kick. + + self assert: (perf at: #kick) equals: 16 downbeats + +] + +{ #category : #tests } +PerformanceTest >> testStringOfHexToPerformance [ + +| p | +p := Performance uniqueInstance . +'8888' to: #kick. +self assert: (p at: #kick) equals: '8888' pattern +] + +{ #category : #tests } +PerformanceTest >> testUnsoloKeyInPerformance [ + + | p | + p := Performance uniqueInstance. + 16 downbeats to: #kick. + 16 upbeats to: #ch. + #ch solo. + #ch unsolo. + self assert: (p at: #kick) equals: 16 downbeats. + self assert: (p at: #ch) equals: 16 upbeats +] + +{ #category : #tests } +PerformanceTest >> testUnsoloPerformance [ + + | p | + p := Performance uniqueInstance. + 16 downbeats to: #kick. + 16 upbeats to: #ch. + p solo: #ch. + p unsolo: #ch. + self assert: (p at: #kick) equals: 16 downbeats. + self assert: (p at: #ch) equals: 16 upbeats +] + +{ #category : #tests } +PerformanceTest >> waitASecond [ + + self flag: #TODO. "Change time management" + (Delay forSeconds: 1) wait +] diff --git a/src/Coypu/Performer.class.st b/src/Coypu/Performer.class.st new file mode 100644 index 0000000..7c92ec2 --- /dev/null +++ b/src/Coypu/Performer.class.st @@ -0,0 +1,34 @@ +" +The Performer selects the audio backend. +PerformerKyma for Symbolic SoundKyma. +PerformerLocal for a local OSC backend (FAUST, ChucK, PureData, Max/MSP - just to name a few) +PerformerSuperDirt for the SuperDirt audio engine, a SuperCollider quark which is usually controlled by TidalCycles +" +Class { + #name : #Performer, + #superclass : #Object, + #instVars : [ + 'performance', + 'freq', + 'fourDaysAt120BPM' + ], + #category : #'Coypu-Performance' +} + +{ #category : #initialization } +Performer >> initialize [ +super initialize . +performance := Performance uniqueInstance . +freq := performance freq. +fourDaysAt120BPM := 4 days asMilliSeconds / ((60/ 120) /4) +] + +{ #category : #playing } +Performer >> play [ +self subclassResponsibility +] + +{ #category : #playing } +Performer >> playFor: aNumberOfSteps [ +self subclassResponsibility +] diff --git a/src/Coypu/PerformerKyma.class.st b/src/Coypu/PerformerKyma.class.st new file mode 100644 index 0000000..0523675 --- /dev/null +++ b/src/Coypu/PerformerKyma.class.st @@ -0,0 +1,202 @@ +" +Performer for Symbolic Sound Kyma +" +Class { + #name : #PerformerKyma, + #superclass : #Performer, + #category : #'Coypu-Performance' +} + +{ #category : #playing } +PerformerKyma >> play [ +self playKymaSequencFor: fourDaysAt120BPM +] + +{ #category : #'as yet unclassified' } +PerformerKyma >> playFor: aNumberOfSteps [ + "with this method you can chane the Performance speed in real time! " + + " iterate over a process for self times, at a rate of aRateInMilliSeconds for step, through a dictionary of patterns, containing arrays of numbers as values and symbols represnting the OSC address without slash that will be sent out. +an OSC with the address /vcs/BPM/1 with the rate converted in BeatsPerMinutes" + + | bpm | + 'x' asPacaAddress. "if PacaAddress is not stored yet" + performance resetAllSequencers. "reset all the sequencers" + bpm := 60 / (performance freq * 4) bpm toKyma: 'BPM'. + + + performance bpm: bpm. + performance transportStep: 1. + performance activeProcess: ([ + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: performance freq) wait. + "sequencers scan" + [ + performance valuesDo: [ :seq | + (seq gates wrap: performance transportStep) = 1 + ifTrue: [ + seq playFullKymaEventAt: seq noteIndex. + "increment note Index" + seq noteIndex: seq noteIndex + 1 ] + ifFalse: [ nil ] ] ] forkAt: Processor timingPriority. + + "step is incremented anyway" + performance incrementTransportStep ] ] forkAt: + Processor timingPriority) +] + +{ #category : #'as yet unclassified' } +PerformerKyma >> playForOld: aNumberOfSteps [ + +"with this method you can chane the Performance speed in real time! " + " iterate over a process for self times, at a rate of aRateInMilliSeconds for step, through a dictionary of patterns, containing arrays of numbers as values and symbols represnting the OSC address without slash that will be sent out. +an OSC with the address /vcs/BPM/1 with the rate converted in BeatsPerMinutes" + + | step bpm beatInSeconds gateTime | + 'x' asPacaAddress. "if PacaAddress is not stored yet" + performance resetAllSequencers. "reset all the sequencers" + "as in early hardware sequencers, default gatetime is 80% of the step duration" + + + step := 0. + +performance activeProcess: ([ + bpm := 60 / (freq * 4). + + gateTime := freq * 0.8. + beatInSeconds := freq * 4. + bpm toKyma: 'BPM'. + + + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: freq) wait. + " test if dictionary values is an Array of two arrays or as defined, a sequence" + + performance keysAndValuesDo: [ :key :value | + value isSequencer + ifTrue: [ + (value gates at: (step modulo: value gatesSize)) = 1 + ifTrue: [ + | note | + note := value notes at: + (value noteIndex modulo: value notesSize). + key asString + toKymaAsGate: gateTime + * + (value durations at: + (value noteIndex modulo: value durationsSize)) + note: note. + + "maybe this part is not useful anymore" + value durations isArray + ifTrue: [ + (value durations at: + (value noteIndex modulo: value durationsSize)) toKyma: + key asString , 'Duration' ] + ifFalse: [ nil ]. + + "experimental, for extra control parameter" + value extra1 notNil + ifTrue: [ + ((value extra1 at: 2) at: + (value noteIndex modulo: value extra1Size)) toKyma: + key asString , (value extra1 at: 1) ] + ifFalse: [ nil ]. + + "new experimental, for second extra control" + value extra2 notNil + ifTrue: [ + ((value extra2 at: 2) at: + (value noteIndex modulo: value extra2Size)) toKyma: + key asString , (value extra2 at: 1) ] + ifFalse: [ nil ]. + + + value noteIndex: value noteIndex + 1 + + "noteIndex := noteIndex + 1." ] + ifFalse: [ nil ] ] + ifFalse: [ + (value at: (step modulo: value size)) toKyma: key asString ] ]. + + "step is incrementing in any case" + step := step + 1 ] ] forkAt: Processor timingPriority) +] + +{ #category : #'as yet unclassified' } +PerformerKyma >> playKymaSequencFor: aNumberOfSteps [ + +"with this method you can chane the Performance speed in real time! " + " iterate over a process for self times, at a rate of aRateInMilliSeconds for step, through a dictionary of patterns, containing arrays of numbers as values and symbols represnting the OSC address without slash that will be sent out. +an OSC with the address /vcs/BPM/1 with the rate converted in BeatsPerMinutes" + + | step bpm beatInSeconds gateTime | + 'x' asPacaAddress. "if PacaAddress is not stored yet" + performance resetAllSequencers. "reset all the sequencers" + "as in early hardware sequencers, default gatetime is 80% of the step duration" + + + step := 0. + +performance activeProcess: ([ + bpm := 60 / (freq * 4). + + gateTime := freq * 0.8. + beatInSeconds := freq * 4. + bpm toKyma: 'BPM'. + + + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: freq) wait. + " test if dictionary values is an Array of two arrays or as defined, a sequence" + + performance keysAndValuesDo: [ :key :value | + value isSequencer + ifTrue: [ + (value gates at: (step modulo: value gatesSize)) = 1 + ifTrue: [ + | note | + note := value notes at: + (value noteIndex modulo: value notesSize). + key asString + toKymaAsGate: gateTime + * + (value durations at: + (value noteIndex modulo: value durationsSize)) + note: note. + + "maybe this part is not useful anymore" + value durations isArray + ifTrue: [ + (value durations at: + (value noteIndex modulo: value durationsSize)) toKyma: + key asString , 'Duration' ] + ifFalse: [ nil ]. + + "experimental, for extra control parameter" + value extra1 notNil + ifTrue: [ + ((value extra1 at: 2) at: + (value noteIndex modulo: value extra1Size)) toKyma: + key asString , (value extra1 at: 1) ] + ifFalse: [ nil ]. + + "new experimental, for second extra control" + value extra2 notNil + ifTrue: [ + ((value extra2 at: 2) at: + (value noteIndex modulo: value extra2Size)) toKyma: + key asString , (value extra2 at: 1) ] + ifFalse: [ nil ]. + + + value noteIndex: value noteIndex + 1 + + "noteIndex := noteIndex + 1." ] + ifFalse: [ nil ] ] + ifFalse: [ + (value at: (step modulo: value size)) toKyma: key asString ] ]. + + "step is incrementing in any case" + step := step + 1 ] ] forkAt: Processor timingPriority) +] diff --git a/src/Coypu/PerformerLocal.class.st b/src/Coypu/PerformerLocal.class.st new file mode 100644 index 0000000..1c11d24 --- /dev/null +++ b/src/Coypu/PerformerLocal.class.st @@ -0,0 +1,66 @@ +" +Performer for a generic OSC audio client on port: 57142 +" +Class { + #name : #PerformerLocal, + #superclass : #Performer, + #category : #'Coypu-Performance' +} + +{ #category : #playing } +PerformerLocal >> play [ +self playLocalSequenceAt: 132 bpm for: fourDaysAt120BPM +] + +{ #category : #accessing } +PerformerLocal >> playLocalSequenceAt: aRateInSeconds for: aNumberOfSteps [ + + " iterate over a process for self times, at a rate of aRateInMilliSeconds for step, through a dictionary of patterns, containing arrays of numbers as values and symbols represnting the OSC address without slash that will be sent out. +an OSC with the address /vcs/BPM/1 with the rate converted in BeatsPerMinutes" + + "if the value in the dictionay is a Sequencer, an OSC message is sent to the key as OSC address with two arguments, one for the gate, and one for the note. the gate stays at 1 for the duration selected." + + | step bpm beatInSeconds gateTime | + bpm := 60 / (aRateInSeconds * 4). + gateTime := aRateInSeconds * 0.8. + beatInSeconds := aRateInSeconds * 4. + step := 0. + + performance activeProcess: ([ + bpm toLocal: 'BPM/'. + + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: aRateInSeconds) wait. + " test if dictionary values is an Array of two arrays or as defined, a sequence" + + performance keysAndValuesDo: [ :key :value | + value isSequencer + ifTrue: [ + (value gates at: (step modulo: value gatesSize)) = 1 + ifTrue: [ + | note | + note := value notes at: + (value noteIndex modulo: value notesSize). + key asString + toLocalAsGate: gateTime + * + (value durations at: + (value noteIndex modulo: value durationsSize)) + note: note. + + + "experimental, for extra control parameter" + value extra1 notNil + ifTrue: [ + ((value extra1 at: 2) at: + (value noteIndex modulo: value extra1Size)) toLocal: + key asString , (value extra1 at: 1) ] + ifFalse: [ nil ]. + value noteIndex: value noteIndex + 1 ] + ifFalse: [ nil ] ] + ifFalse: [ + (value at: (step modulo: value size)) toLocal: key asString ] ]. + + "step is incrementing in any case" + step := step + 1 ] ] forkAt: Processor timingPriority) +] diff --git a/src/Coypu/PerformerMIDI.class.st b/src/Coypu/PerformerMIDI.class.st new file mode 100644 index 0000000..a313363 --- /dev/null +++ b/src/Coypu/PerformerMIDI.class.st @@ -0,0 +1,129 @@ +" +Performer for external MIDI hardware +" +Class { + #name : #PerformerMIDI, + #superclass : #Performer, + #classInstVars : [ + 'midiOut' + ], + #category : #'Coypu-Performance' +} + +{ #category : #accessing } +PerformerMIDI class >> midiOut [ + + ^ midiOut +] + +{ #category : #accessing } +PerformerMIDI class >> midiOut: aMIDISender [ + + midiOut := aMIDISender +] + +{ #category : #playing } +PerformerMIDI >> play [ +self shouldBeImplemented +] + +{ #category : #playing } +PerformerMIDI >> playFor: aNumberOfSteps [ + + "play the performance to SuperDIrt/SuperCollider audio engine - default freq is 132 bpm + Performance speed can be changed with p freq: a bpm" + + + performance bpm: 60 / (performance freq * 4). + performance transportStep: 1. + performance activeProcess: ([ + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: performance freq) wait. + "sequencers scan" + [ + performance valuesDo: [ :seq | + (seq gates wrap: performance transportStep) = 1 + ifTrue: [ + seq playMIDIEventAt: seq noteIndex "delta!!!!" . + "increment note Index" + seq noteIndex: seq noteIndex + 1 ] + ifFalse: [ nil ] ] ] forkAt: Processor timingPriority. + + "step is incremented anyway" + performance incrementTransportStep ] ] forkAt: Processor timingPriority). + + + +] + +{ #category : #accessing } +PerformerMIDI >> playMIDISequenceAt: aRateInSeconds for: aNumberOfSteps on: aMIDISender [ + + "test" + + " iterate over a process for self times, at a rate of aRateInMilliSeconds for step, through athe Performance containing arrays of numbers as values +keys in the Performance represents MIDI channels and must be written as #ch1 #ch2 #ch3 and so on if you want to send noteOn/off or + +if you want to send out ccs +" + + | step bpm beatInSeconds gateTime | + "as in early hardware sequencers, default gatetime is 80% of the step duration" + bpm := 60 / (aRateInSeconds * 4). + beatInSeconds := aRateInSeconds * 4. + gateTime := aRateInSeconds * 0.8. + step := 0. + + + performance activeProcess: ([ "bpm toKyma: 'BPM'."" we dont do a visualization at the momentn""visualization startWith: bpm." + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: aRateInSeconds) wait. + " test if dictionary values is an Array of two arrays or as defined, a sequence" + + performance valuesDo: [ :value | + value isSequencer + ifTrue: [ + (value gates at: (step modulo: value gatesSize)) = 1 ifTrue: [ "noteIndex := noteIndex + 1." + value hasChords + ifFalse: [ + aMIDISender + playDrum: + (value notes at: (value noteIndex modulo: value notesSize)) + onChannel: value midiChannel ] + ifTrue: [ + aMIDISender + playChord: 60 + type: (Chord chordLists at: + (value chords at: + (value noteIndex modulo: value chords size))) + onChannel: value midiChannel + duration: 0.2 ]. + "sends a ControlChange message on the same channel, if provided" + value extra1 notNil + ifTrue: [ + aMIDISender + sendCC: (value extra1 at: 1) + withValue: + ((value extra1 at: 2) at: + (value noteIndex modulo: value extra1Size)) + onChannel: value midiChannel ] + ifFalse: [ nil ]. + + "sends another ControlChange message on the same channel, if provided" + value extra2 notNil + ifTrue: [ + aMIDISender + sendCC: (value extra2 at: 1) + withValue: + ((value extra2 at: 2) at: + (value noteIndex modulo: value extra2Size)) + onChannel: value midiChannel ] + ifFalse: [ nil ]. + + "advance" + value noteIndex: value noteIndex + 1 ] ] + ifFalse: [ nil ] ]. + + "step is incrementing in any case" + step := step + 1 ] ] forkAt: Processor timingPriority) +] diff --git a/src/Coypu/PerformerPhaust.class.st b/src/Coypu/PerformerPhaust.class.st new file mode 100644 index 0000000..5b2dba7 --- /dev/null +++ b/src/Coypu/PerformerPhaust.class.st @@ -0,0 +1,48 @@ +" +Performer for Phausto. +Phausto and its libraries must be loaded into your image. +Find Phausto here: +https://github.com/lucretiomsp/phausto +" +Class { + #name : #PerformerPhaust, + #superclass : #Performer, + #category : #'Coypu-Performance' +} + +{ #category : #playing } +PerformerPhaust >> play [ +"play a Phaust performance for 4 days at 120 bpm" + +self playFor: fourDaysAt120BPM +] + +{ #category : #playing } +PerformerPhaust >> playFor: aNumberOfSteps [ + "play the performance to SuperDIrt/SuperCollider audio engine - default freq is 132 bpm + Performance speed can be changed with p freq: a bpm" + + Performance uniqueInstance activeDSP + ifNil: [ + ^ Error new signal: + 'There must be an active DSP for this performance' ] + ifNotNil: [ + performance bpm: 60 / (performance freq * 4). + performance transportStep: 1. + performance activeProcess: ([ + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: performance freq) wait. + "sequencers scan" + [ + performance valuesDo: [ :seq | + (seq gates wrap: performance transportStep) = 1 + ifTrue: [ + seq playPhaustEventAt: seq noteIndex. "delta!!!!" + "increment note Index" + seq noteIndex: seq noteIndex + 1] + ifFalse: [ nil ] ] ] forkAt: Processor timingPriority. + + "step is incremented anyway" + performance incrementTransportStep ] ] forkAt: + Processor timingPriority) ] +] diff --git a/src/Coypu/PerformerSuperDirt.class.st b/src/Coypu/PerformerSuperDirt.class.st new file mode 100644 index 0000000..d4052d8 --- /dev/null +++ b/src/Coypu/PerformerSuperDirt.class.st @@ -0,0 +1,104 @@ +" +Peformer to play the Performance on the SuperDirt audio engine for SuperCollider. +" +Class { + #name : #PerformerSuperDirt, + #superclass : #Performer, + #category : #'Coypu-Performance' +} + +{ #category : #playing } +PerformerSuperDirt >> play [ +"play the performance for almost 4 days at 120 bpm" +self playDirtFor: fourDaysAt120BPM +] + +{ #category : #playing } +PerformerSuperDirt >> playDirt [ + + "play the performance to SuperDIrt/SuperCollider audio engine - default freq is 132 bpm + Performance speed can be changed with p freq: a bpm" + + + performance bpm: 60 / (performance freq * 4). + performance transportStep: 1. + performance activeProcess: ([ + "the perfrormance last 4 days at 120 bpm" + fourDaysAt120BPM timesRepeat: [ + (Delay forSeconds: performance freq) wait. + "sequencers scan" + [ + performance valuesDo: [ :seq | + (seq gates wrap: performance transportStep) = 1 + ifTrue: [ + seq playFullDirtEventAt: seq noteIndex "delta!!!!" . + "increment note Index" + seq noteIndex: seq noteIndex + 1 ] + ifFalse: [ nil ] ] ] forkAt: Processor timingPriority. + + "step is incremented anyway" + performance incrementTransportStep ] ] forkAt: Processor timingPriority). + + + +] + +{ #category : #playing } +PerformerSuperDirt >> playDirtFor: aNumberOfSteps [ + + "play the performance to SuperDIrt/SuperCollider audio engine - default freq is 132 bpm + Performance speed can be changed with p freq: a bpm" + + + + performance bpm: 60 / (performance freq * 4). + performance transportStep: 1. + performance activeProcess: ([ + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: performance freq) wait. + "sequencers scan" + [ + performance valuesDo: [ :seq | + (seq gates wrap: performance transportStep) = 1 + ifTrue: [ + seq playFullDirtEventAt: seq noteIndex . + "increment note Index" + seq noteIndex: seq noteIndex + 1 ] + ifFalse: [ nil ] ] ] forkAt: Processor timingPriority. + + "step is incremented anyway" + performance incrementTransportStep ] ] forkAt: Processor timingPriority). + + + +] + +{ #category : #playing } +PerformerSuperDirt >> playFor: aNumberOfSteps [ + + "play the performance to SuperDIrt/SuperCollider audio engine - default freq is 132 bpm + Performance speed can be changed with p freq: a bpm" + + + + performance bpm: 60 / (performance freq * 4). + performance transportStep: 1. + performance activeProcess: ([ + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: performance freq) wait. + "sequencers scan" + [ + performance valuesDo: [ :seq | + (seq gates wrap: performance transportStep) = 1 + ifTrue: [ + seq playFullDirtEventAt: seq noteIndex "delta!!!!" . + "increment note Index" + seq noteIndex: seq noteIndex + 1 ] + ifFalse: [ nil ] ] ] forkAt: Processor timingPriority. + + "step is incremented anyway" + performance incrementTransportStep ] ] forkAt: Processor timingPriority). + + + +] diff --git a/src/Coypu/PerformerSuperDirtAndRoassal.class.st b/src/Coypu/PerformerSuperDirtAndRoassal.class.st new file mode 100644 index 0000000..8763713 --- /dev/null +++ b/src/Coypu/PerformerSuperDirtAndRoassal.class.st @@ -0,0 +1,186 @@ +" +Performer sdfor SuperDirt with Roassal visualization +" +Class { + #name : #PerformerSuperDirtAndRoassal, + #superclass : #Performer, + #instVars : [ + 'canvasBlock', + 'canvas', + 'random', + 'mode', + 'frame', + 'color', + 'border' + ], + #category : #'Coypu-Performance' +} + +{ #category : #accessing } +PerformerSuperDirtAndRoassal >> canvas [ + + ^ canvasBlock +] + +{ #category : #accessing } +PerformerSuperDirtAndRoassal >> canvas: anObject [ + + canvasBlock := anObject +] + +{ #category : #initialization } +PerformerSuperDirtAndRoassal >> initialize [ + super initialize. + self canvas: [ self updateCanvas ]. + + self initializeRoassalCanvas. +] + +{ #category : #initialization } +PerformerSuperDirtAndRoassal >> initializeRoassalCanvas [ + + canvas := RSCanvas new. + canvas shouldClearBackground: false. + random := Random new. + mode := 1. + canvas color: Color black. + frame := 0. + canvas camera position: 0@ 50. + color := NSScale ordinal + domain: #( 0 1 2 ); + range: { + [ Color black ]. + [ :e | Color h: (e propertyAt: #hue) s: 0.7 v: 1 ]. + [ :e | + frame even + ifTrue: [ Color black ] + ifFalse: [ Color h: (e propertyAt: #hue) s: 1 v: 1 ] ] }. + + border := RSBorder new + width: 0.5; + yourself +] + +{ #category : #initialization } +PerformerSuperDirtAndRoassal >> newBall [ + + | e att xs ys | + e := RSEllipse new. + e border: border. + att := e properties. + xs := random rsNext: -3 and: 3. + ys := (xs between: -1 and: 1) + ifTrue: [ + random next > 0.5 + ifTrue: [ random rsNext: 1 and: 3 ] + ifFalse: [ random rsNext: -3 and: -1 ] ] + ifFalse: [ random rsNext: 3 and: -3 ]. + att + at: #s put: 0; + at: #t put: 0; + at: #degX put: random next; + at: #degY put: random next; + at: #xs put: xs; + at: #ys put: ys; + at: #hue put: random next * 360; + at: #hue2 put: 4. + ^ e +] + +{ #category : #'instance creation' } +PerformerSuperDirtAndRoassal >> open [ + canvas open + position: 0@0; + extent: self currentWorld extent * (1 @ 0.2). +] + +{ #category : #playing } +PerformerSuperDirtAndRoassal >> playFor: aNumberOfSteps [ + "play the performance to SuperDIrt/SuperCollider audio engine - default freq is 132 bpm + Performance speed can be changed with p freq: a bpm" + + | process | + performance bpm: 60 / (performance freq * 4). + performance transportStep: 1. + performance activeProcess: ([ + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: performance freq) wait. + + canvasBlock value. + "sequencers scan" + [ + performance valuesDo: [ :seq | + (seq gates wrap: performance transportStep) = 1 + ifTrue: [ + seq playFullDirtEventAt: seq noteIndex. "delta!!!!" + + "increment note Index" + seq noteIndex: seq noteIndex + 1 ] + ifFalse: [ nil ] ] ] forkAt: Processor timingPriority. + + "step is incremented anyway" + performance incrementTransportStep ] ] forkAt: + Processor timingPriority) +] + +{ #category : #initialization } +PerformerSuperDirtAndRoassal >> updateCanvas [ + + | shapes scale ball | + frame := performance transportStep. + + frame % 16 = 0 ifTrue: [ + scale := NSScale linear + domain: { 1. 12}; + range: { 20. 0 }. + ball := self newBall. + canvas add: ball. + ball propertyAt: #s put: (scale scale: performance size) ]. + + frame % 64 = 0 ifTrue: [ mode := mode + 1 % 3 ]. + frame % 128 = 0 ifTrue: [ + canvas color: (canvas color = Color black + ifTrue: [ Color white ] + ifFalse: [ Color black ]). + canvas shouldClearBackground: true. + canvas newAnimation + duration: 100 milliSeconds; + when: RSAnimationEndEvent + do: [ canvas shouldClearBackground: false ] + for: canvas ]. + shapes := canvas shapes copy. + shapes do: [ :e | + | att t s degX degY xs ys hue hue2 | + att := e properties. + t := att at: #t. + s := att at: #s. + degX := att at: #degX. + degY := att at: #degY. + xs := att at: #xs. + ys := att at: #ys. + hue := att at: #hue. + hue2 := att at: #hue2. + + e color: ((color scale: mode) rsValue: e). + e border color: (Color h: 360 - (att at: #hue) s: 1 v: 1). + e size: s. + + t := t + 0.02. + s := s + 0.2. + e translateBy: xs @ ys + (degX cos @ degY cos) * t. + degX := degX + (random rsNext: 0.1 and: 0.8). + degY := degY + (random rsNext: 0.1 and: 0.8). + hue := hue + hue2. + (hue > 360 or: [ hue < 0 ]) ifTrue: [ hue2 := hue2 negated ]. + att + at: #t put: t; + at: #s put: s; + at: #degX put: degX; + at: #degY put: degY; + at: #hue put: hue; + at: #hue2 put: hue2. + + (canvas visibleRectangle intersects: e encompassingRectangle) ifFalse: [ + e remove ] ]. + canvas signalUpdate +] diff --git a/src/Coypu/PerformerSuperDirtAndVisual.class.st b/src/Coypu/PerformerSuperDirtAndVisual.class.st new file mode 100644 index 0000000..f21fa2c --- /dev/null +++ b/src/Coypu/PerformerSuperDirtAndVisual.class.st @@ -0,0 +1,45 @@ +" +EXPERIMENTAL!!!!! +Sends OSC mesages to SuperDirt port and to port 8000 to use for visual with Processing or Hydra or Jitter (just to name a few) +" +Class { + #name : #PerformerSuperDirtAndVisual, + #superclass : #Performer, + #category : #'Coypu-Performance' +} + +{ #category : #playing } +PerformerSuperDirtAndVisual >> play [ +self playDirtAndVisual +] + +{ #category : #'as yet unclassified' } +PerformerSuperDirtAndVisual >> playDirtAndVisual [ + + "play the performance to SuperDIrt/SuperCollider audio engine - default freq is 132 bpm + Performance speed can be changed with p freq: a bpm" + + + performance bpm: 60 / (performance freq * 4). + performance transportStep: 1. + performance activeProcess: ([ + "the perfrormance last 4 days at 120 bpm" + fourDaysAt120BPM timesRepeat: [ + (Delay forSeconds: performance freq) wait. + "sequencers scan" + [ + performance valuesDo: [ :seq | + (seq gates wrap: performance transportStep) = 1 + ifTrue: [ + seq playFullDirtEventAt: seq noteIndex port: 57120 "delta!!!!" ; playFullDirtEventAt: seq noteIndex port: 8000. + + "increment note Index" + seq noteIndex: seq noteIndex + 1 ] + ifFalse: [ nil ] ] ] forkAt: Processor timingPriority. + + "step is incremented anyway" + performance incrementTransportStep ] ] forkAt: Processor timingPriority). + + + +] diff --git a/src/Coypu/Playground4LiveCoding.class.st b/src/Coypu/Playground4LiveCoding.class.st new file mode 100644 index 0000000..93025cd --- /dev/null +++ b/src/Coypu/Playground4LiveCoding.class.st @@ -0,0 +1,35 @@ +" +Minimal Playground for programming music on-the fly. + +" +Class { + #name : #Playground4LiveCoding, + #superclass : #StPlaygroundPagePresenter, + #category : #'Coypu-Playground' +} + +{ #category : #layout } +Playground4LiveCoding class >> defaultLayout [ + + ^ SpBoxLayout newTopToBottom + spacing: 4; + add: #text; + add: #statusbar expand: false; + yourself +] + +{ #category : #accessing } +Playground4LiveCoding class >> defaultTitle [ + + ^ 'Programming music on-the-fly' +] + +{ #category : #initialization } +Playground4LiveCoding >> initializeWindow: aWindowPresenter [ + + super initializeWindow: aWindowPresenter. + + aWindowPresenter + title: 'PROGRAMMING MUSIC ON-THE-FLY'; + initialExtent: self currentWorld width @ 700 +] diff --git a/src/Coypu/PmDeviceInfo.class.st b/src/Coypu/PmDeviceInfo.class.st new file mode 100644 index 0000000..0df7696 --- /dev/null +++ b/src/Coypu/PmDeviceInfo.class.st @@ -0,0 +1,111 @@ +Class { + #name : #PmDeviceInfo, + #superclass : #FFIStructure, + #classVars : [ + 'OFFSET_INPUT', + 'OFFSET_INTERF', + 'OFFSET_ISVIRTUAL', + 'OFFSET_NAME', + 'OFFSET_OPENED', + 'OFFSET_OUTPUT', + 'OFFSET_STRUCTVERSION' + ], + #category : #Coypu +} + +{ #category : #'field definition' } +PmDeviceInfo class >> fieldsDesc [ +^ #( +int structVersion; +char* interf; +char* name; +int input; +int output; +int opened; +int isVirtual; +) +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> input [ + "This method was automatically generated" + ^handle signedLongAt: OFFSET_INPUT +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> input: anObject [ + "This method was automatically generated" + handle signedLongAt: OFFSET_INPUT put: anObject +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> interf [ + "This method was automatically generated" + ^ExternalData fromHandle: (handle pointerAt: OFFSET_INTERF) type: ExternalType char asPointerType +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> interf: anObject [ + "This method was automatically generated" + handle pointerAt: OFFSET_INTERF put: anObject getHandle. +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> isVirtual [ + "This method was automatically generated" + ^handle signedLongAt: OFFSET_ISVIRTUAL +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> isVirtual: anObject [ + "This method was automatically generated" + handle signedLongAt: OFFSET_ISVIRTUAL put: anObject +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> name [ + "This method was automatically generated" + ^ExternalData fromHandle: (handle pointerAt: OFFSET_NAME) type: ExternalType char asPointerType +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> name: anObject [ + "This method was automatically generated" + handle pointerAt: OFFSET_NAME put: anObject getHandle. +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> opened [ + "This method was automatically generated" + ^handle signedLongAt: OFFSET_OPENED +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> opened: anObject [ + "This method was automatically generated" + handle signedLongAt: OFFSET_OPENED put: anObject +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> output [ + "This method was automatically generated" + ^handle signedLongAt: OFFSET_OUTPUT +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> output: anObject [ + "This method was automatically generated" + handle signedLongAt: OFFSET_OUTPUT put: anObject +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> structVersion [ + "This method was automatically generated" + ^handle signedLongAt: OFFSET_STRUCTVERSION +] + +{ #category : #'accessing - structure variables' } +PmDeviceInfo >> structVersion: anObject [ + "This method was automatically generated" + handle signedLongAt: OFFSET_STRUCTVERSION put: anObject +] diff --git a/src/Coypu/PmError.class.st b/src/Coypu/PmError.class.st new file mode 100644 index 0000000..85c1256 --- /dev/null +++ b/src/Coypu/PmError.class.st @@ -0,0 +1,32 @@ +Class { + #name : #PmError, + #superclass : #FFIEnumeration, + #classVars : [ + 'pmBadData', + 'pmBadPtr', + 'pmBufferMaxSize', + 'pmGotData', + 'pmInterfaceNotSupoorted', + 'pmInvalidDeviceId', + 'pmNameConflict', + 'pmNoData', + 'pmNoError', + 'pmNotImplemented' + ], + #category : #Coypu +} + +{ #category : #'enum declaration' } +PmError class >> enumDecl [ + ^ #( + pmNoError 0 + pmNoData 1 + pmGotData 2 + pmInvalidDeviceId 3 + pmBadPtr 4 + pmBadData 5 + pmBufferMaxSize 6 + pmNotImplemented 7 + pmInterfaceNotSupoorted 8 + pmNameConflict 9) +] diff --git a/src/Coypu/PmEvent.class.st b/src/Coypu/PmEvent.class.st new file mode 100644 index 0000000..957080d --- /dev/null +++ b/src/Coypu/PmEvent.class.st @@ -0,0 +1,42 @@ +Class { + #name : #PmEvent, + #superclass : #FFIStructure, + #classVars : [ + 'OFFSET_MESSAGE', + 'OFFSET_TIMESTAMP' + ], + #category : #Coypu +} + +{ #category : #'field definition' } +PmEvent class >> fieldsDesc [ +^ +#( +int message; +int timestamp; +) +] + +{ #category : #'accessing - structure variables' } +PmEvent >> message [ + "This method was automatically generated" + ^handle signedLongAt: OFFSET_MESSAGE +] + +{ #category : #'accessing - structure variables' } +PmEvent >> message: anObject [ + "This method was automatically generated" + handle signedLongAt: OFFSET_MESSAGE put: anObject +] + +{ #category : #'accessing - structure variables' } +PmEvent >> timestamp [ + "This method was automatically generated" + ^handle signedLongAt: OFFSET_TIMESTAMP +] + +{ #category : #'accessing - structure variables' } +PmEvent >> timestamp: anObject [ + "This method was automatically generated" + handle signedLongAt: OFFSET_TIMESTAMP put: anObject +] diff --git a/src/Coypu/PortAudio.class.st b/src/Coypu/PortAudio.class.st new file mode 100644 index 0000000..274794e --- /dev/null +++ b/src/Coypu/PortAudio.class.st @@ -0,0 +1,24 @@ +Class { + #name : #PortAudio, + #superclass : #FFILibrary, + #instVars : [ + 'outStream' + ], + #category : #Coypu +} + +{ #category : #'accessing - platform' } +PortAudio >> getDeviceCount [ +^ self ffiCall: #(int Pa_GetDeviceCount()) +] + +{ #category : #'accessing - platform' } +PortAudio >> macModuleName [ + +^ 'libportaudio.2.dylib' +] + +{ #category : #'accessing - platform' } +PortAudio >> pa_Initialize [ +^ self ffiCall: #(int Pa_Initialize()) +] diff --git a/src/Coypu/PortMidi.class.st b/src/Coypu/PortMidi.class.st new file mode 100644 index 0000000..b5ab8ef --- /dev/null +++ b/src/Coypu/PortMidi.class.st @@ -0,0 +1,206 @@ +" +PortMidi for Pharo, Lucretio's edition +" +Class { + #name : #PortMidi, + #superclass : #FFILibrary, + #instVars : [ + 'outStream' + ], + #category : #'Coypu-MIDI' +} + +{ #category : #'as yet unclassified' } +PortMidi class >> countDevices [ +^ self ffiCall: #(int Pm_CountDevices()) library: '//Users/domenicocipriani/MyStuff/portmidi-master/Release/libportmidi.2.0.3.dylib' +] + +{ #category : #writing } +PortMidi class >> createNewPointerForStreamWithType: pointerType [ + "Create a new instance of FFIExternalValueHolder and an instance of this new class to get a pointer for poiterType." + | midiStream valueHolderClass | + valueHolderClass := FFIExternalValueHolder ofType: pointerType. + midiStream := valueHolderClass new. + ^ midiStream tfPointerAddress. +] + +{ #category : #'as yet unclassified' } +PortMidi class >> getDeviceInfo: aDeviceID [ +^ self ffiCall: #(PmDeviceInfo * Pm_GetDeviceInfo (int aDeviceID)) library: '/Users/domenicocipriani/MyStuff/portmidi-master/Release/libportmidi.2.0.3.dylib' +] + +{ #category : #'as yet unclassified' } +PortMidi class >> malloc: aSize [ +^ self ffiCall: #(void * malloc (size_t aSize)) library: LibC +] + +{ #category : #'as yet unclassified' } +PortMidi class >> message: status and: data1 and: data2 [ +| result| + +result := (((data2 bitShift: 16) bitAnd: 16rFF0000) bitOr: ((data1 bitShift: 8) bitAnd: 16rFF00)) + bitOr: (status bitAnd: 16rFF). +^ result +] + +{ #category : #'as yet unclassified' } +PortMidi class >> openOutput: aDeviceIntID on: aPortMidiStream [ +self ffiCall: +#(PmError Pm_OpenOutput #(void ** aPortMidiStream, int aDeviceIntID, NULL, int 512, NULL, NULL, int 0)) +library: '/Users/domenicocipriani/MyStuff/portmidi-master/Release/libportmidi.2.0.3.dylib' +] + +{ #category : #'as yet unclassified' } +PortMidi class >> pm_Initialize [ +self ffiCall: #(PmError Pm_Initialize ()) library: '/Users/domenicocipriani/MyStuff/portmidi-master/Release/libportmidi.2.0.3.dylib' +] + +{ #category : #'system startup' } +PortMidi class >> start [ +^ self ffiCall: #(PtError Pt_Start(int 1, NULL, NULL)) +library: '/Users/domenicocipriani/MyStuff/portmidi-master/Release/libportmidi.2.0.3.dylib' +] + +{ #category : #'debugging actions' } +PortMidi class >> terminate [ +^ self ffiCall: #(PmError Pm_Terminate()) library: '/Users/domenicocipriani/MyStuff/portmidi-master/Release/libportmidi.2.0.3.dylib' +] + +{ #category : #'as yet unclassified' } +PortMidi class >> traceAllDevices [ + " return an array of MIDI devices" + + | devices | + devices := (0 to: self countDevices - 1) collect: [ :i | + self uniqueInstance getDeviceNameid: i ]. + Transcript + clear. + 0 to: self countDevices - 1 do: [ :i | Transcript show: i; tab. + (self uniqueInstance getDeviceNameid: i) traceCr ]. + + Transcript open +] + +{ #category : #writing } +PortMidi class >> writeShortOn: aPointerToAStream for: aPmMessage [ +"messages is sent out immediately, so timestamp is 0" +^ self ffiCall: #(PmError Pm_WriteShort (void *aPointerToAStream, 0, int aPmMessage)) + library: '/Users/domenicocipriani/MyStuff/portmidi-master/Release/libportmidi.2.0.3.dylib' +] + +{ #category : #'as yet unclassified' } +PortMidi >> close: aMIDIStream [ +^ self ffiCall: #(PmError Pm_Close(void* aMIDIStream)) +] + +{ #category : #'accessing - devices' } +PortMidi >> countDevices [ +^ self ffiCall: #(int Pm_CountDevices()) +] + +{ #category : #ioManagement } +PortMidi >> createNewPointerForStreamWithType: pointerType [ + "Create a new instance of FFIExternalValueHolder and an instance of this new class to get a pointer for poiterType." + | midiStream valueHolderClass | + valueHolderClass := FFIExternalValueHolder ofType: pointerType. + midiStream := valueHolderClass new. + ^ midiStream tfPointerAddress. +] + +{ #category : #'as yet unclassified' } +PortMidi >> createOutStream [ + +outStream := self class createNewPointerForStreamWithType: 'void**'. +^ outStream +] + +{ #category : #'accessing - devices' } +PortMidi >> getDeviceInfo: aDeviceID [ +^ self ffiCall: #(PmDeviceInfo * Pm_GetDeviceInfo (int aDeviceID)) +] + +{ #category : #'accessing - devices' } +PortMidi >> getDeviceNameid: aDeviceID [ + + | name type | + name := (self getDeviceInfo: aDeviceID) name bytesFromCString + utf8Decoded. + (self getDeviceInfo: aDeviceID) input = 0 + ifTrue: [ type := ' is OUTPUT' ] + ifFalse: [ type := 'is INPUT' ]. + ^ name , type +] + +{ #category : #initialization } +PortMidi >> initialize [ +outStream := 99. +] + +{ #category : #'accessing - platform' } +PortMidi >> macModuleName [ + "Returns the name of the PortMidi library for Mac." + ^ 'libportmidi.2.0.3.dylib' +] + +{ #category : #'as yet unclassified' } +PortMidi >> message: status and: data1 and: data2 [ +| result| + +result := (((data2 bitShift: 16) bitAnd: 16rFF0000) bitOr: ((data1 bitShift: 8) bitAnd: 16rFF00)) + bitOr: (status bitAnd: 16rFF). +^ result +] + +{ #category : #'as yet unclassified' } +PortMidi >> openOutput: aDeviceIntID [ +self ffiCall: +#(PmError Pm_OpenOutput #(void ** outStream, int aDeviceIntID, NULL, int 0, NULL, NULL, int 0)) +] + +{ #category : #'as yet unclassified' } +PortMidi >> openOutput: aDeviceIntID on: aPortMidiStream [ +self ffiCall: +#(PmError Pm_OpenOutput #(void ** aPortMidiStream, int aDeviceIntID, NULL, int 0, NULL, NULL, int 0)) +] + +{ #category : #'as yet unclassified' } +PortMidi >> outStream [ +^ outStream . +] + +{ #category : #'as yet unclassified' } +PortMidi >> playNote: aNoteNumber onChannel: aMIDIChannel [ +" play aNoteNumber on channel aChannel for a standard duration of 250 ms" +| noteOn noteOff channel| +channel := aMIDIChannel -1. +noteOn := self message: 144 + aMIDIChannel and: aNoteNumber and: 100. +noteOff := self message: 128 + aMIDIChannel and: aNoteNumber and: 100. + + +] + +{ #category : #'as yet unclassified' } +PortMidi >> pm_Initialize [ +self ffiCall: #(PmError Pm_Initialize ()) +] + +{ #category : #'as yet unclassified' } +PortMidi >> start [ +^ self ffiCall: #(PtError Pt_Start(int 1, NULL, NULL)) +] + +{ #category : #'as yet unclassified' } +PortMidi >> terminate [ +^ self ffiCall: #(PmError Pm_Terminate()) +] + +{ #category : #writing } +PortMidi >> writeOn: aPointerToStream event: aBuffer length: aLength [ +^ self ffiCall: #(PmError Pm_Write(void * aPointerToStream, PmEvent* aBuffer, int aLength)) +] + +{ #category : #'as yet unclassified' } +PortMidi >> writeShortOn: aPointerToAStream for: aPmMessage [ +"messages is sent out immediately, so timestamp is 0" +^ self ffiCall: #(PmError Pm_WriteShort (void *aPointerToAStream, int -1, int aPmMessage)) +] diff --git a/src/Coypu/Process.extension.st b/src/Coypu/Process.extension.st new file mode 100644 index 0000000..c88d7c5 --- /dev/null +++ b/src/Coypu/Process.extension.st @@ -0,0 +1,8 @@ +Extension { #name : #Process } + +{ #category : #'*Coypu' } +Process >> swapWith: anotherProcess [ +"terminate the Process and start anotherProcess" +self terminate. +anotherProcess resume. +] diff --git a/src/Coypu/PtError.class.st b/src/Coypu/PtError.class.st new file mode 100644 index 0000000..df3a91f --- /dev/null +++ b/src/Coypu/PtError.class.st @@ -0,0 +1,17 @@ +Class { + #name : #PtError, + #superclass : #FFIEnumeration, + #classVars : [ + 'error', + 'noerror' + ], + #category : #Coypu +} + +{ #category : #'enum declaration' } +PtError class >> enumDecl [ +^ #( +noerror 0 +error 1 +) +] diff --git a/src/Coypu/Random.extension.st b/src/Coypu/Random.extension.st new file mode 100644 index 0000000..19cc806 --- /dev/null +++ b/src/Coypu/Random.extension.st @@ -0,0 +1,22 @@ +Extension { #name : #Random } + +{ #category : #'*Coypu' } +Random >> nextIntFromZero: anInteger [ +"returns a new random number in the range [0 aNumber]" +^ (self nextInt: (anInteger + 1)) -1. +] + +{ #category : #'*Coypu' } +Random >> nextTrig [ +"return 0 or 1 randomly" +^ self next rounded. +] + +{ #category : #'*Coypu' } +Random >> sign [ +"return 1 or -1 " + | condition | + condition := self nextInt: 2. + (condition = 1) ifTrue: [ ^ 1 ] ifFalse: [ ^ -1] + +] diff --git a/src/Coypu/RandomTests.class.st b/src/Coypu/RandomTests.class.st new file mode 100644 index 0000000..ebc729f --- /dev/null +++ b/src/Coypu/RandomTests.class.st @@ -0,0 +1,13 @@ +Class { + #name : #RandomTests, + #superclass : #TestCase, + #category : #'Coypu-Tests' +} + +{ #category : #tests } +RandomTests >> testRandomNextInt [ + +self assert: ((Random new nextInt: 10) > 0 ) & ((Random new nextInt: 10) < 10 )equals: true + + +] diff --git a/src/Coypu/Rhythm.class.st b/src/Coypu/Rhythm.class.st new file mode 100644 index 0000000..666aa8d --- /dev/null +++ b/src/Coypu/Rhythm.class.st @@ -0,0 +1,137 @@ +" +Rhythm class is to be used to generate Arrays of values in the range 0,1 to be used mainly to feed sequencers. +Deprecated at the moment, send message to Integers to create sequencer-like patterns. + + +about OSC package in use: +Versions Stable 1.0 +################### +Repository +Metacello script +Metacello new + smalltalkhubUser: 'Pharo' project: 'MetaRepoForPharo50'; + configuration: 'OSC'; + version: #stable; + load. during: +" +Class { + #name : #Rhythm, + #superclass : #Array, + #type : #variable, + #category : #'Coypu-Sequencers' +} + +{ #category : #accessing } +Rhythm class >> list [ + + "return all the possible Rhythms obtainable sending messages to integers" + + | rhythmList | + rhythmList := #( 'adowa' 'aksak' 'banda' 'bomba' 'bossa' 'breves' + 'semibreves' 'claveSon' 'downbeats' 'gahu' 'ones' + 'quavers' 'semiquavers' 'rumba' 'shiko' 'sikyi' + 'soukous' 'tresillo' 'trueAksak' 'tumbao' 'upbeats' ). + +Transcript clear. rhythmList do: [ :i | i traceCr ]. Transcript open. +] + +{ #category : #responses } +Rhythm class >> random: aNumber [ +"generates an Array of random values in the range (0,1) of size: aNumber, whith only 2 decimal places" + | r | + r := Random new. + ^ (1 to: aNumber) collect: [ :i | (r next ) printShowingDecimalPlaces: 2]. +] + +{ #category : #'bit manipulation' } +Rhythm >> >> anArray [ +" same as and: anArray, but rewritten to test precedence improvement" +"returns Sequencer with default note 60nn and default durations 1/16th and noteIndex = 0" +^ (Sequencer with: self with: anArray with: #(0.125) with: 0 ) +] + +{ #category : #controlling } +Rhythm >> and: anArray [ +"returns Sequencer with default note 60nn and default durations 1/16th and noteIndex = 0" +^ (Sequencer with: self with: anArray with: #(0.125) with: 0 ) +] + +{ #category : #'as yet unclassified' } +Rhythm >> and: anArrayOfNotes and: anArrayOfDurations [ + + "returns Sequencer with default note 60nn and default durations 1/16th" + + ^ Sequencer new gates: self ; notes: anArrayOfNotes; durations: anArrayOfDurations +] + +{ #category : #converting } +Rhythm >> asDirtArray [ +"convert Rhythm into an Array" +| array | +array := Array new: (self size). +( 1 to: (self size)) do:[:i | array at: i put: (self at: i)]. +^ array +] + +{ #category : #'sequencer creation' } +Rhythm >> notes: anArray [ + + ^ Sequencer new gates: self; notes: anArray +] + +{ #category : #accessing } +Rhythm >> offset: aNumber [ +"offset the rhythm by aNumber of 'steps' wrapping it around its size" +| newRhythm | +newRhythm := Rhythm new: (self size). +0 to: ((self size) - 1) do: [ :i | newRhythm at: ((i + aNumber) modulo: (self size)) put: (self at: i + 1)]. +^ newRhythm . + +] + +{ #category : #printing } +Rhythm >> printOn: aStream [ + + aStream print: self asDirtArray +] + +{ #category : #'as yet unclassified' } +Rhythm >> randomInts: anArrayWithRangeAndMin [ + + "return an array of self size with random integers with range [anArrayWithRangeAndMin at: 1 + anArrayWithRangeAndMin at: 2]" + + | floatsArray result numberOfGates range min | + range := anArrayWithRangeAndMin at: 1. + min := anArrayWithRangeAndMin at: 2. + numberOfGates := self gates. + floatsArray := numberOfGates randoms * range + min. + result := floatsArray collect: [ :i | i rounded ]. + ^ Sequencer new gates: self; notes: result +] + +{ #category : #'as yet unclassified' } +Rhythm >> randomNotesFrom: anArray [ + + "return an array of self size of random notes from anArray" + + | numberOfGates result | + numberOfGates := self gates. + result := (1 to: numberOfGates) collect: [ :i | + anArray at: (Random new nextInt: anArray size) ]. + ^ Sequencer new notes: self; gates: result +] + +{ #category : #'as yet unclassified' } +Rhythm >> randomNotesFrom: anArray octaves: aNumber [ + + "return an array of self size of a random note from anArray + a random octave between 0 and aNumber" + + | numberOfGates result | + numberOfGates := self gates. + result := (1 to: numberOfGates) collect: [ :i | + (anArray at: (Random new nextInteger: anArray size)) + + ((Random new nextInteger: aNumber + 1) - 1 * 12) ]. + ^ Sequencer new + notes: self; + gates: result +] diff --git a/src/Coypu/Scale.class.st b/src/Coypu/Scale.class.st new file mode 100644 index 0000000..2e4f355 --- /dev/null +++ b/src/Coypu/Scale.class.st @@ -0,0 +1,211 @@ +" +A Scale is an Array of Musical Intervals in Number of Seminotes. +Sending the name of the desired Scale to the Scale class returns the desired class +" +Class { + #name : #Scale, + #superclass : #Object, + #type : #variable, + #instVars : [ + 'root' + ], + #category : #'Coypu-ScalesAndChords' +} + +{ #category : #'creating - withoutRoot' } +Scale class >> augmented [ + + ^ #( 0 3 4 7 8 11 ) +] + +{ #category : #'creating - withRoot' } +Scale class >> augmented: aMidiNoteNumber [ +^ #(0 3 4 7 8 11) + aMidiNoteNumber + +] + +{ #category : #'creating - withoutRoot' } +Scale class >> bebop [ +^ #(0 2 4 5 7 9 10 11) +] + +{ #category : #'creating - withRoot' } +Scale class >> bebop: aMidiNoteNumber [ +^ #(0 2 4 5 7 9 10 11) + aMidiNoteNumber +] + +{ #category : #'creating - withoutRoot' } +Scale class >> blues [ +^ #(0 3 5 6 7 10) +] + +{ #category : #'creating - withRoot' } +Scale class >> blues: aMidiNoteNumber [ + +^ #(0 3 5 6 7 10) + aMidiNoteNumber +] + +{ #category : #'creating - withRoot' } +Scale class >> blues: aMidiNoteNumber octaves: numberOfOctaves [ + +| x scale| +scale := #(0 3 5 6 7 10). +x := OrderedCollection new. +(1 to: numberOfOctaves ) do: [ :i | x addAll: (scale + (aMidiNoteNumber * i) ) ]. +^ x +] + +{ #category : #'creating - withoutRoot' } +Scale class >> chromatic [ +^ #(0 1 2 3 4 5 6 7 8 9 10 11) +] + +{ #category : #'creating - withRoot' } +Scale class >> chromatic: aMidiNoteNumber [ +^ #(0 1 2 3 4 5 6 7 8 9 10 11) + aMidiNoteNumber +] + +{ #category : #'creating - withoutRoot' } +Scale class >> enigmatic [ +^ #(0 1 4 6 8 10 11) +] + +{ #category : #'creating - withRoot' } +Scale class >> enigmatic: aMidiNoteNumber [ +^ #(0 1 4 6 8 10 11) + aMidiNoteNumber +] + +{ #category : #'creating - withoutRoot' } +Scale class >> flamenco [ + +^ #(0 1 4 5 7 8 11) +] + +{ #category : #'creating - withRoot' } +Scale class >> flamenco: aMidiNoteNumber [ +^ (self flamenco) + aMidiNoteNumber +] + +{ #category : #'creating - withoutRoot' } +Scale class >> gypsy [ +^ #(0 2 3 6 7 8 10) +] + +{ #category : #'creating - withRoot' } +Scale class >> gypsy: aMidiNoteNumber [ +^ #(0 2 3 6 7 8 10) + aMidiNoteNumber +] + +{ #category : #'creating - withoutRoot' } +Scale class >> hirajoshi [ +" a tuning scale adapted from shamisen music by Yatsuhashi Kengyō for tuning of the koto." +^ #( 0 4 6 7 11) +] + +{ #category : #'creating - withRoot' } +Scale class >> hirajoshi: aMidiNoteNumber [ +" a tuning scale adapted from shamisen music by Yatsuhashi Kengyō for tuning of the koto." +^ #( 0 4 6 7 11) + aMidiNoteNumber +] + +{ #category : #'creating - withoutRoot' } +Scale class >> insen [ +"a tuning scale adapted from shamisen music by Yatsuhashi Kengyō for tuning of the koto. It only differs from the hirajoshi scale by one note." +^ #(0 1 5 7 10 ) + +] + +{ #category : #'creating - withRoot' } +Scale class >> insen: aMidiNoteNumber [ +"a tuning scale adapted from shamisen music by Yatsuhashi Kengyō for tuning of the koto. It only differs from the hirajoshi scale by one note." +^ #(0 1 5 7 10 ) + aMidiNoteNumber + +] + +{ #category : #'creating - withoutRoot' } +Scale class >> istrian [ +^ #(0 1 3 4 6) +] + +{ #category : #'creating - withRoot' } +Scale class >> istrian: aMidiNoteNumber [ +^ #(0 1 3 4 6) + aMidiNoteNumber +] + +{ #category : #'creating - withoutRoot' } +Scale class >> major [ + +^ #(0 2 4 5 7 9 11). +] + +{ #category : #'accessing - structure variables' } +Scale class >> major: aMidiNoteNumber [ + +^ #(0 2 4 5 7 9 11 ) + aMidiNoteNumber. +] + +{ #category : #'creating - withoutRoot' } +Scale class >> minor [ + + ^ #( 0 2 3 5 7 8 10 12 ) +] + +{ #category : #'accessing - structure variables' } +Scale class >> minor: aMidiNoteNumber [ + +^ #(0 2 3 5 7 8 10) + aMidiNoteNumber +] + +{ #category : #'creating - withoutRoot' } +Scale class >> pentaMajor [ +^ #(0 2 4 7 9 ) +] + +{ #category : #'creating - withRoot' } +Scale class >> pentaMajor: aMidiNoteNumber [ +^ #(0 2 4 7 9) + aMidiNoteNumber +] + +{ #category : #'creating - withoutRoot' } +Scale class >> pentaMinor [ +^ #(0 3 5 7 10 ) +] + +{ #category : #'creating - withoutRoot' } +Scale class >> pentaMinor: aMidiNoteNumber [ +^ #(0 3 5 7 10 ) + aMidiNoteNumber +] + +{ #category : #'creating - withoutRoot' } +Scale class >> persian [ +^ #(0 1 4 5 6 8 11) +] + +{ #category : #'creating - withoutRoot' } +Scale class >> persian: aMidiNoteNumber [ +^ #(0 1 4 5 6 8 11) + aMidiNoteNumber +] + +{ #category : #'creating - withoutRoot' } +Scale class >> sakura [ +"also known as 'in scale' is a traditiona pentatonic scale of Japanese music, specifically for joto and shamisen " +^ #(0 1 5 7 8 ) +] + +{ #category : #'creating - withoutRoot' } +Scale class >> sakura: aMidiNoteNumber [ +"also known as 'in scale' is a traditiona pentatonic scale of Japanese music, specifically for joto and shamisen " +^ #(0 1 5 7 8 ) + aMidiNoteNumber +] + +{ #category : #'creating - withoutRoot' } +Scale class >> yo [ +" A pentatonic scale used in much Japanese music including gagaku and shomyo. it does not contain minor notes" +^ #(0 3 5 7 10 ) +] + +{ #category : #'creating - withoutRoot' } +Scale class >> yo: aMidiNoteNumber [ +" A pentatonic scale used in much Japanese music including gagaku and shomyo. it does not contain minor notes" +^ #(0 3 5 7 10 ) + aMidiNoteNumber +] diff --git a/src/Coypu/SequenceableCollection.extension.st b/src/Coypu/SequenceableCollection.extension.st new file mode 100644 index 0000000..02a430e --- /dev/null +++ b/src/Coypu/SequenceableCollection.extension.st @@ -0,0 +1,16 @@ +Extension { #name : #SequenceableCollection } + +{ #category : #'*Coypu' } +SequenceableCollection >> asOSCMessageForSuperDirt [ + + + | localAddress scLangPort values| + localAddress := '127.0.0.1'. + scLangPort := 57120. + values := OrderedCollection new. + values add: '/dirt/play'. + self do: [:i | values add: i]. + + + (OSCMessage for: values asDirtArray ) sendToAddressString: localAddress port: scLangPort +] diff --git a/src/Coypu/Sequencer.class.st b/src/Coypu/Sequencer.class.st new file mode 100644 index 0000000..88bff31 --- /dev/null +++ b/src/Coypu/Sequencer.class.st @@ -0,0 +1,1276 @@ +" +A Sequencer contains gates, notes, duration and a noteIndex. extra1 and extra2 controls can be added. +A Sequencer can contain a MIDI channel, as intended to play it on a Performance for a MIDI sender. +A Sequencer contains a template for a message for the SuperDirt audio engine. + +The Sequencer is the basic unit in Performance. + + +It is conceived as something in between a Digital Audio Workstation track and a ""clip"". +" +Class { + #name : #Sequencer, + #superclass : #Object, + #type : #variable, + #instVars : [ + 'seqKey', + 'extra1String', + 'extra2String', + 'extra1', + 'extra2', + 'notes', + 'durations', + 'noteIndex', + 'gates', + 'midiChannel', + 'visualGroup', + 'cycleDurations', + 'seqChords', + 'soundPattern', + 'samplesIndex', + 'dirtMessage', + 'orbit', + 'isPoly', + 'phaustGateDestination', + 'phaustNoteDestination', + 'kymaMessage' + ], + #classInstVars : [ + 'orbit' + ], + #category : #'Coypu-Sequencers' +} + +{ #category : #accessing } +Sequencer class >> orbit [ + +orbit isNil ifTrue: [ ^ 0 ] ifFalse: [^ orbit] +] + +{ #category : #accessing } +Sequencer class >> orbit: anInteger [ + + orbit := anInteger % 12 +] + +{ #category : #replication } +Sequencer >> * anInteger [ + + "returns a sequencer containing anInteger repetition of self - polysemic bunary version of times:" + + | newGates newNotes newDurations | + newGates := (self gates times: anInteger) asRhythm. + newNotes := self notes times: anInteger. + newDurations := self durations times: anInteger. + + + + ^ SequencerMono new + gates: newGates; + notes: newNotes; + durations: newDurations; + noteIndex: 0 +] + +{ #category : #arithmetic } +Sequencer >> + aString [ + self shouldBeImplemented. +] + +{ #category : #combination } +Sequencer >> , anotherSequencer [ + + "combine the gates of two sequencers, returns a sequencer" + + | newGates newNotes newDurations notes1 notes2 | + notes1 := self notes. + notes2 := anotherSequencer notes. + newGates := self gates , anotherSequencer gates. + newDurations := self durations , anotherSequencer durations. + newNotes := notes1 , notes2. + ^ self class new + gates: newGates; + notes: newNotes; + durations: newDurations; + noteIndex: 0 +] + +{ #category : #comparing } +Sequencer >> < anotherArray [ + + "same as extra1 but binary!" + + " add array of extravalues to the Sequencer +the first element of the array should be a string - for example 'Index'' - the second element o the array should be an array of numbers - for xample #( 1 2 3) +" + + ^ self extra1: anotherArray +] + +{ #category : #comparing } +Sequencer >> = anotherSequencer [ +" equality between sequencers" +((self notes = anotherSequencer notes) & (self gates = anotherSequencer gates) & (self durations = anotherSequencer durations) & (self extra1 = anotherSequencer extra1) & (self extra2 = anotherSequencer extra2)) ifTrue: [ ^ true ] ifFalse: [ ^ false ] +] + +{ #category : #addNotesToGates } +Sequencer >> > anArrayOfNotesOrANote [ + + " change notes in the sequencer - polysemic binary version of notes:" + anArrayOfNotesOrANote isArray ifTrue: + [notes := anArrayOfNotesOrANote] ifFalse: [ notes := Array new: self numberOfTrigs withAll: anArrayOfNotesOrANote ] +] + +{ #category : #adding } +Sequencer >> add: anAssociation [ +" add an associtation of a parameter and a value or an array of values to the dirtMessage" +dirtMessage add: anAssociation + +] + +{ #category : #modifying } +Sequencer >> allNotes: anInteger [ + + "fill sequencer notes with anArray with all equals notenumbers" + + | newNotes | + newNotes := Array new: self numberOfGates. + 1 to: newNotes size do: [ :i | newNotes at: i put: anInteger ]. + self notes: newNotes. +] + +{ #category : #converting } +Sequencer >> asPolySeq [ + + +self subclassResponsibility +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> bpf: aFloatOrAnArray [ +" add bandpass filter effect to the Sequencer at aFloatOrArray frequency" + self dirtMessage at: 'bandf' put: aFloatOrAnArray asDirtArray +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> bpq: aFloatOrAnArray [ +" add bandpass filter effect to the Sequencer with aFloatOrArray frequency" + self dirtMessage at: 'bpq' put: aFloatOrAnArray asDirtArray +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> ccn: anIntOrAnArray ccv: anotherIntOrArray [ +" add a cc value/array of values to a cc number - to use with MIDI" + self dirtMessage at: 'ccn' put: anIntOrAnArray asDirtArray. + self dirtMessage at: 'ccv' put: anotherIntOrArray asDirtArray . +] + +{ #category : #accessing } +Sequencer >> chords: aStringWithOneOrMoreChords [ +" note and chord type separated by an hyphen for example 'c-maj d#-min sus4 min7')" +| arrayOfChords | +arrayOfChords := (aStringWithOneOrMoreChords chordsToArrays) . +seqChords := arrayOfChords . + + ^ self asPolySeq . + +] + +{ #category : #accessing } +Sequencer >> chords: aStringWithOneOrMoreChords octave: anIntegerOrAnArray [ +" note and chord type separated by an hyphen for example 'c-maj d#-min sus4 min7')" +| arrayOfChords | +arrayOfChords := (aStringWithOneOrMoreChords chordsToArrays) . +. +seqChords := arrayOfChords + (anIntegerOrAnArray * 12) . + + ^ self asPolySeq . + +] + +{ #category : #'sequencer - cycles' } +Sequencer >> cycleDurations [ +" return the duration of each event in the cycle" + ^ cycleDurations +] + +{ #category : #'sequencer - cycles' } +Sequencer >> cycleDurations: anArrayOfFractions [ + +cycleDurations := anArrayOfFractions +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> delay: aFloatOrAnArray [ +" add delay wet/dry pattern to the Sequencer" + self dirtMessage at: 'delay' put: aFloatOrAnArray asDirtArray +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> delayFb: aFloatOrAnArray [ +" add delay feedback pattern to the Sequencer" + self dirtMessage at: 'delayfb' put: aFloatOrAnArray asDirtArray +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> delayTime: aFloatOrAnArray [ +" add delay time pattern to the Sequencer" + self dirtMessage at: 'delaytime' put: aFloatOrAnArray asDirtArray +] + +{ #category : #'as yet unclassified' } +Sequencer >> dirt: anArrayForSuperDirt [ +"fills the OSC dirtmessage for SuperDirt with suitable values" +| keys values msgExtension msgExtensionSize | +keys := OrderedCollection new. +values := OrderedCollection new. +msgExtension := OrderedCollection new. + +msgExtensionSize := anArrayForSuperDirt size /2. + + +anArrayForSuperDirt doWithIndex: [ :value :i| (i odd ) ifTrue: [keys add: value ] ifFalse: +[ value isNumber ifTrue: [ values add: value asFloat asDirtArray ] ifFalse: [ values add: value asDirtArray ] ] +]. + +1 to: msgExtensionSize do: [:i | dirtMessage at: (keys at: i) put: (values at: i)] . + + + +] + +{ #category : #'as yet unclassified' } +Sequencer >> dirt: anArrayForSuperDirt to: aKeyInPerformance [ +"convenience method to avoid extra parenthesis" +(self dirt: anArrayForSuperDirt ) to: aKeyInPerformance +] + +{ #category : #accessing } +Sequencer >> dirtMessage [ + + ^ dirtMessage +] + +{ #category : #accessing } +Sequencer >> dirtMessage: anArrayOfAssociationForSuperDirt [ +" template for OSC messages for SuperDirt" + + dirtMessage := anArrayOfAssociationForSuperDirt +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> dirtNotes: aStringWithNotes [ + "add into a Sequencer anotes from a String in which numbers can be multiplicated" + + | tokens dNotes arrayOfNotes | + tokens := aStringWithNotes splitOn: ','. + dNotes := ' '. + tokens do: [ :i | + (i includes: $*) + ifTrue: [ dNotes := dNotes , i multiplyStringsInString ] + ifFalse: [ [ dNotes := dNotes , i ] ] ]. + + arrayOfNotes := (dNotes findBetweenSubstrings: ' ') collect: [ :each | + each asNumber ]. + self notes: arrayOfNotes asArray. + self dirtMessage at: 'n' put: arrayOfNotes asDirtArray. + + ^ self +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> djf: aFloatOrAnArray [ +"Made by Alex McLean. A fun classic DJ Filter. Low pass filter for the first half of the range, high pass for the rest:" + self dirtMessage at: 'djf' put: aFloatOrAnArray asDirtArray +] + +{ #category : #accessing } +Sequencer >> duration [ + + ^ self durations at: (self noteIndex modulo: self durationsSize) +] + +{ #category : #accessing } +Sequencer >> durations [ + +^ durations +] + +{ #category : #accessing } +Sequencer >> durations: anArray [ + +durations := anArray . +] + +{ #category : #accessing } +Sequencer >> durationsSize [ + + ^ durations size +] + +{ #category : #accessing } +Sequencer >> extra1 [ + + " returns extra1 if it is defined, otherwise returns a dummy array" + + ^ extra1 +] + +{ #category : #accessing } +Sequencer >> extra1: anArrayWithStringAndArray [ + + "example #('cutoff' #(99 111 87 56))" + + + extra1 := anArrayWithStringAndArray +] + +{ #category : #accessing } +Sequencer >> extra1: aSymbol values: anArrayOfValues [ + + "example + extra1: #cutoff values: #(300 600 800). + " + + | x1 | + x1 := Array with: aSymbol with: anArrayOfValues . + extra1 := x1 +] + +{ #category : #accessing } +Sequencer >> extra1Size [ + +"return the size of the array of values for extra1" +| values | +values := extra1 at: 2. + ^ extra1 notNil + ifTrue: [ ^ values size ] + ifFalse: [ ^ 1 ] +] + +{ #category : #accessing } +Sequencer >> extra1String [ + + ^ extra1String +] + +{ #category : #accessing } +Sequencer >> extra1String: aString [ +"allows the definition of extra1 sufffix" +extra1String := aString. +] + +{ #category : #accessing } +Sequencer >> extra2 [ + + " returns extra1 if it is defined, otherwise returns a dummy array" + + ^ extra2 +] + +{ #category : #accessing } +Sequencer >> extra2: anArrayWithStringAndArray [ + + "example #('cutoff' #(99 111 87 56))" + + extra2 := anArrayWithStringAndArray +] + +{ #category : #accessing } +Sequencer >> extra2Size [ + + "return the size of the array of values for extra2" + + | values | + values := extra2 at: 2. + ^ extra1 notNil + ifTrue: [ ^ values size ] + ifFalse: [ ^ 1 ] +] + +{ #category : #accessing } +Sequencer >> extra2String [ + + ^ extra2String +] + +{ #category : #accessing } +Sequencer >> extra2String: aString [ +"allows the definition of extra1 sufffix" +extra2String := aString. +] + +{ #category : #'Sequencer - transformation' } +Sequencer >> flip [ + + " flip the gates in a Sequener: rests become gates, gates become rests" + + | result | + result := self gates collect: [ :i | (i ~= 1) asInteger ] . + + ^ result asRhythm asSeq. +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> gain: aFloatOrAnArray [ +" add gain pattern to the Sequencer" + self dirtMessage at: 'gain' put: aFloatOrAnArray asDirtArray +] + +{ #category : #accessing } +Sequencer >> gates [ + + ^ gates +] + +{ #category : #modifying } +Sequencer >> gates: aRhythm [ + + "change the rhythm inside the sequencer" + + gates := aRhythm +] + +{ #category : #accessing } +Sequencer >> gatesSize [ + + ^ gates size +] + +{ #category : #'LiveCoding - sequencer' } +Sequencer >> gatesSize: anInteger [ + self shouldBeImplemented. +] + +{ #category : #accessing } +Sequencer >> hasChords [ + +| result | +result := false. + self chords isNil + ifTrue: [ ^ false ] + ifFalse: [ ^ true ]. + ^ result +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> hold: aFloatOrAnArray [ +" add hold ASR envelope pattern to the Sequencer" + self dirtMessage at: 'rel' put: aFloatOrAnArray asDirtArray +] + +{ #category : #'accessing - sequencer' } +Sequencer >> index [ + + ^ (self extra1 at: 2) +] + +{ #category : #'LiveCoding - sequencer' } +Sequencer >> index: anArrayOfIndexes [ + self extra1: #Index values: anArrayOfIndexes +] + +{ #category : #'LiveCoding - Performance' } +Sequencer >> index: anArrayOfIndexes to: aKeyInPerformance [ + "convenience method to avoid extra parenthesis to put a sequencer of gates with sample indexes into a performance" + | p | + p := Performance uniqueInstance . + self to: aKeyInPerformance . + aKeyInPerformance indexes: anArrayOfIndexes +] + +{ #category : #initialization } +Sequencer >> initialize [ + +super initialize . +isPoly := false. +dirtMessage := Dictionary new. + +dirtMessage at: 'cps' put: Performance uniqueInstance freq * 4. +"superdirt orbits are 12" +self class orbit: self class orbit + 1. +orbit := self class orbit. + +"dirtMessage addAll: { '_id' -> 1 . 'cps' -> 0.56 . 'orbit' -> 0 }" +] + +{ #category : #accessing } +Sequencer >> isPoly [ + + ^ isPoly +] + +{ #category : #accessing } +Sequencer >> isPoly: aBolean [ + + isPoly := aBolean +] + +{ #category : #'LiveCoding - sequencer' } +Sequencer >> isPolySeq [ + +self subclassResponsibility +] + +{ #category : #testing } +Sequencer >> isSequencer [ + +^ true +] + +{ #category : #accessing } +Sequencer >> kymaMessage [ + + ^ kymaMessage +] + +{ #category : #accessing } +Sequencer >> kymaMessage: anObject [ + + kymaMessage := anObject +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> lock: aFloatOrAnArray [ +" add lock pattern to the Sequencer - a pattern of numbers. Specifies whether delaytime is calculated relative to bpm. When set to 1, delaytime is a direct multiple of a bar." + self dirtMessage at: 'lock' put: aFloatOrAnArray asDirtArray +] + +{ #category : #modifying } +Sequencer >> melodyFrom: aScale [ + + | array | + array := self numberOfGates randomNotesFrom: aScale. + array at: 1 put: (aScale at: 1). + 2 to: array size do: [ :i | + ((array at: i) rem: 12) = 0 ifTrue: [ + array at: i - 1 put: (aScale at: 6) ]. + ((array at: i) rem: 12) = 2 ifTrue: [ + array at: i - 1 put: (aScale at: 5) ] ]. + "last note is mediante or dominante" + ^ self notes: array +] + +{ #category : #'LiveCoding - Performance' } +Sequencer >> midiCh: anInteger to: aKeyInPerformance [ + + " convenience method to avoid extra parenthesis when assigning a midiChannel to akey in a performance" + | p | +p := Performance uniqueInstance . + p at: aKeyInPerformance put: (self midiChannel: anInteger) . +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> midiChan: anIntegerOrAnArrayOfIntegers [ +" add midichannel for SuperDirt to the Sequencer " + self dirtMessage at: 'midichan' put: (anIntegerOrAnArrayOfIntegers -1) asDirtArray. + " the Sequencer will pe played from the dirtMidiDevice assigned to the Sequencer class" + self dirtMessage at: 's' put: (Performance uniqueInstance dirtMIDIDevice ). + "id sequencer has no note values" + (self dirtMessage includesKey: 'n') ifFalse: [ (self dirtNotes: 0)]. + ^ self + +] + +{ #category : #'LiveCoding - Performance' } +Sequencer >> midiChan: anInteger to: aKeyInPerformance [ + + " for use with SuperDirt + convenience method to avoid extra parenthesis when assigning a midiChan to akey in a performance" + | p | +p := Performance uniqueInstance . + p at: aKeyInPerformance put: (self midiChan: anInteger) . +] + +{ #category : #accessing } +Sequencer >> midiChannel [ + +^ midiChannel +] + +{ #category : #accessing } +Sequencer >> midiChannel: anIntegerBetween1And16 [ + + midiChannel := anIntegerBetween1And16 +] + +{ #category : #accessing } +Sequencer >> note [ + + ^ self notes at: (self noteIndex modulo: self notesSize) +] + +{ #category : #accessing } +Sequencer >> noteIndex [ + " returns the fourth array of the sequencer if there is one, otherwise returns 0. +noteIndex is used to play the folloowing note in the array. Only if there is a trig in the gates the noteIndex will be incremented" + + ^ noteIndex +] + +{ #category : #accessing } +Sequencer >> noteIndex: anInteger [ + + " returns the fourth array of the sequencer if there is one, otherwise returns 0. +noteIndex is used to play the folloowing note in the array. Only if there is a trig in the gates the noteIndex will be incremented" + +noteIndex := anInteger +] + +{ #category : #accessing } +Sequencer >> notes [ + + ^ notes +] + +{ #category : #accessing } +Sequencer >> notes1: anObject [ + + notes := anObject +] + +{ #category : #modifying } +Sequencer >> notes: anArrayOfNotesOrANote [ + + " change notes in the sequencer" + + anArrayOfNotesOrANote isArray + ifTrue: [ notes := anArrayOfNotesOrANote ] + ifFalse: [ + notes := Array + new: self numberOfTrigs + withAll: anArrayOfNotesOrANote ] +] + +{ #category : #'as yet unclassified' } +Sequencer >> notes: anArrayOfNotes durs: anArrayOfDurations [ +" change notes in the sequencer" +^ (Sequencer with: (self at: 1) with: anArrayOfNotes with: anArrayOfDurations with: 0). +] + +{ #category : #'LiveCoding - Performance' } +Sequencer >> notes: anArrayOfNotes index: anArrayOfIndexes to: aKeyInPerformance [ + "convenience method to avoid extra parenthesis while declaring a sequencer of gates with its notes and its sample indexes" + | p | + p := Performance uniqueInstance . + self to: aKeyInPerformance . + aKeyInPerformance notes: anArrayOfNotes . + aKeyInPerformance indexes: anArrayOfIndexes +] + +{ #category : #'LiveCoding - Performance' } +Sequencer >> notes: anArrayOfNotes midiCh: anInteger to: aKeyInPerformance [ + "convenience method to avoid extra parenthesis" + +((self notes: anArrayOfNotes ) midiChannel: anInteger) to: aKeyInPerformance . +] + +{ #category : #'LiveCoding - Performance' } +Sequencer >> notes: anArrayOfNotes mode: anArrayOfModes to: aKeyInPerformance [ + "convenience mode to avoid extra parenthesis to put chord modes into sequencer - designed for polyphonic synths" + +(( self notes: anArrayOfNotes ) extra1: { #Mode . anArrayOfModes } ) to: aKeyInPerformance +] + +{ #category : #'LiveCoding - sequencer' } +Sequencer >> notes: anArrayOfNotes to: aKeyInThePerformance [ + " convenience method to fill keys in performance without using too many parenthesis" + | p | + p := Performance uniqueInstance . + self to: aKeyInThePerformance . + aKeyInThePerformance notes: anArrayOfNotes +] + +{ #category : #accessing } +Sequencer >> notesSize [ + + ^ notes size +] + +{ #category : #counting } +Sequencer >> numberOfGates [ + +"return number of elements in the Sequencer gates array that are greater than 0" +| result size | +result := (self gates) select: [ :i | i > 0 ]. +size := result size. +^ size +] + +{ #category : #counting } +Sequencer >> numberOfTrigs [ + + "return number of elements in the Sequencer gates array that are greater than 0" + + | result size | + result := self gates select: [ :i | i > 0 ]. + size := result size. + ^ size +] + +{ #category : #'Sequencer - transformation' } +Sequencer >> offset: aNumber [ + + "offset the rhythm by aNumber of 'steps' wrapping it around its size" + + | newArray | + newArray := Array new: self gates size. + 1 to: self gates size do: [ :i | newArray at: ((i + aNumber ) modulo: self gates size) put: (self gates at: i) ]. + self gates: newArray +] + +{ #category : #accessing } +Sequencer >> orbit [ + + ^ orbit +] + +{ #category : #accessing } +Sequencer >> orbit: anObject [ + + orbit := anObject +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> pan: aFloatOrAnArray [ +" add gain pattern to the Sequencer" + self dirtMessage at: 'pan' put: aFloatOrAnArray asDirtArray +] + +{ #category : #accessing } +Sequencer >> phaustGateDestination [ + + ^ phaustGateDestination +] + +{ #category : #accessing } +Sequencer >> phaustGateDestination: aString [ + + phaustGateDestination := aString +] + +{ #category : #accessing } +Sequencer >> phaustNoteDestination [ + + ^ phaustNoteDestination +] + +{ #category : #accessing } +Sequencer >> phaustNoteDestination: anObject [ + + phaustNoteDestination := anObject +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> pitch: anIntOrAnArray [ +" add speed pattern to the Sequencer - change speed of samples playback but in chromatic way!" + | speeds | + speeds := anIntOrAnArray collect: [ :i | i semitonesToSpeed ]. + self dirtMessage at: 'speed' put: speeds asDirtArray +] + +{ #category : #'sequencer - cycles' } +Sequencer >> playAsCycle [ + +] + +{ #category : #'sequencer - cycles' } +Sequencer >> playAsCycle: aNumberOfCycles on: aMIDISender [ + + "playMIDISequenceAt: aRateInSeconds steps: aNumberOfSteps on: aMIDISender" + + "test" + + " iterate over a process for self times, at a rate of aRateInMilliSeconds for step, through athe Performance containing arrays of numbers as values +keys in the Performance represents MIDI channels and must be written as #ch1 #ch2 #ch3 and so on if you want to send noteOn/off or + +if you want to send out ccs +" + + | step gateTime | + "as in early hardware sequencers, default gatetime is 80% of the step duration" + gateTime := 0. "we dont use it for now" + step := 0. + + + ^ [ + aNumberOfCycles timesRepeat: [ + (Delay forSeconds: (Cycle duration) + * (self cycleDurations at: (step modulo: self cycleDurations size))) + wait. + + " test if dictionary values is an Array of two arrays or as defined, a sequence" + + + (self gates at: (step modulo: self gatesSize)) = 1 ifTrue: [ + aMIDISender + playDrum: + (self notes at: (self noteIndex modulo: self notesSize)) + onChannel: self midiChannel. + "advance" + self noteIndex: self noteIndex + 1 ]. + + + step := step + 1 ] ] forkAt: Processor timingPriority +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> playFullDirtEventAt: anIndex [ + +self subclassResponsibility . +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> playFullDirtEventAt: anIndex delta: aRateInSeconds [ + + "sends a mesaage to SuperDirt with all the desired OSC arguments and values" +| message dur | +message := OrderedCollection new. +message add: '/dirt/play'. +dur := self durations asDirtArray wrap: anIndex . +message add: 'delta'; add: (aRateInSeconds * dur) asFloat asDirtArray . +dirtMessage keysAndValuesDo: [ :key :value | message add: key; add: (value asDirtArray wrap: anIndex ) ]. + + "| index sound message delta| + index := 'n' -> (self samplesIndex wrap: anIndex) asFloat. + sound := 'sound' -> (self soundPattern wrap: anIndex). + delta := 'delta' -> 0.125. change it to have changing delta values + message := OrderedCollection new. +message addAll: { '_id' . '1' . 'cps' . 0.56 . 'delta' . 0.125 . 'orbit' . 0 . 's' . sound . 'n' . index}. " + + message asOSCMessageForSuperDirt . + ^ true +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> playFullDirtEventAt: anIndex port: aPort [ + + "sends a mesaage to SuperDirt with all the desired OSC arguments and values" +| message dur | +message := OrderedCollection new. +message add: '/dirt/play'. +dur := self durations asDirtArray wrap: anIndex . + +message add: 'delta'; add: 0.123. "delta should change" +dirtMessage keysAndValuesDo: [ :key :value | message add: key; add: (value asDirtArray wrap: anIndex ) ]. + + "| index sound message delta| + index := 'n' -> (self samplesIndex wrap: anIndex) asFloat. + sound := 'sound' -> (self soundPattern wrap: anIndex). + delta := 'delta' -> 0.125. change it to have changing delta values + message := OrderedCollection new. +message addAll: { '_id' . '1' . 'cps' . 0.56 . 'delta' . 0.125 . 'orbit' . 0 . 's' . sound . 'n' . index}. " + + (OSCBundle for: { OSCMessage for: message } ) sendToAddressString: '127.0.0.1' port: aPort. + ^ true +] + +{ #category : #'LiveCoding - Performance' } +Sequencer >> playLocalEventAt: anIndex [ + +self subclassResponsibility +] + +{ #category : #'LiveCoding - Performance' } +Sequencer >> playMIDIEventAt: anIndex [ + +self subclassResponsibility +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> playToDirt: aNumberOfSteps rate: aRateInSeconds [ + + "play the sequencer to the SuperDirt/SuperCollider synth engine" + + | process step | + step := 1. + + process := [ + aNumberOfSteps timesRepeat: [ + (Delay forSeconds: aRateInSeconds) wait. + (self gates wrap: step) = 1 + ifTrue: [ + self playBasicDirtEventAt: self noteIndex. + "increment note Index" + self noteIndex: self noteIndex + 1 ] + ifFalse: [ nil ]. + + "step is incremented anyway" + step := step + 1 ] ] forkAt: Processor timingPriority. + + ^ process +] + +{ #category : #printing } +Sequencer >> printOn: aStream [ + + super printOn: aStream. + aStream nextPutAll: ' with gates: '. + aStream print: gates. + aStream nextPutAll: ' with notes: '. + aStream print: notes. +] + +{ #category : #progressions } +Sequencer >> progression: anArrayOfIntervals [ + + "return a new Sequencer that join together the original sequencer with as many transpositions of self for as many intervals in anArrayOfIntervals" + + | oldSeq newSeq | + oldSeq := self. + newSeq := self. + (1 to: anArrayOfIntervals size) do: [ :i | + newSeq := newSeq , (oldSeq transpose: (anArrayOfIntervals at: i)) ]. + ^ newSeq +] + +{ #category : #'as yet unclassified' } +Sequencer >> randomCounterPoint [ + + " creates aSequencer of trigs randomly counterpointed with self array" + + | newArray | + newArray := Array new: self gates size. + 1 to: self gates size do: [ :i | + (self gates at: i) = 1 + ifTrue: [ newArray at: i put: 0 ] + ifFalse: [ newArray at: i put: Random new nextTrig ] ]. + ^ newArray asSeq +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> randomSamplesFromFolder: aStringForAFolderOfSamples [ +"returns a random number (between 2 and 512) of indexes from the selected folder inside Dirt-Samples" +| randIndex folder folderSize| + randIndex := Random new nextIntegerBetween: 2 and: 512. + folder := SuperDirt samplesFolder / aStringForAFolderOfSamples . + folderSize := folder asFileReference allChildren size. + self sound: aStringForAFolderOfSamples ; dirtNotes: (randIndex randomInts: folderSize) . +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> rel: aFloatOrAnArray [ +" add release ASR envelope pattern to the Sequencer" + self dirtMessage at: 'rel' put: aFloatOrAnArray asDirtArray +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> room: aFloatOrAnArray [ +" add room size reverb pattern to the Sequencer" + self dirtMessage at: 'room' put: aFloatOrAnArray asDirtArray +] + +{ #category : #accessing } +Sequencer >> samplesIndex [ + + ^ samplesIndex +] + +{ #category : #accessing } +Sequencer >> samplesIndex: anArrayOfIntegers [ + + samplesIndex := anArrayOfIntegers +] + +{ #category : #accessing } +Sequencer >> seqChords [ + + ^ seqChords +] + +{ #category : #accessing } +Sequencer >> seqChords: anObject [ + + seqChords := anObject +] + +{ #category : #accessing } +Sequencer >> seqKey [ +^seqKey +] + +{ #category : #accessing } +Sequencer >> seqKey: anObject [ + + seqKey := anObject +] + +{ #category : #counting } +Sequencer >> size [ + + "polysemic version of numberOfTrigs method!!!!. return the number of elements in the Sequencer gates array that are greater than 0" + + | result size | + result := self gates select: [ :i | i > 0 ]. + size := result size. + ^ size +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> size: aFloatOrAnArray [ +" add rdepth reverb pattern to the Sequencer" + self dirtMessage at: 'size' put: aFloatOrAnArray asDirtArray +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> sound: soundsAndIndexs [ + "assign to a Sequencer an array with samples as string and one with samples index - intended to be used with the SuperDirt audioengine for SuperCollider for example: + 16 downbeats sound: #('cp' 'bd:3' 'tom:4') + + //// what if using symbools insteda of strings? + + " + + | soundsAndIndexsSeparated indexes sounds | + soundsAndIndexsSeparated:= soundsAndIndexs findBetweenSubstrings: ' '. + + + sounds := OrderedCollection new. + 1 to: soundsAndIndexsSeparated size do: [ :i | + sounds add: + (((soundsAndIndexsSeparated at: i) findBetweenSubstrings: ':') at: 1) ]. + indexes := OrderedCollection new. + (1 to: soundsAndIndexsSeparated size) do: [ :i | + ((soundsAndIndexsSeparated at: i) findBetweenSubstrings: ':') size > 1 + ifTrue: [ + indexes add: + (((soundsAndIndexsSeparated at: i) findBetweenSubstrings: ':') at: 2) + asInteger ] + ifFalse: [ indexes add: 0 ] ]. + + +self dirtMessage at: 's' put: (sounds asDirtArray); at: 'n' put: indexes asDirtArray. + +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> sound: aString dirtNotes: anArrayfIndexes [ +"add into a Sequencer a soundPattern and notes suitable for SuperDirt" + self sound: aString . + self dirtMessage at: 'n' put: anArrayfIndexes asDirtArray +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> sound: anArrayOfStrings dirtNotes: anArrayOfMidiNN to: aKeyInPerformance [ +"convenience method to avoid extra parenthesis" + (self sound: anArrayOfStrings dirtNotes: anArrayOfMidiNN ) to: aKeyInPerformance +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> sound: soundsAndIndexs to: aKeyInPerformance [ +"convenience method to avoid extra parenthesis" + +(self sound: soundsAndIndexs ) to: aKeyInPerformance +] + +{ #category : #accessing } +Sequencer >> soundPattern [ + + ^ soundPattern +] + +{ #category : #accessing } +Sequencer >> soundPattern: anArrayOfStrings [ + + soundPattern := anArrayOfStrings +] + +{ #category : #accessing } +Sequencer >> sounds: aStringForDirt [ + + | soundsAndIndexs sounds indexes | + self + deprecated: 'Use #sound: instead' + on: '11 May 2023' + in: + 'Pharo-11.0.0+build.688.sha.cf3d3fd1805673a058ddf99229edb72ef062c890 (64 Bit)'. + soundsAndIndexs := aStringForDirt findBetweenSubstrings: ' '. + sounds := OrderedCollection new. + (1 to: soundsAndIndexs size) do: [ :i | + sounds add: + (((soundsAndIndexs at: i) findBetweenSubstrings: ':') at: 1) ]. + indexes := OrderedCollection new. + (1 to: soundsAndIndexs size) do: [ :i | + ((soundsAndIndexs at: i) findBetweenSubstrings: ':') size > 1 + ifTrue: [ + indexes add: + (((soundsAndIndexs at: i) findBetweenSubstrings: ':') at: 2) + asInteger ] + ifFalse: [ indexes add: 0 ] ]. + + self + soundPattern: sounds asDirtArray; + samplesIndex: indexes asDirtArray +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> speed: aFloatOrAnArray [ +" add speed size pattern to the Sequencer - change speed of samples playback" + self dirtMessage at: 'speed' put: aFloatOrAnArray asDirtArray +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> squiz: aFloatOrAnArray [ +" add squiz distortion to the Sequencer" + self dirtMessage at: 'room' put: aFloatOrAnArray asDirtArray +] + +{ #category : #accessing } +Sequencer >> stepIndex [ + + self at: 4 put: self noteIndex + 1 +] + +{ #category : #replication } +Sequencer >> times: anInteger [ + + "returns a sequencer containing anInteger repetition of self" + + | newGates newNotes newDurations | + newGates := (self gates times: anInteger) asRhythm. + newNotes := self notes times: anInteger. + newDurations := self durations times: anInteger. + + + + ^ SequencerMono new + gates: newGates; + notes: newNotes; + durations: newDurations; + noteIndex: 0 +] + +{ #category : #'LiveCoding - Performance' } +Sequencer >> to: aSymbol [ +" assign sequencer to the performance at aSymbol. if a sound for SuperDirt is not specified, the symbol is used for sound name" + + | sampleFolder sampleIndex | + self seqKey: aSymbol asString . + + "For SuperDirt" + self dirtMessage at: '_id' put: self seqKey. + self dirtMessage at: 'orbit' put: self orbit. + + (self dirtMessage includesKey: 's') ifFalse: [ self dirtMessage at: 's' put: aSymbol asString asDirtArray ] ifTrue: [ ] . + + "For Polyphony" + seqChords ifNil: [Performance uniqueInstance at: aSymbol put: self] ifNotNil: [ Performance uniqueInstance at: aSymbol put: self asPolySeq] . + + +"For PerformerPhaust" +(Performance uniqueInstance performer class = PerformerPhaust ) ifTrue: [ self phaustGateDestination: self seqKey , 'Gate' . self phaustNoteDestination:self seqKey , 'Note' ] +] + +{ #category : #addToPerformance } +Sequencer >> to: aPerformance at: aKey [ + +aPerformance add: aKey -> self +] + +{ #category : #printing } +Sequencer >> trace [ + +gates traceCr . +notes traceCr. +durations traceCr. +] + +{ #category : #modifying } +Sequencer >> transpose: aNumberOfSemitones [ + + | melody | + melody := self notes. + ^ self class new + gates: self gates; + notes: melody + aNumberOfSemitones; + durations: self durations; + noteIndex: self noteIndex +] + +{ #category : #'as yet unclassified' } +Sequencer >> trigs [ +"return the number of trigs, i.e. gates = 1, of the sequencer" +| result | +result := 0. +(1 to: (self gates size)) do: [ :i | ((self gates at: i) = 1) ifTrue: [result := result + 1] ifFalse: [ nil]]. +^ result. +] + +{ #category : #'as yet unclassified' } +Sequencer >> visualGroup [ + + ^ visualGroup +] + +{ #category : #'as yet unclassified' } +Sequencer >> visualGroup: aRSGroup [ + + visualGroup := aRSGroup +] + +{ #category : #visualization } +Sequencer >> visualizeOn: aRSCanvas [ + + "visualize the sequencer on a Roassal canvas" + + | nIndex color red green blue border layout gatesSize minimumRadius | + nIndex := 0. " to represent noteNumber" + minimumRadius := self gates size. + color := Color random. + gatesSize := 40. + border := red := Random new nextInteger: 255. + green := Random new nextInteger: 255. + blue := Random new nextInteger: 255. + border := RSBorder new color: (Color r: red g: green b: blue). + visualGroup := RSGroup new. + self gates do: [ :g | + nIndex := nIndex + 1. + visualGroup add: (RSCircle new + size: + 3.1 * (80 - (self notes at: (nIndex modulo: self notesSize))); + border: border; + + color: color * g) ]. + layout := RSCircleLayout new + radius: + (Random new + nextIntegerBetween: minimumRadius * 2 + 50 + and: 400); + initialAngleInDegree: 270. + layout on: visualGroup. + visualGroup translateTo: 0 @ 0. + aRSCanvas add: visualGroup asShape. + aRSCanvas signalUpdate +] + +{ #category : #'LiveCoding - SuperDirt' } +Sequencer >> voice: aFloatOrAnArray [ +" add voice variation for SuperDirt Synths to the Sequencer" + self dirtMessage at: 'voice' put: aFloatOrAnArray asDirtArray +] + +{ #category : #random } +Sequencer >> withRandomNotesFrom: anArray root: aRootNote octaves: aNumber [ + + "return an array of self size of a random note from anArray of note numbers with root aRootNote, and aNumber octave range" + + | intervals melody | + intervals := (1 to: self numberOfGates) collect: [ :i | + (anArray at: (Random new nextInteger: anArray size)) + + ((Random new nextInteger: aNumber + 1) - 1 * 12) ]. + melody := intervals + aRootNote. + self notes: melody. + +] + +{ #category : #random } +Sequencer >> withRandomNotesMin: anInteger max: anotherInteger [ + + "return an array of self size of a random note from anArray of note numbers with root aRootNote, and aNumber octave range" + + | newNotes | + newNotes := (1 to: self numberOfGates ) collect: [ :i | + (Random new nextInteger: anotherInteger - anInteger) + + anInteger ]. + ^ self notes: newNotes +] diff --git a/src/Coypu/SequencerMono.class.st b/src/Coypu/SequencerMono.class.st new file mode 100644 index 0000000..23aa0ca --- /dev/null +++ b/src/Coypu/SequencerMono.class.st @@ -0,0 +1,169 @@ +" +Monophonic sequencer. +" +Class { + #name : #SequencerMono, + #superclass : #Sequencer, + #type : #variable, + #category : #'Coypu-Sequencers' +} + +{ #category : #arithmetic } +SequencerMono >> + anInteger [ + "transpose up the sequencer notes by anInteger semitones" + + self notes: self notes + anInteger +] + +{ #category : #arithmetic } +SequencerMono >> - anInteger [ + "transpose the sequencer notes by anInteger semitones" + + self notes: self notes - anInteger +] + +{ #category : #'as yet unclassified' } +SequencerMono >> arpeggiate: aStringOfChords [ + | arpNotes | + + arpNotes := OrderedCollection new. +aStringOfChords chordsToArrays do: [ :i | arpNotes addAll: i ]. +self dirtMessage at: 'n' put: arpNotes . +] + +{ #category : #'as yet unclassified' } +SequencerMono >> arpeggiate: aStringOfChords octave: anIntegerOrAnArray [ + | arpNotes | + + arpNotes := OrderedCollection new. +aStringOfChords chordsToArrays do: [ :i | arpNotes addAll: i ]. +self dirtMessage at: 'n' put: arpNotes + (anIntegerOrAnArray * 12) . +] + +{ #category : #converting } +SequencerMono >> asPolySeq [ + +| polySeq | +polySeq := SequencerPoly new. +polySeq gates: self gates; noteIndex: self noteIndex; durations: self durations; dirtMessage: self dirtMessage; seqChords: self seqChords . +polySeq dirtMessage removeKey: #n ifAbsent: [ ] . +polySeq isPoly: true. +^ polySeq +] + +{ #category : #LiveCoding } +SequencerMono >> extra: anArray [ + + self kymaMessage + ifNil: [ self kymaMessage: Dictionary new. self kymaMessage at: (anArray at: 1) put: (anArray at: 2) ] + ifNotNil: [ + self kymaMessage at: (anArray at: 1) put: (anArray at: 2) ] +] + +{ #category : #'LiveCoding - sequencer' } +SequencerMono >> isPolySeq [ +^ false +] + +{ #category : #'LiveCoding - SuperDirt' } +SequencerMono >> playFullDirtEventAt: anIndex [ + + "sends a mesaage to SuperDirt with all the desired OSC arguments and values" +| message dur stepDuration| +stepDuration := Performance uniqueInstance freq. +message := OrderedCollection new. +message add: '/dirt/play'. +dur := self durations asDirtArray wrap: anIndex . + +message add: 'delta'; add: stepDuration * dur. "delta should change" +dirtMessage keysAndValuesDo: [ :key :value | message add: key; add: (value asDirtArray wrap: anIndex ) ]. + + + + (OSCBundle for: { OSCMessage for: message } ) sendToAddressString: '127.0.0.1' port: 57120. + ^ true +] + +{ #category : #'LiveCoding - SuperDirt' } +SequencerMono >> playFullKymaEventAt: anIndex [ + "sends a mesaage to SuperDirt with all the desired OSC arguments and values" + + | message dur note stepDuration | + stepDuration := Performance uniqueInstance freq. + + "as in hardware sequencer, default gate time is 80% of duration time" + dur := (self durations asDirtArray wrap: anIndex) * stepDuration * 0.8. + note := self notes asDirtArray wrap: anIndex. + self seqKey toKymaAsGate: dur note: note. + " for extra data" + "dirtMessage keysAndValuesDo: [ :key :value | + message + add: key; + add: (value asDirtArray wrap: anIndex) ]." + " ####################" + ^ true +] + +{ #category : #'LiveCoding - SuperDirt' } +SequencerMono >> playLocalEventAt: anIndex [ + + "sends a mesaage to SuperDirt with all the desired OSC arguments and values" +| message dur stepDuration| +stepDuration := Performance uniqueInstance freq. +message := OrderedCollection new. + +dur := self durations asDirtArray wrap: anIndex . + +message add: 'delta'; add: stepDuration * dur. "delta should change" +dirtMessage keysAndValuesDo: [ :key :value | message add: key; add: (value asDirtArray wrap: anIndex ) ]. + + + + (OSCBundle for: { OSCMessage for: message } ) sendToAddressString: '127.0.0.1' port: 57120. + ^ true +] + +{ #category : #'LiveCoding - SuperDirt' } +SequencerMono >> playMIDIEventAt: anIndex [ + + "sends a mesaage to SuperDirt with all the desired OSC arguments and values" +| gateTime dur midiNote mch stepDuration midiSender freq | +freq := Performance uniqueInstance freq. +gateTime := 0.9. "must be changeable" +midiSender := PerformerMIDI midiOut . +mch := self midiChannel . +stepDuration := Performance uniqueInstance freq. +midiNote := self notes asDirtArray wrap: anIndex . +dur := self durations asDirtArray wrap: anIndex . +midiSender playNote: midiNote onChannel: mch duration: dur * freq * gateTime . + + + ^ true +] + +{ #category : #'LiveCoding - SuperDirt' } +SequencerMono >> playPhaustEventAt: anIndex [ + "sends a message to Phausto / the DSP api must be written in a sensible way " + + | dur stepDuration dsp | + dsp := Performance uniqueInstance activeDSP. + stepDuration := Performance uniqueInstance freq. + + " please use also duration for gate !!! - future implementation" + dur := self durations asDirtArray wrap: anIndex. + + dsp + setValue: (self notes wrap: anIndex) + parameter: self phaustNoteDestination. + "as in early sequencers, standard gate time is 80% of duration" + dsp trig: self phaustGateDestination for: dur * stepDuration * 0.8. + + ^ true +] + +{ #category : #highlighting } +SequencerMono >> reverse [ + " reverese gates" + self gates: self gates reverse. + +] diff --git a/src/Coypu/SequencerPlayAnnouncement.class.st b/src/Coypu/SequencerPlayAnnouncement.class.st new file mode 100644 index 0000000..af3a49c --- /dev/null +++ b/src/Coypu/SequencerPlayAnnouncement.class.st @@ -0,0 +1,8 @@ +" +Use this announcements to modify a visualisation every time a Sequencer play anevent +" +Class { + #name : #SequencerPlayAnnouncement, + #superclass : #Object, + #category : #'Coypu-Announcements' +} diff --git a/src/Coypu/SequencerPoly.class.st b/src/Coypu/SequencerPoly.class.st new file mode 100644 index 0000000..edcae82 --- /dev/null +++ b/src/Coypu/SequencerPoly.class.st @@ -0,0 +1,42 @@ +" +Polyphonic Sequencer +" +Class { + #name : #SequencerPoly, + #superclass : #Sequencer, + #type : #variable, + #category : #'Coypu-Sequencers' +} + +{ #category : #converting } +SequencerPoly >> asPolySeq [ +^ self +] + +{ #category : #'LiveCoding - sequencer' } +SequencerPoly >> isPolySeq [ +^ true +] + +{ #category : #'LiveCoding - SuperDirt' } +SequencerPoly >> playFullDirtEventAt: anIndex [ + + "sends a mesaage to SuperDirt with all the desired OSC arguments and values" +| message dur stepDuration notesInChord chordMessages chord| + +stepDuration := Performance uniqueInstance freq. +message := OrderedCollection new. +message add: '/dirt/play'. +dur := self durations asDirtArray wrap: anIndex . +chord := self seqChords asDirtArray wrap: anIndex. +notesInChord := chord size. +message add: 'delta'; add: stepDuration * dur. "delta should change" +dirtMessage keysAndValuesDo: [ :key :value | message add: key; add: (value asDirtArray wrap: anIndex ) ]. + + +chordMessages := (1 to: notesInChord) collect: [ :i | OSCMessage for: ((message copyWith: 'n') copyWith: (chord at: i))]. + + (OSCBundle for: chordMessages ) sendToAddressString: '127.0.0.1' port: 57120. + + ^ true +] diff --git a/src/Coypu/SequencerTests.class.st b/src/Coypu/SequencerTests.class.st new file mode 100644 index 0000000..22fe641 --- /dev/null +++ b/src/Coypu/SequencerTests.class.st @@ -0,0 +1,148 @@ +Class { + #name : #SequencerTests, + #superclass : #TestCase, + #instVars : [ + 'sequencer' + ], + #category : #'Coypu-Tests' +} + +{ #category : #running } +SequencerTests >> setUp [ + + sequencer := 8 downbeats. + + + "Put here a common initialization logic for tests" +] + +{ #category : #tests } +SequencerTests >> testAddAssociationForDirtMesage [ + +| seq | +seq := 16 downbeats sound: 'cp'. +seq add: 'squiz'->#(0 1 2). + +self assert: (seq dirtMessage includesKey: #squiz) equals: true. +self assert: (seq dirtMessage includes: #( 0 1 2) ) equals: true. +] + +{ #category : #tests } +SequencerTests >> testArpeggiateArray [ + +| initial result| +initial := #(60 61 62 63). +result := #(60 61 62 63 61 62 63 64 62 63 64 65 63 64 65 66). + +self assert: (initial arp: #(0 1 2 3)) equals: result +] + +{ #category : #tests } +SequencerTests >> testAsPolySeq [ + +| monoseq polyseq | + +monoseq := 16 downbeats. +polyseq := monoseq asPolySeq. + +self assert: polyseq isPolySeq . + +self assert: polyseq gates equals: monoseq gates. +self assert: polyseq durations equals: monoseq durations. +self assert: polyseq noteIndex equals: monoseq noteIndex. +self assert: polyseq dirtMessage equals: monoseq dirtMessage . +self assert: (polyseq dirtMessage includesKey: 'n') equals: false. + +] + +{ #category : #tests } +SequencerTests >> testChangeSequencerGatesSize [ + + | seq | + seq := 16 downbeats. + seq gatesSize: 1. + self assert: (seq gatesSize) equals: 1 +] + +{ #category : #tests } +SequencerTests >> testChords: aChordOrAnArrayOfChords [ + +| polySeq | +polySeq := 4 breves sound: 'superpiano'; chords: 'c-major d-minor e-major'. + +self assert: (polySeq dirtMessage at: 'n') equals: ( 'c-major d-minor e-major' chordsToArrays ). + +] + +{ #category : #tests } +SequencerTests >> testEuclideanRhythmFromArray [ + + | tresillo seq | + tresillo := #( 3 8) euclidean. + seq := #( 1 0 0 1 0 0 1 0) asRhythm asSeq. + self assert: tresillo equals: seq +] + +{ #category : #tests } +SequencerTests >> testFlipSequencer [ + + | tresillo seq | + tresillo := #( 3 8 ) euclidean flip. + seq := #( 0 1 1 0 1 1 0 1 ) asRhythm asSeq . + self assert: tresillo equals: seq +] + +{ #category : #tests } +SequencerTests >> testInitialize [ +self assert: (Sequencer new orbit > 0) & (Sequencer new orbit < 12) equals: true +] + +{ #category : #tests } +SequencerTests >> testReverse [ + +| seq | +seq := '000F' hexBeat sound: 'sd cp'; dirtNotes: #(1 2 3) . +self assert: seq reverse gates equals: #( 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0) asRhythm . +self assert: (seq reverse dirtMessage at: 'n') equals: #( 3 2 1). +self assert: (seq reverse dirtMessage at: 's') equals: #('cp' 'sd'). +] + +{ #category : #tests } +SequencerTests >> testSequencerArpeggiate [ +| seq | +seq := 8 semiquavers . +seq arpeggiate: 'c-minor7 d-minor7'. + +self assert: (seq dirtMessage at: 'n') equals: #(0 3 7 10 2 5 9 12) asArray . +] + +{ #category : #tests } +SequencerTests >> testSequencerOffset [ + +| seq1 seq2 result1 result2 | +seq1 := 4 downbeats. +seq2 := 4 downbeats. + +result1 := #(0 0 1 0) asSeq . +result2 := #(0 0 0 1) asSeq . + +self assert: (seq1 offset: 2) gates equals: result1 gates. +self assert: (seq2 offset: -1) gates equals: result2 gates. +] + +{ #category : #tests } +SequencerTests >> testSequencerSound [ + +| seq | +seq := 16 downbeats sound: #('bd' 'cp:2'). + +self assert: seq soundPattern equals: #('bd' 'cp'). +self assert: seq samplesIndex equals: #(0 2). +] + +{ #category : #tests } +SequencerTests >> testSequencerToPerformance [ + +sequencer to: #track. +self assert: (Performance uniqueInstance at: #track) equals: sequencer +] diff --git a/src/Coypu/SmallInteger.extension.st b/src/Coypu/SmallInteger.extension.st new file mode 100644 index 0000000..b2b2efa --- /dev/null +++ b/src/Coypu/SmallInteger.extension.st @@ -0,0 +1,98 @@ +Extension { #name : #SmallInteger } + +{ #category : #'*Coypu' } +SmallInteger >> asInt8 [ + + | bitArray firsComplement binaryString secondComplementAsString secondComplement result | + bitArray := self asBitsArrayOfSize: 8. + firsComplement := (bitArray at: 1) * 128 negated. + binaryString := String new: 7. + 2 to: 8 do: [ :i | + binaryString at: i - 1 put: (bitArray at: i) asCharacterDigit ]. + secondComplementAsString := '2r' , binaryString. + secondComplement := Smalltalk compiler evaluate: + secondComplementAsString. + result := firsComplement + secondComplement. + ^ result +] + +{ #category : #'*Coypu' } +SmallInteger >> bars [ + "return number of steps in self bars" + ^ self*16 +] + +{ #category : #'*Coypu' } +SmallInteger >> isNoteOn [ + +"test if corresponging MIDI status byte is a Note On message" + +^ 16r90 isVoiceMessage +] + +{ #category : #'*Coypu' } +SmallInteger >> isVoiceMessage [ + "In MIDI communication, each MIDI message consists of a status byte followed by one or two data bytes. The status byte contains information about the type of message being sent, such as note-on, note-off, control change, etc. +The status byte has a specific format: The most significant nibble (the leftmost 4 bits) indicates the type of message. The least significant nibble (the rightmost 4 bits) typically represents the MIDI channel. For example, in the MIDI protocol: + +Note-on messages usually have a status byte starting with 0x90 (binary 1001xxxx), where xxxx represents the MIDI channel. +Note-off messages usually have a status byte starting with 0x80 (binary 1000xxxx), again with xxxx representing the MIDI channel. +When receiving MIDI messages, it's common to filter and process messages based on their type. In your case, you're interested in detecting note-on messages, which typically have a status byte starting with 0x90. +The reason for performing bitwise AND with 16rF0 (which is 11110000 in binary) is to mask out the least significant nibble of the status byte, effectively isolating the most significant nibble. This allows you to compare only the most significant nibble with a given value to determine the type of MIDI message. + +" + + ^ (self bitAnd: 16rf0) = self +] + +{ #category : #'*Coypu' } +SmallInteger >> saw [ + + "fill an array of self steps with values representing a sawtooth with range 0 - 1" + + ^ (0 to: 1 by: 1 / (self -1)) asDirtArray +] + +{ #category : #'*Coypu' } +SmallInteger >> sawCC: aRange [ + + "fill an array of self steps with values representing a sawtooth with aRange 1" + + | min max range arrayOfFloats result | + min := aRange at: 1. + max := aRange at: 2. + range := max - min. + arrayOfFloats := (min to: max by: range / (self - 1)) asDirtArray. + result := arrayOfFloats collect: [ :i | i rounded ]. +^ result +] + +{ #category : #'*Coypu' } +SmallInteger >> triangle [ + + "return an array of self size with a triangle wave with range 0 -1" + + | increment up down result | + increment := 1 / (self / 2 - 1). + up := (0 to: 1 by: increment) asDirtArray. + down := up reverse. + result := up , down. + ^ result +] + +{ #category : #'*Coypu' } +SmallInteger >> triangleCC: aRange [ + + "return an array of self size with a triangle wave with aRange suited for MIDI ccs" + + | increment range min max up down arrayOfFloats result | + min := aRange at: 1. + max := aRange at: 2. + range := (max - min). + increment := range / (self / 2 - 1). + up := (min to: max by: increment) asDirtArray. + down := up reverse. + arrayOfFloats := up , down. + result := arrayOfFloats collect: [ :i | i rounded ]. + ^ result +] diff --git a/src/Coypu/SuperDirt.class.st b/src/Coypu/SuperDirt.class.st new file mode 100644 index 0000000..5e9ba4d --- /dev/null +++ b/src/Coypu/SuperDirt.class.st @@ -0,0 +1,117 @@ +" +Convenience class to interface with and get information on the SuperDirt audioEngine for SuperCollider +" +Class { + #name : #SuperDirt, + #superclass : #Object, + #category : #'Coypu-SuperDirt' +} + +{ #category : #'SuperDirt-List' } +SuperDirt class >> listOfDrumSynth [ + + +| drumList | + +drumList := OrderedCollection new. +drumList add: (SuperDirtSynth name: 'superkick' parameters: #('n' 'accelerate' 'pitch1' 'decay')); add: (SuperDirtSynth name: 'super808' parameters: #('n' 'voice' 'rate')). + +^ drumList +] + +{ #category : #'SuperDirt-List' } +SuperDirt class >> listOfDrums [ + + +| drumList | + +drumList := OrderedCollection new. +drumList add: (SuperDirtSynth name: 'superkick' parameters: #('n' 'accelerate' 'pitch1' 'decay')); add: (SuperDirtSynth name: 'super808' parameters: #('n' 'voice' 'rate')). + +^ drumList +] + +{ #category : #'SuperDirt-List' } +SuperDirt class >> listOfSynths [ +"print on Transcript the list of SuperDirt available Synths" +| synthList cr | +synthList := #( '##### SYNTHS #####' 'supermandolin' 'supergong' ' superpiano' 'superhex' 'supersquare' +'supersaw' 'superpwm' 'supercomparator' 'superchip' 'superfork' 'superhammond' +'superhive' 'superzow' 'supergrind' 'superprimes' 'superwavemechanichs' 'supertron' +'superreese' 'superfm' 'supersiren' 'supernoise' +'##### DRUMS #####' 'superkick' ' super808' 'supersnare' 'superclap' ' soskick' +'soshats' 'sostoms' 'sossnare' +). + + +^ synthList +] + +{ #category : #'as yet unclassified' } +SuperDirt class >> listOfSynths2 [ +"print on Transcript the list of SuperDirt available Synths" +| synthList | + +synthList := OrderedCollection new. +synthList add: (SuperDirtSynth name: 'supermandolin' parameters: #('sustain' 'accelerate' 'detune')); +add: (SuperDirtSynth name: 'supergong' parameters: #('voice' 'accelerate' 'decay')); +add: (SuperDirtSynth name: 'supersquare' parameters: #('voice' 'accelerate' 'decay' 'semitone' 'resonance' 'lfo' 'rate' 'pitch1')); +add: (SuperDirtSynth name: 'supersaw' parameters: #('voice' 'accelerate' 'decay' 'semitone' 'resonance' 'lfo' 'rate' 'pitch1')); +add: (SuperDirtSynth name: 'superpwm' parameters: #('voice' 'accelerate' 'decay' 'semitone' 'resonance' 'lfo' 'rate' 'pitch1')); +add: (SuperDirtSynth name: 'superchip' parameters: #('voice' 'slide' 'accelerate' 'rate' 'pitch2' 'pitch3' )); +add: (SuperDirtSynth name: 'superhoover' parameters: #('decay' 'accelerate' 'slide')); +add: (SuperDirtSynth name: 'superzow' parameters: #('decay' 'accelerate' 'slide' 'detune')); +add: (SuperDirtSynth name: 'supertron' parameters: #('voice' 'accelerate' 'detune')); +add: (SuperDirtSynth name: 'superreese' parameters: #('voice' 'accelerate' 'detune')); +add: (SuperDirtSynth name: 'superstatic' parameters: #('voice' 'accelerate' 'pitch1' 'slide' 'resonance')); +add: (SuperDirtSynth name: 'supercomparator' parameters: #('voice' 'decya' 'accelerate' 'lfo' 'rate' 'pitch1' 'slide' 'resonance')) + +. + + + + + + + +^ synthList +] + +{ #category : #'as yet unclassified' } +SuperDirt class >> openListOfSynths [ + + | instrumentsTable drumsTable presenter | + instrumentsTable := SpTablePresenter new. + instrumentsTable + items: self listOfSynths2; + addColumn: + (SpStringTableColumn title: 'synth' evaluated: [ :e | e name ]); + addColumn: (SpStringTableColumn + title: 'parameters' + evaluated: [ :e | e parameters joinUsing: ' ' ]). + + drumsTable := SpTablePresenter new. + drumsTable + items: self listOfDrums; + addColumn: + (SpStringTableColumn title: 'drums' evaluated: [ :e | e name ]); + addColumn: (SpStringTableColumn + title: 'parameters' + evaluated: [ :e | e parameters joinUsing: ' ' ]). + presenter := SpPresenter new. + presenter layout: (SpBoxLayout newTopToBottom + add: instrumentsTable; + add: drumsTable; + yourself). + (presenter open) title: 'ListOfSynths'; extent: 600@400. + ^ presenter +] + +{ #category : #'as yet unclassified' } +SuperDirt class >> samplesFolder [ + "return the path to the folder where the superdirt samples are located" +| home folder | +home := FileLocator home. +folder := (home / 'Library') / 'Application Support/SuperCollider/downloaded-quarks/Dirt-Samples/'. +^ folder +] diff --git a/src/Coypu/SuperDirtSynth.class.st b/src/Coypu/SuperDirtSynth.class.st new file mode 100644 index 0000000..c9101a4 --- /dev/null +++ b/src/Coypu/SuperDirtSynth.class.st @@ -0,0 +1,42 @@ +Class { + #name : #SuperDirtSynth, + #superclass : #Object, + #instVars : [ + 'parameters', + 'name' + ], + #category : #'Coypu-SuperDirt' +} + +{ #category : #'instance creation' } +SuperDirtSynth class >> name: aString parameters: aCollection [ + + ^ self new + name: aString; + parameters: aCollection; + yourself +] + +{ #category : #accessing } +SuperDirtSynth >> name [ + + ^ name +] + +{ #category : #accessing } +SuperDirtSynth >> name: anObject [ + + name := anObject +] + +{ #category : #accessing } +SuperDirtSynth >> parameters [ + + ^ parameters +] + +{ #category : #accessing } +SuperDirtSynth >> parameters: anObject [ + + parameters := anObject +] diff --git a/src/Coypu/SuperDirtTests.class.st b/src/Coypu/SuperDirtTests.class.st new file mode 100644 index 0000000..e6fc8df --- /dev/null +++ b/src/Coypu/SuperDirtTests.class.st @@ -0,0 +1,5 @@ +Class { + #name : #SuperDirtTests, + #superclass : #TestCase, + #category : #'Coypu-Tests' +} diff --git a/src/Coypu/Transport.class.st b/src/Coypu/Transport.class.st new file mode 100644 index 0000000..80f79e6 --- /dev/null +++ b/src/Coypu/Transport.class.st @@ -0,0 +1,54 @@ +" +Modeled on the typical Digital Audio Workstation transport, controls the playback of the Performance +" +Class { + #name : #Transport, + #superclass : #Object, + #instVars : [ + 'isRunning', + 'position' + ], + #category : #'Coypu-Performance' +} + +{ #category : #accessing } +Transport >> increment [ +position := position + 1. +^ position +] + +{ #category : #initialization } +Transport >> initialize [ +super initialize . +self position: 0. +self isRunning: false. +] + +{ #category : #accessing } +Transport >> isRunning [ + + ^ isRunning +] + +{ #category : #accessing } +Transport >> isRunning: aBoolean [ +"if true, the Transport is running" + isRunning := aBoolean +] + +{ #category : #accessing } +Transport >> position [ + + ^ position +] + +{ #category : #accessing } +Transport >> position: anObject [ + + position := anObject +] + +{ #category : #initialization } +Transport >> reset [ +self position: 0. +] diff --git a/src/Coypu/VerticalFader.class.st b/src/Coypu/VerticalFader.class.st new file mode 100644 index 0000000..bbf739c --- /dev/null +++ b/src/Coypu/VerticalFader.class.st @@ -0,0 +1,195 @@ +" +Vertical fader to control values on the client +" +Class { + #name : #VerticalFader, + #superclass : #Morph, + #instVars : [ + 'faderAddress', + 'faderCanMove', + 'cursor', + 'minValue', + 'maxValue', + 'cursorColor', + 'destination', + 'parameter' + ], + #category : #'Coypu-GUI' +} + +{ #category : #'instance creation' } +VerticalFader class >> closeAll [ + + | fadersInWorld | + + fadersInWorld := self currentWorld submorphs select: [ :i | + i class = FaderWindow ]. + fadersInWorld do: [ :i | i delete ] +] + +{ #category : #'instance creation' } +VerticalFader class >> newWithAddress: anAddress [ + + " the fader label is also the OSC address" + + ^ self new + address: anAddress; + extent: 200 @ 600; + color: Color random; + cursorColor: Color black; + minValue: 0; + maxValue: 1; + yourself +] + +{ #category : #'instance creation' } +VerticalFader class >> newWithAddress: anAddress range: anArrayWithMinAndMax [ +" the fader label is also the OSC address" +^ (self new) + address: anAddress ; + minValue: (anArrayWithMinAndMax at: 1); + maxValue: (anArrayWithMinAndMax at: 2); + yourself +] + +{ #category : #accessing } +VerticalFader >> address [ +^ faderAddress +] + +{ #category : #accessing } +VerticalFader >> address: anAddress [ +"set address (and labels)" +faderAddress := anAddress +] + +{ #category : #accessing } +VerticalFader >> cursorColor [ +^ cursorColor +] + +{ #category : #accessing } +VerticalFader >> cursorColor: aColor [ +cursor color: aColor +] + +{ #category : #'as yet unclassified' } +VerticalFader >> drawCursor [ +"draw the fader cursor" +| cursorWidth cursorHeight | + +cursorHeight := self extent y /5. +cursorWidth := self width. + +cursor extent: 400@cursorHeight . + +self addMorph: cursor. +cursor center: self center. +] + +{ #category : #'event handling' } +VerticalFader >> handlesMouseDown: anEvent [ +^ true +] + +{ #category : #'event handling' } +VerticalFader >> handlesMouseMove: anEvent [ +^ true +] + +{ #category : #'event handling' } +VerticalFader >> handlesMouseUp: anEvent [ +^ true +] + +{ #category : #initialization } +VerticalFader >> initialize [ + + super initialize. + faderCanMove := false. + + "self extent: 400@500" + cursor := Morph new extent: 400 @ 300. + cursor color: Color red. + self drawCursor +] + +{ #category : #accessing } +VerticalFader >> maxValue: aNumber [ +maxValue := aNumber asFloat . +] + +{ #category : #accessing } +VerticalFader >> minValue: aNumber [ +minValue := aNumber asFloat . +] + +{ #category : #'event handling' } +VerticalFader >> mouseDown: anEvent [ + + +faderCanMove := true. + + + + +] + +{ #category : #'as yet unclassified' } +VerticalFader >> mouseMove: anEvent [ + +| newLocation mappedY cursorY faderBottom | + +faderCanMove ifTrue: +[ +faderBottom := self extent y + self position y. +cursorY := anEvent position y. +newLocation := Point x: (self center x) y: cursorY. +cursor center: newLocation . +"map cursorY to value from 0 to 1" +mappedY := ((((cursorY min: faderBottom )- faderBottom ) abs) min: self extent y) / self extent y. +Transcript show: newLocation ; cr. +Transcript show: 'mapped value' , ' ' , mappedY asString ; cr. +] ifFalse: [ nil] . + +"for debug" +] + +{ #category : #'event handling' } +VerticalFader >> mouseUp: anEvent [ +faderCanMove := false +] + +{ #category : #'as yet unclassified' } +VerticalFader >> openInWindow [ + +|faderWidth faderHeight positionLeft positionTop fadersInWorld bgColor| +fadersInWorld := FaderWindow countFadersInWorld. +faderWidth := 150. +faderHeight := self currentWorld height / 2.1. +positionLeft := self currentWorld width - (faderWidth * ((fadersInWorld + 1) modulo: 3)). +positionTop := (self currentWorld submorphs at: 1) extent y + (faderHeight * ((fadersInWorld // 3) )). +bgColor := Color random lighter lighter. +self color: bgColor. +self cursorColor: Color black. + + ^ ((self openInWindowLabeled: destination ) position: (positionLeft @positionTop )) extent: faderWidth@faderHeight. +] + +{ #category : #'as yet unclassified' } +VerticalFader >> wrappedInWindow [ + "Changed to include the inset margin in the bound calculation." + + | window extent | + window := FaderWindow new. + window + addMorph: self frame: (0@0 extent: 1@1); + updatePaneColors. + " calculate extent after adding in case any size related attributes were changed. Use + fullBounds in order to trigger re-layout of layout morphs" + extent := self fullBounds extent + + (window borderWidth@window labelHeight) + window borderWidth + + (window class borderWidth * 2 @ (window class borderWidth + 1)). "include inset margin" + window extent: extent. + ^ window +] diff --git a/src/Coypu/VerticalFaderForKyma.class.st b/src/Coypu/VerticalFaderForKyma.class.st new file mode 100644 index 0000000..bf98d8a --- /dev/null +++ b/src/Coypu/VerticalFaderForKyma.class.st @@ -0,0 +1,28 @@ +" +Vertical Fader for Kyma +" +Class { + #name : #VerticalFaderForKyma, + #superclass : #VerticalFader, + #category : #'Coypu-GUI' +} + +{ #category : #'event handling' } +VerticalFaderForKyma >> mouseMove: anEvent [ + + | newLocation mappedY cursorY faderBottom range faderTop| + range := maxValue - minValue. + + faderCanMove + ifTrue: [ + faderBottom := self extent y + self position y. + cursorY := anEvent position y. + faderTop := self position y. + cursorY := (anEvent position y min: faderBottom) max: faderTop. + cursor center: newLocation. + "map cursorY to value from 0 to 1" + mappedY := (((cursorY min: faderBottom) - faderBottom) abs min: + self extent y) / self extent y * range + minValue. + mappedY toKyma: faderAddress ] + ifFalse: [ "for debug" nil ] +] diff --git a/src/Coypu/VerticalFaderForMIDI.class.st b/src/Coypu/VerticalFaderForMIDI.class.st new file mode 100644 index 0000000..7d8aa2f --- /dev/null +++ b/src/Coypu/VerticalFaderForMIDI.class.st @@ -0,0 +1,126 @@ +" +Creates a Vertical Fader to send MIDI COntrol CHange messages to the selected MIDI Channel. +" +Class { + #name : #VerticalFaderForMIDI, + #superclass : #VerticalFader, + #instVars : [ + 'ccNumber', + 'midiSender', + 'midiChannel', + 'faderLabel', + 'visualization' + ], + #category : #'Coypu-GUI' +} + +{ #category : #'instance creation' } +VerticalFaderForMIDI class >> new: anArray [ + + "shortcut to create MIDI faderLabel #(aCCNumber aMIDISender a channel)" + + ^ self new + color: Color random; + ccNumber: (anArray at: 1); + midiSender: (anArray at: 2); + midiChannel: (anArray at: 3); + minValue: 0; + maxValue: 127; + faderLabel: 'Control Change'; + openInWindow; + yourself +] + +{ #category : #'instance creation' } +VerticalFaderForMIDI class >> newWithCC: aCCNumber forSender: aMIDISender [ +" the fader label is also the OSC address" +^ (self new) + ccNumber: aCCNumber ; + midiSender: aMIDISender; + cursorColor: Color red; + minValue: 0; + maxValue: 127; + yourself +] + +{ #category : #'instance creation' } +VerticalFaderForMIDI class >> newWithCC: aCCNumber forSender: aMIDISender channel: aMIDIChannel [ + + + ^ self new + ccNumber: aCCNumber; + midiSender: aMIDISender; + midiChannel: aMIDIChannel; + minValue: 0; + maxValue: 127; + yourself +] + +{ #category : #accessing } +VerticalFaderForMIDI >> ccNumber [ +^ ccNumber +] + +{ #category : #accessing } +VerticalFaderForMIDI >> ccNumber: aCCNumber [ + +ccNumber := aCCNumber . +] + +{ #category : #accessing } +VerticalFaderForMIDI >> faderLabel [ +^ faderLabel +] + +{ #category : #accessing } +VerticalFaderForMIDI >> faderLabel: aString [ +faderLabel := aString +] + +{ #category : #accessing } +VerticalFaderForMIDI >> midiChannel [ +^ midiChannel +] + +{ #category : #accessing } +VerticalFaderForMIDI >> midiChannel: aChannelNumber [ +midiChannel := aChannelNumber +] + +{ #category : #accessing } +VerticalFaderForMIDI >> midiSender [ +^ midiSender +] + +{ #category : #accessing } +VerticalFaderForMIDI >> midiSender: aMIDISender [ +midiSender := aMIDISender . +] + +{ #category : #'event handling' } +VerticalFaderForMIDI >> mouseMove: anEvent [ + + | newLocation mappedY cursorY faderBottom faderTop | + faderCanMove + ifTrue: [ + faderBottom := self extent y + self position y. + faderTop := self position y. + cursorY := (anEvent position y min: faderBottom) max: faderTop. + newLocation := Point x: self center x y: cursorY. + cursor center: newLocation. + "map cursorY to value from 0 to 1" + mappedY := (((cursorY min: faderBottom) - faderBottom) abs min: + self extent y) / self extent y * 127. + + + midiSender + sendCC: ccNumber + withValue: mappedY asInteger + onChannel: midiChannel ] + ifFalse: [ "for debug" nil ] +] + +{ #category : #accessing } +VerticalFaderForMIDI >> visualization: aVisualization [ +visualization := aVisualization . +] diff --git a/src/Coypu/VerticalFaderForOSC.class.st b/src/Coypu/VerticalFaderForOSC.class.st new file mode 100644 index 0000000..5dba8b9 --- /dev/null +++ b/src/Coypu/VerticalFaderForOSC.class.st @@ -0,0 +1,83 @@ +" +Vertical Fader to send OSC messages to the audio server. +" +Class { + #name : #VerticalFaderForOSC, + #superclass : #VerticalFader, + #category : #'Coypu-GUI' +} + +{ #category : #'instance creation' } +VerticalFaderForOSC class >> new: aStringWithADestinationAndAParameter [ + +" if the paameter is a number, the fader sends out to MIDI via SuperDirt the cursor value for the control change number assigned" +| parsedString dest param | + +parsedString := aStringWithADestinationAndAParameter findBetweenSubstrings: ' '. +dest := (parsedString at: 1). +param := (parsedString at: 2). + +param asInteger ifNotNil: [ ((Performance uniqueInstance at: dest) dirtMessage) at: 'ccn' put: param asInteger. param := 'ccv'] ifNil: [ ] . + +^ (self new) + destination: dest; + parameter: param; + + minValue: 0; + maxValue: 1 + ( 126 * ((parsedString at: 2) asInteger isNotNil asInteger)); + yourself +] + +{ #category : #'instance creation' } +VerticalFaderForOSC class >> new: aStringWithADestinationAndAParameter range: anArrayWirhMinAndMax [ +| parsedString | + +parsedString := aStringWithADestinationAndAParameter findBetweenSubstrings: ' '. +^ (self new) + destination: (parsedString at: 1) ; + parameter: (parsedString at: 2) ; + + minValue: anArrayWirhMinAndMax first; + maxValue: anArrayWirhMinAndMax second; + yourself +] + +{ #category : #accessing } +VerticalFaderForOSC >> destination [ +^ destination +] + +{ #category : #accessing } +VerticalFaderForOSC >> destination: aDestination [ +destination := aDestination +] + +{ #category : #'event handling' } +VerticalFaderForOSC >> mouseMove: anEvent [ + + | newLocation mappedY cursorY faderBottom range faderTop | + range := maxValue - minValue. + + faderCanMove + ifTrue: [ + faderBottom := self extent y + self position y. + faderTop := self position y. + cursorY := (anEvent position y min: faderBottom) max: faderTop. + newLocation := Point x: self center x y: cursorY. + cursor center: newLocation. + "map cursorY to value from 0 to 1" + mappedY := (((cursorY min: faderBottom) - faderBottom) abs min: + self extent y) / self extent y * range + minValue. + mappedY toLocal: faderAddress ] + ifFalse: [ "for debug" nil ] +] + +{ #category : #accessing } +VerticalFaderForOSC >> parameter [ +^ parameter +] + +{ #category : #accessing } +VerticalFaderForOSC >> parameter: aParameter [ +parameter := aParameter +] diff --git a/src/Coypu/VerticalFaderForSuperDirt.class.st b/src/Coypu/VerticalFaderForSuperDirt.class.st new file mode 100644 index 0000000..ecb1d4a --- /dev/null +++ b/src/Coypu/VerticalFaderForSuperDirt.class.st @@ -0,0 +1,30 @@ +" +Special OSC Vertical Fader to use with SuperDirt +" +Class { + #name : #VerticalFaderForSuperDirt, + #superclass : #VerticalFaderForOSC, + #category : #'Coypu-GUI' +} + +{ #category : #'event handling' } +VerticalFaderForSuperDirt >> mouseMove: anEvent [ +" the VerticalFaderForSuperDirt modify the contents of the Sequencer in Performance named as destination" + | newLocation mappedY cursorY faderBottom range faderTop | + range := maxValue - minValue. + + faderCanMove + ifTrue: [ + faderBottom := self extent y + self position y. + faderTop := self position y. + cursorY := (anEvent position y min: faderBottom) max: faderTop. + newLocation := Point x: self center x y: cursorY. + cursor center: newLocation. + "map cursorY to value from 0 to 1" + mappedY := (((cursorY min: faderBottom) - faderBottom) abs min: + self extent y) / self extent y * range + minValue. + + (Performance uniqueInstance at: destination ifAbsent: [ ] ) ifNotNil: [ (Performance uniqueInstance at: destination ) dirtMessage at: parameter put: mappedY. + ] ifNil: [ ] ] + ifFalse: [ "for debug" nil ] +] diff --git a/src/Coypu/VerticalFaderForSuperDirtGlobal.class.st b/src/Coypu/VerticalFaderForSuperDirtGlobal.class.st new file mode 100644 index 0000000..62916f0 --- /dev/null +++ b/src/Coypu/VerticalFaderForSuperDirtGlobal.class.st @@ -0,0 +1,49 @@ +" +Vertical Fader to control FX on Elektron Syntakt. +" +Class { + #name : #VerticalFaderForSuperDirtGlobal, + #superclass : #VerticalFaderForSuperDirt, + #category : #'Coypu-GUI' +} + +{ #category : #'instance creation' } +VerticalFaderForSuperDirtGlobal class >> new: aStringWithADestinationAndAParameter [ + +" if the paameter is a number, the fader sends out to MIDI via SuperDirt the cursor value for the control change number assigned" +| parsedString dest param | + +parsedString := aStringWithADestinationAndAParameter findBetweenSubstrings: ' '. +dest := (parsedString at: 1). +param := (parsedString at: 2) asInteger . + + +^ (self new) + destination: dest; + parameter: param; + + minValue: 0; + maxValue: 127; + yourself +] + +{ #category : #'event handling' } +VerticalFaderForSuperDirtGlobal >> mouseMove: anEvent [ +" the VerticalFaderForSuperDirt modify the contents of the Sequencer in Performance named as destination" + | newLocation mappedY cursorY faderBottom range faderTop | + range := maxValue - minValue. + + faderCanMove + ifTrue: [ + faderBottom := self extent y + self position y. + faderTop := self position y. + cursorY := (anEvent position y min: faderBottom) max: faderTop. + newLocation := Point x: self center x y: cursorY. + cursor center: newLocation. + "map cursorY to value from 0 to 1" + mappedY := (((cursorY min: faderBottom) - faderBottom) abs min: + self extent y) / self extent y * range + minValue. + + (OSCBundle for: { OSCMessage for: { '/dirt/play'. 'midichan' . 12.'s' . self destination . 'n' . 0 . 'ccn'. self parameter .'ccv'. mappedY } }) sendToAddressString: '127.0.0.1' port: 57120. + ]ifFalse: [ "for debug" nil ] +] diff --git a/src/Coypu/VerticalFaderFreq.class.st b/src/Coypu/VerticalFaderFreq.class.st new file mode 100644 index 0000000..3d7dd0f --- /dev/null +++ b/src/Coypu/VerticalFaderFreq.class.st @@ -0,0 +1,39 @@ +" +VerticalFader to slow down or speed up the Performance speed +" +Class { + #name : #VerticalFaderFreq, + #superclass : #VerticalFader, + #category : #'Coypu-GUI' +} + +{ #category : #initialization } +VerticalFaderFreq >> initialize [ +super initialize . +destination := 'BPM'. +minValue := 55. +maxValue := 190. + +"cursor postion should be at the current freq" +] + +{ #category : #'as yet unclassified' } +VerticalFaderFreq >> mouseMove: anEvent [ +" the VerticalFaderForSuperDirt modify the contents of the Sequencer in Performance named as destination" + | newLocation mappedY cursorY faderBottom range faderTop | + range := maxValue - minValue. + + faderCanMove + ifTrue: [ + faderBottom := self extent y + self position y. + faderTop := self position y. + cursorY := (anEvent position y min: faderBottom) max: faderTop. + newLocation := Point x: self center x y: cursorY. + cursor center: newLocation. + "map cursorY to value from 0 to 1" + mappedY := (((cursorY min: faderBottom) - faderBottom) abs min: + self extent y) / self extent y * range + minValue. +Performance uniqueInstance freq: mappedY bpm. + ] + ifFalse: [ "for debug" nil ] +] diff --git a/src/Coypu/package.st b/src/Coypu/package.st new file mode 100644 index 0000000..6c402b5 --- /dev/null +++ b/src/Coypu/package.st @@ -0,0 +1 @@ +Package { #name : #Coypu }