Skip to content

Commit

Permalink
Test pitch modulation & update setup instructions
Browse files Browse the repository at this point in the history
  • Loading branch information
kushaangupta committed Aug 1, 2022
1 parent 0a512e4 commit 504d2a5
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 60 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ $ pip install -r requirements.txt
```

## Usage
```muselsl stream
python brain-music-player.py
```
114 changes: 56 additions & 58 deletions brain-music-player.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
FigureCanvasQTAgg as FigureCanvas
)
from matplotlib.figure import Figure
from mido import MidiFile, tick2second
from mido import MidiFile, tick2second, Message
from pylsl import StreamInfo, StreamInlet, StreamOutlet, resolve_byprop
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import pyqtSlot
Expand Down Expand Up @@ -87,7 +87,7 @@ def __init__(self):

# music timer
self.music_timer = QtCore.QTimer()
self.music_timer.setInterval(30) #msec
self.music_timer.setInterval(0) #msec
self.music_timer.timeout.connect(self.update_music)
self.music_timer.start()
self.mdata=[0]
Expand Down Expand Up @@ -122,7 +122,7 @@ def getData(self):
QtWidgets.QApplication.processEvents()
# samples,time = self.inlet.pull_chunk(timeout=.5, max_samples=CHUNK)
samples = self.btm.stream_update([0])
print(samples)
# print(samples)
self.pq.put_nowait(samples)

if self.plot_on is False:
Expand All @@ -133,8 +133,8 @@ def getData(self):
def getAudio(self):
QtWidgets.QApplication.processEvents()

mid = MidiFile('data/Never-Gonna-Give-You-Up-2.mid')
BUFFER_LEN = 5 # secs
mid = MidiFile('data/Never_Gonna_Give_You_Up.mid')
BUFFER_LEN = self.btm.buffer_len # secs
past_dur = 0 # secs
past_msg_itr = 0
tempo = None
Expand All @@ -148,65 +148,64 @@ def getAudio(self):
break

#the max number of samples pulled from lsl stream
srate = 250
CHUNK = srate*BUFFER_LEN
CHUNK = self.btm.freqs*BUFFER_LEN

buff = np.zeros(srate*BUFFER_LEN)*8 #init array that will hold 2 seconds (500 samples) of eeg data to display
buff = np.zeros(self.btm.freqs*BUFFER_LEN)*8 #init array that will hold 2 seconds (500 samples) of eeg data to display
#init color map which converts a numeric value to rgb color, the range of the value is between 0 and 1

pygame.mixer.init()

tot_msgs = len(mid.tracks[0])
tstart = time.time()
# time.sleep(BUFFER_LEN)
LOW, HIGH = -8192, 8191
while(self.music_on):

QtWidgets.QApplication.processEvents()
tstart = time.time()
#pull a chunk of eeg data from lsl stream
samples = self.plotdata
samples = self.btm.eeg_buffer
#check that samples contains values

if len(samples) > 0:
#samples is a length <= 250 list of lists which contain one value of the eeg stream
#convert it to a shape (1,250) array (1 channel, 250 timesteps)
chunk_data = np.vstack(samples).T
channel_1 = chunk_data[0] #get a shape (250,) array of data from the first (and only) channel

#update data array
data = live_matplot_funcs.update_data_array(buff,channel_1)

dur = past_dur
subset_midi = deepcopy(meta_mid)
for itr in range(past_msg_itr, tot_msgs):
msg = mid.tracks[0][itr]
past_msg_itr += 1
if msg.type == 'set_tempo':
tempo = msg.tempo
if (
not msg.is_meta
):
if msg.type == 'note_on':
msg.velocity # ranges from 0-127
msg.velocity = np.clip((np.random.randint(-32, 32) + msg.velocity,), 30, 115)[0]
# https://music.stackexchange.com/questions/86241/how-can-i-split-a-midi-file-programatically
curr_time = tick2second(msg.time, mid.ticks_per_beat, tempo)
if dur + curr_time - past_dur > BUFFER_LEN:
past_dur = dur
break
dur += curr_time
if dur >= past_dur:
subset_midi.tracks[0].append(msg)
subset_midi.tracks[0].append(mid.tracks[0][-1])

bytestream = BytesIO()
subset_midi.save(file=bytestream)
bytestream.seek(0)
pygame.mixer.music.load(bytestream)
pygame.mixer.music.play()
if past_msg_itr >= tot_msgs:
self.music_on = False
time.sleep(BUFFER_LEN - (time.time() - tstart))
std = np.std(samples, axis=0)[0]

dur = past_dur
subset_midi = deepcopy(meta_mid)
for itr in range(past_msg_itr, tot_msgs):
msg = mid.tracks[0][itr]
past_msg_itr += 1
if msg.type == 'set_tempo':
tempo = msg.tempo
if (
not msg.is_meta
):
if msg.type == 'note_on':
msg.velocity # ranges from 0-127
# https://music.stackexchange.com/questions/86241/how-can-i-split-a-midi-file-programatically
curr_time = tick2second(msg.time, mid.ticks_per_beat, tempo)
if dur + curr_time - past_dur > BUFFER_LEN:
past_dur = dur
break
dur += curr_time
if dur >= past_dur:
dev = np.random.normal(scale=std)
subset_midi.tracks[0].append(Message(
'pitchwheel',
channel=0,
pitch=round(min(max(dev, LOW), HIGH)),
time=msg.time
))
subset_midi.tracks[0].append(msg)
subset_midi.tracks[0].append(mid.tracks[0][-1])

bytestream = BytesIO()
subset_midi.save(file=bytestream)
bytestream.seek(0)
pygame.mixer.music.load(bytestream)
pygame.mixer.music.play()
if past_msg_itr >= tot_msgs:
self.music_on = False
time.sleep(BUFFER_LEN - (time.time() - tstart))
if self.music_on is False:
break

Expand All @@ -232,14 +231,13 @@ def start_music(self):
self.music_worker = Worker(self.start_music_stream, )
self.threadpool.start(self.music_worker)
self.mreference_plot = None
self.music_timer.setInterval(30) #msec
self.music_timer.setInterval(0) #msec

def stop_music(self):

self.music_on = False
# with self.mq.mutex:
# self.mq.queue.clear()


def start_music_stream(self):
self.getAudio()
Expand All @@ -249,8 +247,8 @@ def start_plot_stream(self):

def update_feature(self,value):
self.feature = self.features_list.index(value)
print(self.feature)
# print(self.feature)

def update_channel(self,state):
if state == QtCore.Qt.Checked:
print('Checked')
Expand All @@ -265,19 +263,19 @@ def update_plot(self):
QtWidgets.QApplication.processEvents()
try:
self.pdata = self.pq.get_nowait()

except queue.Empty:
break

chunk_data = np.vstack(self.pdata).T
new_data = chunk_data[0] #get a shape (250,)
# chunk_data = np.vstack(self.pdata).T
# new_data = chunk_data[0] #get a shape (250,)
# self.plotdata = live_matplot_funcs.update_data_array(self.plotdata, new_data)

# self.plotdata[ -len(new_data) : ] = new_data

self.plotdata = new_data
self.plotdata = self.btm.eeg_buffer
if self.preference_plot is None:
plot_refs = self.canvas.axes.plot( self.plotdata, color=(0,1,0.29))
plot_refs = self.canvas.axes.plot(self.plotdata, color=(0,1,0.29))
self.preference_plot = plot_refs[0]
else:
self.preference_plot.set_ydata(self.plotdata)
Expand Down
3 changes: 1 addition & 2 deletions btm.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def stream_update(self, channels):

ch_data = np.array(eeg_data)[:, channels]
self.eeg_buffer = self.update_buffer(self.eeg_buffer, ch_data)
print(self.eeg_buffer)
# print(self.eeg_buffer)
return self.eeg_buffer


Expand Down Expand Up @@ -120,7 +120,6 @@ def buffer_to_file(self, eeg_buffer):
if eeg_buffer[0,0] != 0:
with open('eegs.csv', 'w', newline='') as csvfile:
for row in eeg_buffer:
print
spamwriter = csv.writer(csvfile, delimiter=' ')
spamwriter.writerow(row)

Expand Down
Binary file added data/Never_Gonna_Give_You_Up.mid
Binary file not shown.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ mido
numpy
pygame
pylsl
muselsl
PyQt5

0 comments on commit 504d2a5

Please sign in to comment.