commit 90e082d82be8a2903c1fd5802941cf0808f9ff1c Author: petern Date: Tue Jul 21 20:23:12 2009 +0000 -Initial import git-svn-id: http://svn.fuzzle.org/mloop/mloop/trunk@1 ba049829-c6ef-42ef-81ac-908dd8d2e907 diff --git a/src/jack.cpp b/src/jack.cpp new file mode 100644 index 0000000..bc7a465 --- /dev/null +++ b/src/jack.cpp @@ -0,0 +1,250 @@ +/* $Id$ */ + +#include +#include +#include +#include +#include +#include +#include +#include +#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; + } + } + } +} diff --git a/src/jack.h b/src/jack.h new file mode 100644 index 0000000..42cd8b8 --- /dev/null +++ b/src/jack.h @@ -0,0 +1,48 @@ +/* $Id$ */ + +#ifndef JACK_H +#define JACK_H + +#include +#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 */ diff --git a/src/loop.cpp b/src/loop.cpp new file mode 100644 index 0000000..9534888 --- /dev/null +++ b/src/loop.cpp @@ -0,0 +1,83 @@ +/* $Id$ */ + +#include +#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; +} +*/ diff --git a/src/loop.h b/src/loop.h new file mode 100644 index 0000000..cc5c4e6 --- /dev/null +++ b/src/loop.h @@ -0,0 +1,39 @@ +/* $Id$ */ + +#ifndef LOOP_H +#define LOOP_H + +#include +#include +#include +#include + +enum LoopState { + LS_IDLE, + LS_PLAY_LOOP, + LS_PLAY_ONCE, + LS_STOPPING, +}; + +typedef std::pair Event; +typedef std::list 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 */ diff --git a/src/mloop.cpp b/src/mloop.cpp new file mode 100644 index 0000000..97ab29b --- /dev/null +++ b/src/mloop.cpp @@ -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; +} diff --git a/src/ringbuffer.h b/src/ringbuffer.h new file mode 100644 index 0000000..e8775b4 --- /dev/null +++ b/src/ringbuffer.h @@ -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 */ diff --git a/src/wscript b/src/wscript new file mode 100644 index 0000000..bdb41bb --- /dev/null +++ b/src/wscript @@ -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') diff --git a/waf b/waf new file mode 100755 index 0000000..156a32e Binary files /dev/null and b/waf differ diff --git a/wscript b/wscript new file mode 100644 index 0000000..f2bb2ab --- /dev/null +++ b/wscript @@ -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')