Skip to content

Commit

Permalink
🎃 Jack midi (#2)
Browse files Browse the repository at this point in the history
# jack midi
Use the `rtmidi.API_UNIX_JACK` for all connections.

Exposed and closed #3 

## logging
Added info / debug logging levels for `LaunchpadX`
  • Loading branch information
mrharpo authored Jan 15, 2024
2 parents 8d2cae5 + b60cf47 commit 389cf63
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 145 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
The **Holophonor** project is an plugin based architecture, designed to allow complex control of loops with a variety of hardware components and network controllers to allow complete control of any number of loops running simultaneously, even over multiple machines with large network latency!

## install
`pip install holophonor`
Clone this repo:

`git clone https://github.com/quaternionmedia/holophonor.git`

Install locally:

`pip install -e holophonor/`

## configure
change holophonor/main.py to match your configuration
Expand Down
22 changes: 11 additions & 11 deletions holophonor/constants.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
EMPTY = 0 # black
INACTIVE = 1 # grey
RECORDING = 5 # red
PLAYING = 21 # green
STOPPED = 43 # navy
CUT = 13 # cut mode - yellow
ERASE = 84 # orange
PULSE = 53 # pink
TAP = 37 # cyan
GREEN = [ 123, 23, 64, 22, 76, 87, 21, 122 ]
EMPTY = 0 # black
INACTIVE = 1 # grey
RECORDING = 5 # red
PLAYING = 21 # green
STOPPED = 43 # navy
CUT = 13 # cut mode - yellow
ERASE = 84 # orange
PULSE = 53 # pink
TAP = 37 # cyan
GREEN = [123, 23, 64, 22, 76, 87, 21, 122]

NUMBER_LOOPS = 32
NUMBER_SCENES = 8
NUMBER_SCENES = 8
14 changes: 10 additions & 4 deletions holophonor/daw.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@
from holophonor import holoimpl
from rtmidi.midiutil import open_midioutput
from rtmidi.midiconstants import CONTROL_CHANGE
FX = [35, 36, 37, 38, 45, 46, 47, 48 ]
from rtmidi import API_UNIX_JACK

FX = [35, 36, 37, 38, 45, 46, 47, 48]


class Bitwig:
def __init__(self, hook, port, client_name='bitwig', **kwargs):
self.hook = hook
self.midi, self.name = open_midioutput(port, client_name=client_name)
self.fx = [False]*8
self.midi, self.name = open_midioutput(
port, client_name=client_name, api=API_UNIX_JACK
)
self.fx = [False] * 8

@holoimpl
def toggleFX(self, fx: int):
self.midi.send_message([CONTROL_CHANGE, FX[fx], 0 if self.fx[fx] else 127])
self.fx[fx] = not self.fx[fx]
self.fx[fx] = not self.fx[fx]
44 changes: 26 additions & 18 deletions holophonor/freewheeling.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from holophonor import holoimpl
from holophonor.holospecs import Holophonor
from holophonor.constants import NUMBER_LOOPS, NUMBER_SCENES
from rtmidi.midiconstants import NOTE_ON, NOTE_OFF, POLY_AFTERTOUCH, CONTROL_CHANGE, PROGRAM_CHANGE
from rtmidi.midiconstants import (
NOTE_ON,
NOTE_OFF,
POLY_AFTERTOUCH,
CONTROL_CHANGE,
PROGRAM_CHANGE,
)
from time import sleep


Expand All @@ -14,39 +20,39 @@ def playLoop(self, loop: int, volume: int):
self.stopLoop(loop)
self.midi.send_message([NOTE_ON, loop, volume if volume > 0 else 100])
self.loops[loop] = volume

@holoimpl
def stopLoop(self, loop: int):
if self.loops[loop]:
self.midi.send_message([NOTE_ON, loop, 1])
if self.loops[loop] < 0:
# loop was recording or overdubbing
# send another message to stop
sleep(.01)
sleep(0.01)
self.midi.send_message([NOTE_ON, loop, 1])
self.loops[loop] = 0

@holoimpl
def recordLoop(self, loop: int):
if self.loops[loop] == None:
self.playLoop(loop, 127)
self.loops[loop] = -1

@holoimpl
def eraseLoop(self, loop: int):
if self.loops[loop] is not None:
self.toggleShift()
self.playLoop(loop, 127)
self.toggleShift()
self.loops[loop] = None

@holoimpl
def overdubLoop(self, loop: int):
self.toggleOverdub()
self.midi.send_message([NOTE_ON, loop, 127])
self.toggleOverdub()
self.loops[loop] = -2

@holoimpl
def recallScene(self, scene: int):
if self.current_scene and self.scenes[self.current_scene] == -1:
Expand All @@ -68,6 +74,7 @@ def recallScene(self, scene: int):
# if the loop is < 0 (recording, overdubbing)
# we have no volume information. Guess at 100
self.playLoop(l, s[l] if s[l] > 0 else 100)

@holoimpl
def storeScene(self, scene: int):
if self.current_scene != None and self.scenes[self.current_scene] == -1:
Expand All @@ -78,37 +85,37 @@ def storeScene(self, scene: int):
self.scenes[scene] = -1
elif self.scenes[scene] == -1:
self.scenes[scene] = self.loops.copy()

@holoimpl
def eraseScene(self, scene: int):
self.scenes[scene] = None

@holoimpl
def toggleShift(self):
self.midi.send_message([CONTROL_CHANGE, 98, 0 if self.shift else 127])
self.shift = not self.shift

@holoimpl
def toggleCut(self):
self.midi.send_message([CONTROL_CHANGE, 96, 0 if self.cut else 127])
self.cut = not self.cut

@holoimpl
def toggleOverdub(self):
self.midi.send_message([CONTROL_CHANGE, 97, 0 if self.overdub else 127])
self.overdub = not self.overdub

@holoimpl
def deletePulse(self):
self.midi.send_message([CONTROL_CHANGE, 108, 0])
self.loops = [None]*NUMBER_LOOPS
self.scenes = [None]*NUMBER_SCENES
self.loops = [None] * NUMBER_LOOPS
self.scenes = [None] * NUMBER_SCENES
self.current_scene = None

@holoimpl
def tapPulse(self):
self.midi.send_message([CONTROL_CHANGE, 95, 127])

@holoimpl
def stopAllLoops(self):
for i, l in enumerate(self.loops):
Expand All @@ -117,6 +124,7 @@ def stopAllLoops(self):

@holoimpl
def toggleMute(self, channel: int):
self.midi.send_message([CONTROL_CHANGE, 56 + channel, 0 if self.mutes[channel] else 127])
self.midi.send_message(
[CONTROL_CHANGE, 56 + channel, 0 if self.mutes[channel] else 127]
)
self.mutes[channel] = not self.mutes[channel]

62 changes: 32 additions & 30 deletions holophonor/holospecs.py
Original file line number Diff line number Diff line change
@@ -1,118 +1,120 @@
from pluggy import HookspecMarker
from rtmidi.midiutil import open_midioutput
from rtmidi import API_UNIX_JACK

holospec = HookspecMarker('holophonor')


class Holophonor:
def __init__(self, hook, port, client_name='holo', plugins=[], **kwargs):
self.hook = hook
self.port = port
self.midi, self.name = open_midioutput(self.port, client_name=client_name)
self.midi, self.name = open_midioutput(
self.port, client_name=client_name, api=API_UNIX_JACK
)
self.plugins = plugins
self.loops = [None]*32
self.loops = [None] * 32
self.pulse = False
self.scenes = [None]*8
self.scenes = [None] * 8
self.current_scene = None
self.shift = False
self.mutes = [False]*4
self.mutes = [False] * 4
self.overdub = False
self.cut = False
self.tap = False

@holospec
def playLoop(self, loop: int, volume: int):
'''play loop at volume'''

@holospec
def stopLoop(self, loop: int):
'''pause loop'''

@holospec
def recordLoop(self, loop: int):
'''record loop'''

@holospec
def eraseLoop(self, loop: int):
'''erase the selected loop'''

@holospec
def clearLoop(self, loop: int):
'''clear the button on release for the selected loop'''

@holospec
def overdubLoop(self, loop: int):
'''overdub the selected loop'''

@holospec
def recallScene(self, scene: int):
'''recall a scene'''

@holospec
def storeScene(self, scene: int):
'''store a scene'''

@holospec
def eraseScene(self, scene: int):
'''erase a scene'''

@holospec
def clearScene(self, scene: int):
'''release the scene button'''

@holospec
def toggleShift(self):
'''toggle shift mode'''

@holospec
def toggleCut(self):
'''toggle cut mode'''

@holospec
def toggleOverdub(self):
'''toggle overdub mode'''

@holospec
def deletePulse(self):
'''delete pulse'''

@holospec
def clearPulse(self):
'''release pulse button'''

@holospec
def stopAllLoops(self):
'''stop all loops'''

@holospec
def tapPulse(self):
'''tap pulse'''

@holospec
def toggleMute(self, channel: int):
'''toggle mute on channel'''

@holospec
def toggleFX(self, fx: int):
'''toggle fx'''

@holospec
def setDrumPatch(self, patch: int):
'''set drum pad patch'''

@holospec
def setDrumBank(self, bank: int):
'''set drum pad bank'''

@holospec
def playNote(self, note: tuple):
'''play a synth note'''

@holospec
def clearNote(self, note: tuple):
'''clear button from note'''

@holospec
def close(self):
'''close the connection'''


Loading

0 comments on commit 389cf63

Please sign in to comment.