/* Calf Box, an open source musical instrument. Copyright (C) 2010-2011 Krzysztof Foltman This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "app.h" #include "config.h" #include "config-api.h" #include "dom.h" #include "engine.h" #include "instr.h" #include "io.h" #include "layer.h" #include "menu.h" #include "menuitem.h" #include "midi.h" #include "module.h" #include "pattern.h" #include "rt.h" #include "scene.h" #include "scripting.h" #include "song.h" #include "tarfile.h" #include "ui.h" #include "wavebank.h" #include #include #include #include #include #include #if USE_NCURSES #include #endif #include #include #include #include static const char *short_options = "i:c:" #if USE_PYTHON "r:" #endif "e:s:t:b:d:D:N:o:nmhpP"; static struct option long_options[] = { {"help", 0, 0, 'h'}, {"no-ui", 0, 0, 'n'}, {"play", 0, 0, 'p'}, {"no-io", 1, 0, 'N'}, {"instrument", 1, 0, 'i'}, {"scene", 1, 0, 's'}, {"effect", 1, 0, 'e'}, {"config", 1, 0, 'c'}, {"metronome", 0, 0, 'm'}, {"tempo", 1, 0, 't'}, {"beats", 1, 0, 'b'}, {"drum-pattern", 1, 0, 'd'}, {"drum-track", 1, 0, 'D'}, #if USE_PYTHON {"run-script", 1, 0, 'r'}, #endif {"output", 1, 0, 'o'}, {0,0,0,0}, }; void print_help(char *progname) { printf("Usage: %s [options]\n" "\n" "Options:\n" " -h | --help Show this help text\n" " -m | --metronome Create a simple metronome pattern\n" #if USE_NCURSES " -n | --no-ui Do not start the user interface\n" #endif " -p | --play Start pattern playback (default for -d/-D)\n" " -P | --no-play Don't start pattern playback\n" " -N | --no-io Use off-line processing instead of JACK I/O\n" " -d | --drum-pattern

Load drum pattern with a given name\n" " -D | --drum-track Load drum track with a given name\n" " -t | --tempo Use given tempo (specified in beats/min)\n" " -b | --beats Use given beats/bar\n" " -e | --effect Override master effect with preset \n" " -i | --instrument Load instrument as a single-instrument scene\n" " -s | --scene Load a scene \n" " -c | --config Use specified config file instead of default\n" #if USE_PYTHON " -r | --run-script Run a Python script from a given file\n" #endif " -o | --output Write the first stereo output to a WAV file\n" "\n", progname); exit(0); } #if USE_PYTHON // This is a workaround for what I consider a defect in pyconfig.h #undef _XOPEN_SOURCE #undef _POSIX_C_SOURCE #include static gboolean set_error_from_python(GError **error) { PyObject *ptype = NULL, *pvalue = NULL, *ptraceback = NULL; PyErr_Fetch(&ptype, &pvalue, &ptraceback); PyObject *ptypestr = PyObject_Str(ptype); PyObject *pvaluestr = PyObject_Str(pvalue); PyObject *ptypestr_unicode = PyUnicode_AsUTF8String(ptypestr); PyObject *pvaluestr_unicode = PyUnicode_AsUTF8String(pvaluestr); g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "%s: %s", PyBytes_AsString(ptypestr_unicode), PyBytes_AsString(pvaluestr_unicode)); Py_DECREF(pvaluestr_unicode); Py_DECREF(ptypestr_unicode); //g_error("%s:%s", PyString_AsString(ptypestr), PyString_AsString(pvaluestr)); Py_DECREF(ptypestr); Py_DECREF(pvaluestr); Py_DECREF(ptype); Py_XDECREF(pvalue); Py_XDECREF(ptraceback); return FALSE; } void cbox_script_run(const char *name) { FILE *fp = fopen(name, "rb"); if (!fp) { g_warning("Cannot open script file '%s': %s", name, strerror(errno)); return; } // (_cbox module is discontinued, use _cbox2) // PyImport_AppendInittab("_cbox", &PyInit_cbox); Py_Initialize(); #if 0 if (PyType_Ready(&CboxCallbackType) < 0) { g_warning("Cannot install the C callback type"); return; } Py_INCREF(&CboxCallbackType); engine_initialised = TRUE; #endif if (PyRun_SimpleFile(fp, name) == 1) { GError *error = NULL; set_error_from_python(&error); cbox_print_error(error); } Py_Finalize(); } #endif #if USE_NCURSES static int (*old_menu_on_idle)(struct cbox_ui_page *page); static int on_idle_with_ui_poll(struct cbox_ui_page *page) { cbox_app_on_idle(NULL, NULL); if (old_menu_on_idle) return old_menu_on_idle(page); else return 0; } void run_ui() { struct cbox_menu_state *st = NULL; struct cbox_menu_page *page = cbox_menu_page_new(); cbox_ui_start(); old_menu_on_idle = page->page.on_idle; page->page.on_idle = on_idle_with_ui_poll; struct cbox_menu *main_menu = create_main_menu(); st = cbox_menu_state_new(page, main_menu, stdscr, NULL); page->state = st; cbox_ui_run(&page->page); cbox_ui_stop(); cbox_menu_state_destroy(st); cbox_menu_page_destroy(page); cbox_menu_destroy(main_menu); } #endif void run_no_ui() { printf("Ready. Press ENTER to exit.\n"); fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) | O_NONBLOCK); do { int ch = getchar(); if (ch == 10 || (ch == -1 && errno != EWOULDBLOCK)) break; usleep(100000); cbox_app_on_idle(NULL, NULL); } while(1); fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) &~ O_NONBLOCK); } int main(int argc, char *argv[]) { struct cbox_open_params params; struct cbox_layer *layer; const char *config_name = NULL; const char *instrument_name = NULL; const char *scene_name = NULL; const char *effect_preset_name = NULL; const char *drum_pattern_name = NULL; const char *drum_track_name = NULL; #if USE_PYTHON const char *script_name = NULL; #endif const char *output_name = NULL; char *instr_section = NULL; struct cbox_scene *scene = NULL; int metronome = 0; int bpb = 0; float tempo = 0; GError *error = NULL; #if USE_NCURSES gboolean no_ui = FALSE; #endif gboolean no_io = FALSE; int play_immediately = 0; int no_io_srate = 0; cbox_dom_init(); while(1) { int option_index; int c = getopt_long(argc, argv, short_options, long_options, &option_index); if (c == -1) break; switch (c) { case 'c': config_name = optarg; break; case 'i': instrument_name = optarg; break; case 'o': output_name = optarg; break; case 's': scene_name = optarg; break; case 'e': effect_preset_name = optarg; break; case 'd': drum_pattern_name = optarg; break; case 'D': drum_track_name = optarg; break; #if USE_PYTHON case 'r': script_name = optarg; break; #endif case 'm': metronome = 1; break; case 'n': #if USE_NCURSES no_ui = TRUE; #endif break; case 'N': no_io = TRUE; no_io_srate = atoi(optarg); break; case 'p': play_immediately = 1; break; case 'P': play_immediately = -1; break; case 'b': bpb = atoi(optarg); break; case 't': tempo = atof(optarg); break; case 'h': case '?': print_help(argv[0]); return 0; } } app.tarpool = cbox_tarpool_new(); app.document = cbox_document_new(); app.rt = cbox_rt_new(app.document); app.engine = cbox_engine_new(app.document, app.rt); app.rt->engine = app.engine; cbox_config_init(config_name); if (tempo < 1) tempo = cbox_config_get_float("master", "tempo", 120); if (bpb < 1) bpb = cbox_config_get_int("master", "beats_per_bar", 4); if (no_io) { cbox_rt_set_offline(app.rt, no_io_srate, 1024); } else { GError *error = NULL; if (!cbox_io_init(&app.io, ¶ms, NULL, &error)) { fprintf(stderr, "Cannot initialise sound I/O: %s\n", (error && error->message) ? error->message : "Unknown error"); return 1; } cbox_rt_set_io(app.rt, &app.io); } cbox_wavebank_init(); if (!scene_name && !instrument_name) { scene_name = cbox_config_get_string("init", "scene"); instrument_name = cbox_config_get_string("init", "instrument"); if (!scene_name && !instrument_name) { if (cbox_config_has_section("scene:default")) scene_name = "default"; else if (cbox_config_has_section("instrument:default")) instrument_name = "default"; } } scene = cbox_scene_new(app.document, app.engine); if (!scene) goto fail; if (scene_name) { app.current_scene_name = g_strdup_printf("scene:%s", scene_name); if (!cbox_scene_load(scene, scene_name, &error)) goto fail; } else if (instrument_name) { app.current_scene_name = g_strdup_printf("instrument:%s", instrument_name); layer = cbox_layer_new_with_instrument(scene, instrument_name, &error); if (!layer) goto fail; if (!cbox_scene_add_layer(scene, layer, &error)) goto fail; } if (!effect_preset_name) effect_preset_name = cbox_config_get_string("master", "effect"); if (effect_preset_name && *effect_preset_name) { app.engine->effect = cbox_module_new_from_fx_preset(effect_preset_name, app.document, app.rt, app.engine, &error); if (!app.engine->effect) goto fail; } cbox_master_set_tempo(app.engine->master, tempo); cbox_master_set_timesig(app.engine->master, bpb, 4); if (output_name && scene) { GError *error = NULL; if (!cbox_recording_source_attach(&scene->rec_stereo_outputs[0], cbox_recorder_new_stream(app.engine, app.rt, output_name), &error)) cbox_print_error(error); } cbox_rt_start(app.rt, NULL); if (drum_pattern_name) cbox_song_use_looped_pattern(app.engine->master->song, cbox_midi_pattern_load(app.engine->master->song, drum_pattern_name, 1, app.engine->master->ppqn_factor)); else if (drum_track_name) cbox_song_use_looped_pattern(app.engine->master->song, cbox_midi_pattern_load_track(app.engine->master->song, drum_track_name, 1, app.engine->master->ppqn_factor)); else if (metronome) cbox_song_use_looped_pattern(app.engine->master->song, cbox_midi_pattern_new_metronome(app.engine->master->song, app.engine->master->timesig_num, app.engine->master->ppqn_factor)); gboolean has_song = drum_pattern_name || drum_track_name || metronome; if (play_immediately == 1 || (play_immediately != -1 && has_song)) cbox_master_play(app.engine->master); #if USE_PYTHON if (script_name) cbox_script_run(script_name); else #endif #if USE_NCURSES if (!no_ui) run_ui(); else run_no_ui(); #else run_no_ui(); #endif cbox_rt_stop(app.rt); if (!no_io) cbox_io_close(&app.io); goto ok; fail: fprintf(stderr, "Cannot start: %s\n", error ? error->message : "unknown error"); ok: if (error) g_error_free(error); if (app.engine->effect) { CBOX_DELETE(app.engine->effect); app.engine->effect = NULL; } CBOX_DELETE(app.engine); CBOX_DELETE(app.rt); cbox_tarpool_destroy(app.tarpool); if (cbox_wavebank_get_maxbytes() > 0) g_message("Max waveform usage: %f MB", (float)(cbox_wavebank_get_maxbytes() / 1048576.0)); cbox_document_destroy(app.document); cbox_wavebank_close(); cbox_config_close(); g_free(instr_section); cbox_dom_close(); return 0; }