-Initial import
git-svn-id: http://svn.fuzzle.org/mloop/mloop/trunk@1 ba049829-c6ef-42ef-81ac-908dd8d2e907remotes/git-svn@10
commit
90e082d82b
|
@ -0,0 +1,250 @@
|
|||
/* $Id$ */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <termios.h>
|
||||
#include <jack/jack.h>
|
||||
#include <jack/midiport.h>
|
||||
#include "jack.h"
|
||||
|
||||
Jack::Jack()
|
||||
{
|
||||
m_connected = false;
|
||||
m_recording = false;
|
||||
m_buffer = new RingBuffer(2048);
|
||||
}
|
||||
|
||||
Jack::~Jack()
|
||||
{
|
||||
Disconnect();
|
||||
delete m_buffer;
|
||||
}
|
||||
|
||||
bool Jack::Connect()
|
||||
{
|
||||
if (m_connected) return true;
|
||||
|
||||
jack_status_t status;
|
||||
m_client = jack_client_open("mloop", JackNoStartServer, &status);
|
||||
if (m_client == NULL) {
|
||||
if (status & JackServerFailed) {
|
||||
fprintf(stderr, "JACK server not running\n");
|
||||
} else {
|
||||
fprintf(stderr, "jack_client_open() failed, status = 0x%2.0x\n", status);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
m_connected = true;
|
||||
|
||||
jack_on_shutdown(m_client, &ShutdownCallbackHandler, this);
|
||||
jack_set_process_callback(m_client, &ProcessCallbackHandler, this);
|
||||
|
||||
m_input = jack_port_register(m_client, "input", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
|
||||
m_output = jack_port_register(m_client, "output", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);
|
||||
|
||||
m_control = jack_port_register(m_client, "control", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput|JackPortIsTerminal, 0);
|
||||
|
||||
jack_activate(m_client);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Jack::Disconnect()
|
||||
{
|
||||
if (!m_connected) return;
|
||||
|
||||
m_connected = false;
|
||||
|
||||
jack_deactivate(m_client);
|
||||
jack_client_close(m_client);
|
||||
}
|
||||
|
||||
void Jack::ShutdownCallback()
|
||||
{
|
||||
m_connected = false;
|
||||
printf("shutdowncallback\n");
|
||||
}
|
||||
|
||||
int Jack::ProcessCallback(jack_nframes_t nframes)
|
||||
{
|
||||
void *input = jack_port_get_buffer(m_input, nframes);
|
||||
void *output = jack_port_get_buffer(m_output, nframes);
|
||||
|
||||
void *control = jack_port_get_buffer(m_control, nframes);
|
||||
|
||||
jack_midi_clear_buffer(output);
|
||||
|
||||
/* Copy from input to output */
|
||||
jack_nframes_t event_count = jack_midi_get_event_count(input);
|
||||
jack_nframes_t event_index = 0;
|
||||
|
||||
jack_midi_event_t ev;
|
||||
if (event_index < event_count) {
|
||||
jack_midi_event_get(&ev, input, event_index++);
|
||||
} else {
|
||||
ev.time = UINT_MAX;
|
||||
}
|
||||
|
||||
for (jack_nframes_t frame = 0; frame < nframes; frame++) {
|
||||
while (ev.time == frame) {
|
||||
jack_midi_event_write(output, ev.time, ev.buffer, ev.size);
|
||||
|
||||
if (m_recording) {
|
||||
/* Don't add the event to the buffer if it will become full.
|
||||
* This includes the case where the event would actually fit,
|
||||
* but would cause the buffer to be full. This prevents the
|
||||
* need for extra logic to determine if the buffer is full
|
||||
* or empty.
|
||||
*/
|
||||
if (m_buffer->Free() > sizeof ev.time + sizeof ev.size + ev.size) {
|
||||
m_buffer->Write((uint8_t *)&m_recording_time, sizeof m_recording_time);
|
||||
m_buffer->Write((uint8_t *)&ev.time, sizeof ev.time);
|
||||
m_buffer->Write((uint8_t *)&ev.size, sizeof ev.size);
|
||||
m_buffer->Write((uint8_t *)ev.buffer, ev.size);
|
||||
} else {
|
||||
printf("Buffer full, dropping input!\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (event_index < event_count) {
|
||||
jack_midi_event_get(&ev, input, event_index++);
|
||||
} else {
|
||||
ev.time = UINT_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_LOOPS; i++) {
|
||||
m_loops[i].PlayFrame(output, frame);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_recording) {
|
||||
m_recording_time += nframes;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct termios orig_termios;
|
||||
|
||||
void reset_terminal_mode()
|
||||
{
|
||||
tcsetattr(0, TCSANOW, &orig_termios);
|
||||
}
|
||||
|
||||
void set_conio_terminal_mode()
|
||||
{
|
||||
struct termios new_termios;
|
||||
|
||||
/* take two copies - one for now, one for later */
|
||||
tcgetattr(0, &orig_termios);
|
||||
memcpy(&new_termios, &orig_termios, sizeof(new_termios));
|
||||
|
||||
/* register cleanup handler, and set the new terminal mode */
|
||||
atexit(reset_terminal_mode);
|
||||
cfmakeraw(&new_termios);
|
||||
tcsetattr(0, TCSANOW, &new_termios);
|
||||
}
|
||||
|
||||
int kbhit()
|
||||
{
|
||||
struct timeval tv = { 0L, 0L };
|
||||
fd_set fds;
|
||||
FD_SET(0, &fds);
|
||||
return select(1, &fds, NULL, NULL, &tv);
|
||||
}
|
||||
|
||||
int getch()
|
||||
{
|
||||
int r;
|
||||
unsigned char c;
|
||||
if ((r = read(0, &c, sizeof(c))) < 0) {
|
||||
return r;
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
void Jack::Run()
|
||||
{
|
||||
set_conio_terminal_mode();
|
||||
|
||||
jack_nframes_t recording_time;
|
||||
jack_midi_event_t ev;
|
||||
ev.time = UINT_MAX;
|
||||
|
||||
m_recording = false;
|
||||
|
||||
for (;;) {
|
||||
usleep(10000);
|
||||
if (ev.time == UINT_MAX) {
|
||||
if (m_buffer->Size() >= sizeof recording_time + sizeof ev.time + sizeof ev.size) {
|
||||
m_buffer->Read((uint8_t *)&recording_time, sizeof recording_time);
|
||||
m_buffer->Read((uint8_t *)&ev.time, sizeof ev.time);
|
||||
m_buffer->Read((uint8_t *)&ev.size, sizeof ev.size);
|
||||
}
|
||||
} else {
|
||||
if (m_buffer->Size() >= ev.size) {
|
||||
ev.buffer = (jack_midi_data_t *)malloc(ev.size);
|
||||
m_buffer->Read((uint8_t *)ev.buffer, ev.size);
|
||||
}
|
||||
|
||||
if (m_recording) {
|
||||
printf("Recording event for loop %d\n", m_recording_loop);
|
||||
m_loops[m_recording_loop].AddEvent(recording_time, &ev);
|
||||
}
|
||||
ev.time = UINT_MAX;
|
||||
}
|
||||
|
||||
if (kbhit()) {
|
||||
char c = getch();
|
||||
|
||||
switch (c) {
|
||||
case 3:
|
||||
case 'q': return;
|
||||
|
||||
case 'r':
|
||||
if (m_recording) {
|
||||
m_recording = false;
|
||||
m_loops[m_recording_loop].SetLength(m_recording_time);
|
||||
printf("Finished recording loop %d\n", m_recording_loop);
|
||||
} else {
|
||||
m_recording_time = 0;
|
||||
m_recording = true;
|
||||
printf("Started recording loop %d\n", m_recording_loop);
|
||||
}
|
||||
break;
|
||||
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
if (!m_recording) {
|
||||
m_recording_loop = c - '1';
|
||||
printf("Selected recording loop %d\n", m_recording_loop);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'z':
|
||||
case 'x':
|
||||
printf("Starting loop %d (%s)\n", m_recording_loop, c == 'x' ? "loop" : "once");
|
||||
m_loops[m_recording_loop].Start(c == 'x');
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
printf("Stopping loop %d\n", m_recording_loop);
|
||||
m_loops[m_recording_loop].Stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/* $Id$ */
|
||||
|
||||
#ifndef JACK_H
|
||||
#define JACK_H
|
||||
|
||||
#include <jack/jack.h>
|
||||
#include "loop.h"
|
||||
#include "ringbuffer.h"
|
||||
|
||||
#define NUM_LOOPS 9
|
||||
|
||||
class Jack {
|
||||
private:
|
||||
bool m_connected;
|
||||
jack_client_t *m_client;
|
||||
jack_port_t *m_input;
|
||||
jack_port_t *m_output;
|
||||
jack_port_t *m_control;
|
||||
|
||||
Loop m_loops[NUM_LOOPS];
|
||||
RingBuffer *m_buffer;
|
||||
|
||||
bool m_recording;
|
||||
int m_recording_loop;
|
||||
jack_nframes_t m_recording_time;
|
||||
|
||||
static void ShutdownCallbackHandler(void *arg)
|
||||
{
|
||||
((Jack *)arg)->ShutdownCallback();
|
||||
}
|
||||
static int ProcessCallbackHandler(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
return ((Jack *)arg)->ProcessCallback(nframes);
|
||||
}
|
||||
|
||||
void ShutdownCallback();
|
||||
int ProcessCallback(jack_nframes_t nframes);
|
||||
|
||||
public:
|
||||
Jack();
|
||||
~Jack();
|
||||
|
||||
bool Connect();
|
||||
void Disconnect();
|
||||
void Run();
|
||||
};
|
||||
|
||||
#endif /* JACK_H */
|
|
@ -0,0 +1,83 @@
|
|||
/* $Id$ */
|
||||
|
||||
#include <stdio.h>
|
||||
#include "loop.h"
|
||||
|
||||
void Loop::PlayFrame(void *port_buffer, jack_nframes_t frame)
|
||||
{
|
||||
if (m_state == LS_IDLE) return;
|
||||
|
||||
if (m_state == LS_STOPPING) {
|
||||
printf("Stopping, so send all notes off!\n");
|
||||
|
||||
uint8_t buffer[3];
|
||||
buffer[1] = 0x78;
|
||||
buffer[2] = 0;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
buffer[0] = 0xB0 + i;
|
||||
jack_midi_event_write(port_buffer, frame, buffer, sizeof buffer);
|
||||
}
|
||||
|
||||
m_state = LS_IDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
for (; m_iterator != m_events.end(); ++m_iterator) {
|
||||
|
||||
jack_midi_event_t &event = (*m_iterator).first;
|
||||
jack_nframes_t position = (*m_iterator).second;
|
||||
if (event.time + position > m_position) break;
|
||||
|
||||
jack_midi_event_write(port_buffer, event.time, event.buffer, event.size);
|
||||
}
|
||||
|
||||
m_position++;
|
||||
|
||||
if (m_position == m_length) {
|
||||
if (m_state == LS_PLAY_ONCE) {
|
||||
m_state = LS_IDLE;
|
||||
}
|
||||
printf("Completed %u frames\n", m_position);
|
||||
m_position = 0;
|
||||
m_iterator = m_events.begin();
|
||||
}
|
||||
}
|
||||
|
||||
void Loop::AddEvent(jack_nframes_t position, jack_midi_event_t *event)
|
||||
{
|
||||
Event e;
|
||||
e.first = *event;
|
||||
e.second = position;
|
||||
m_events.push_back(e);
|
||||
}
|
||||
|
||||
void Loop::SetLength(jack_nframes_t length)
|
||||
{
|
||||
if (m_state != LS_IDLE) return;
|
||||
|
||||
m_length = length;
|
||||
}
|
||||
|
||||
void Loop::Start(bool loop)
|
||||
{
|
||||
if (m_state != LS_IDLE) return;
|
||||
|
||||
m_position = 0;
|
||||
m_iterator = m_events.begin();
|
||||
m_state = loop ? LS_PLAY_LOOP : LS_PLAY_ONCE;
|
||||
}
|
||||
|
||||
void Loop::Stop()
|
||||
{
|
||||
m_state = LS_STOPPING;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
void Loop::Reset()
|
||||
{
|
||||
m_state = LS_IDLE;
|
||||
m_position = 0;
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,39 @@
|
|||
/* $Id$ */
|
||||
|
||||
#ifndef LOOP_H
|
||||
#define LOOP_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <list>
|
||||
#include <jack/jack.h>
|
||||
#include <jack/midiport.h>
|
||||
|
||||
enum LoopState {
|
||||
LS_IDLE,
|
||||
LS_PLAY_LOOP,
|
||||
LS_PLAY_ONCE,
|
||||
LS_STOPPING,
|
||||
};
|
||||
|
||||
typedef std::pair<jack_midi_event_t, jack_nframes_t> Event;
|
||||
typedef std::list<Event> EventList;
|
||||
|
||||
class Loop {
|
||||
private:
|
||||
jack_nframes_t m_length; ///< Length of loop, in samples.
|
||||
jack_nframes_t m_position; ///< Current position of loop, in samples.
|
||||
LoopState m_state;
|
||||
|
||||
EventList m_events;
|
||||
EventList::iterator m_iterator;
|
||||
|
||||
public:
|
||||
void PlayFrame(void *port_buffer, jack_nframes_t frame);
|
||||
void AddEvent(jack_nframes_t position, jack_midi_event_t *event);
|
||||
|
||||
void SetLength(jack_nframes_t length);
|
||||
void Start(bool loop);
|
||||
void Stop();
|
||||
};
|
||||
|
||||
#endif /* LOOP_H */
|
|
@ -0,0 +1,15 @@
|
|||
/* $Id$ */
|
||||
|
||||
#include "jack.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
Jack *j = new Jack();
|
||||
|
||||
j->Connect();
|
||||
j->Run();
|
||||
|
||||
delete j;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/* $Id$ */
|
||||
|
||||
#ifndef RINGBUFFER_H
|
||||
#define RINGBUFFER_H
|
||||
|
||||
class RingBuffer {
|
||||
private:
|
||||
uint8_t *m_buffer;
|
||||
uint8_t *m_end;
|
||||
uint8_t *m_write;
|
||||
uint8_t *m_read;
|
||||
|
||||
public:
|
||||
RingBuffer(size_t length)
|
||||
{
|
||||
m_buffer = (uint8_t *)malloc(length);
|
||||
m_end = m_buffer + length;
|
||||
m_write = m_buffer;
|
||||
m_read = m_buffer;
|
||||
}
|
||||
|
||||
~RingBuffer()
|
||||
{
|
||||
free(m_buffer);
|
||||
}
|
||||
|
||||
void Write(uint8_t *buffer, size_t length)
|
||||
{
|
||||
while (length--) {
|
||||
if (m_write == m_end) m_write = m_buffer;
|
||||
*m_write++ = *buffer++;
|
||||
}
|
||||
}
|
||||
|
||||
void Read(uint8_t *buffer, size_t length)
|
||||
{
|
||||
while (length--) {
|
||||
if (m_read == m_end) m_read = m_buffer;
|
||||
*buffer++ = *m_read++;
|
||||
}
|
||||
}
|
||||
|
||||
size_t Size() const
|
||||
{
|
||||
if (m_write >= m_read) return m_write - m_read;
|
||||
return (m_end - m_read) + (m_write - m_buffer);
|
||||
}
|
||||
|
||||
size_t Free() const
|
||||
{
|
||||
return (m_end - m_buffer) - Size();
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
m_write = m_buffer;
|
||||
m_read = m_buffer;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* RINGBUFFER_H */
|
|
@ -0,0 +1,12 @@
|
|||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
def configure(conf):
|
||||
conf.check_cfg(package='jack', uselib_store='JACK', args='--cflags --libs', atleast_version='1.9.2')
|
||||
|
||||
def build(bld):
|
||||
bld.new_task_gen(
|
||||
features = 'cxx cprogram',
|
||||
source = 'jack.cpp loop.cpp mloop.cpp',
|
||||
target = 'mloop',
|
||||
uselib = 'JACK')
|
|
@ -0,0 +1,30 @@
|
|||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
VERSION='0.0.1'
|
||||
APPNAME='mloop'
|
||||
|
||||
srcdir = '.'
|
||||
blddir = 'build'
|
||||
|
||||
def init():
|
||||
pass
|
||||
|
||||
def set_options(opt):
|
||||
opt.tool_options('compiler_cxx')
|
||||
|
||||
def configure(conf):
|
||||
conf.check_tool('compiler_cxx')
|
||||
conf.sub_config('src')
|
||||
|
||||
env = conf.env.copy()
|
||||
env.set_variant('debug')
|
||||
conf.set_env_name('debug', env)
|
||||
conf.setenv('debug')
|
||||
conf.env.CXXFLAGS = ['-Wall', '-g']
|
||||
|
||||
def build(bld):
|
||||
bld.add_subdirs('src')
|
||||
|
||||
for obj in bld.all_task_gen[:]:
|
||||
new_obj = obj.clone('debug')
|
Loading…
Reference in New Issue