/* opcodes.c Copyright (C) 2025 Richard Knight This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "handling.h" #include "shout.h" #include "lame/lame.h" #include "limits.h" #define ARGT static constexpr char const* #define ARGO ARGT otypes #define ARGI ARGT itypes char* shout_strerror(csnd::Csound* csound, int val) { const char* output; switch (val) { case SHOUTERR_INSANE: output = "bad parameters"; break; case SHOUTERR_MALLOC: output = "could not allocate memory"; break; case SHOUTERR_NOCONNECT: output = "could not establish connection"; break; case SHOUTERR_NOLOGIN: output = "login refused"; break; case SHOUTERR_SOCKET: output = "socket error"; break; case SHOUTERR_METADATA: output = "metadata update error"; break; case SHOUTERR_CONNECTED: output = "connected, but disconnected state required"; break; case SHOUTERR_UNCONNECTED: output = "disconnected, but connected state required"; break; case SHOUTERR_UNSUPPORTED: output = "unsupported operation"; break; } return csound->strdup((char*) output); } const char* handleName = "shout"; struct ShoutSession { shout_t* shout; shout_metadata_t* meta; bool open; }; struct shoutinit : csnd::Plugin<1, 5> { ARGO = "i"; ARGI = "SiSSS"; int init() { ShoutSession* session = (ShoutSession*) csound->malloc(sizeof(ShoutSession)); outargs[0] = createHandle(csound, &session, handleName); STRINGDAT &host = inargs.str_data(0); int port = (int) inargs[1]; STRINGDAT &user = inargs.str_data(2); STRINGDAT &password = inargs.str_data(3); STRINGDAT &mount = inargs.str_data(4); if (!(session->shout = shout_new())) { return csound->init_error("Could not allocate shout"); } if (shout_set_host(session->shout, host.data) != SHOUTERR_SUCCESS) { return csound->init_error("Could not set host"); } if (shout_set_protocol(session->shout, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS) { return csound->init_error("Could not set protocol"); } if (shout_set_port(session->shout, port) != SHOUTERR_SUCCESS) { return csound->init_error("Could not set port"); } if (strcmp(password.data, "") != 0 && shout_set_password(session->shout, password.data) != SHOUTERR_SUCCESS) { return csound->init_error("Could not set password"); } if (strcmp(mount.data, "") != 0 && shout_set_mount(session->shout, mount.data) != SHOUTERR_SUCCESS) { return csound->init_error("Could not set mount"); } if (strcmp(user.data, "") != 0 && shout_set_user(session->shout, user.data) != SHOUTERR_SUCCESS) { return csound->init_error("Could not set user"); } if (shout_set_format(session->shout, SHOUT_FORMAT_MP3) != SHOUTERR_SUCCESS) { return csound->init_error("Could not set format"); } session->meta = NULL; session->open = false; return OK; } }; struct shoutopen : csnd::InPlug<1> { ARGO = ""; ARGI = "i"; int init() { ShoutSession* session; if (!(session = getHandle(csound, args[0], handleName))) { return csound->init_error("Invalid shout session handle"); } int res = shout_open(session->shout); if (res != SHOUTERR_SUCCESS) { shout_close(session->shout); char* err = csound->strdup((char*) shout_get_error(session->shout)); return csound->init_error(err); } session->meta = NULL; session->open = true; return OK; } }; struct shoutclose : csnd::InPlug<1> { ARGO = ""; ARGI = "i"; int init() { ShoutSession* session; if (!(session = getHandle(csound, args[0], handleName))) { return csound->init_error("Invalid shout session handle"); } if (session->open) { shout_close(session->shout); session->open = false; } if (session->meta != NULL) { shout_metadata_free(session->meta); session->meta = NULL; } return OK; } }; int setdata(csnd::Csound* csound, MYFLT id, int type, char* value) { ShoutSession* session; if (!(session = getHandle(csound, id, handleName))) { return csound->init_error("Invalid shout session handle"); } shout_t* s = session->shout; int res; switch (type) { case 0: res = shout_set_name(s, value); break; case 1: res = shout_set_public(s, !strcmp(value, "public")); break; case 2: res = shout_set_url(s, value); break; case 3: res = shout_set_genre(s, value); break; case 4: res = shout_set_description(s, value); break; } if (res != SHOUTERR_SUCCESS) { return csound->init_error((char*) shout_get_error(session->shout)); } else { return OK; } } #define SHOUTSET(settype)\ ARGO = "";\ ARGI = "iS";\ int init() {\ STRINGDAT &value = args.str_data(1);\ return setdata(csound, args[0], settype, value.data);\ }\ struct shoutsetname : csnd::InPlug<2> { SHOUTSET(0) }; struct shoutseturl : csnd::InPlug<2> { SHOUTSET(2); }; struct shoutsetgenre : csnd::InPlug<2> { SHOUTSET(3); }; struct shoutsetdescription : csnd::InPlug<2> { SHOUTSET(4); }; struct shoutsetpublic : csnd::InPlug<2> { ARGO = ""; ARGI = "ii"; int init() { return setdata(csound, args[0], 1, (char*) ((args[1] == 0) ? "private" : "public")); } }; struct shoutsetmeta : csnd::InPlug<3> { ARGO = ""; ARGI = "iSS"; int init() { ShoutSession* session; if (!(session = getHandle(csound, args[0], handleName))) { return csound->init_error("Invalid shout session handle"); } if (!session->open) { return csound->init_error("Connection is not open"); } if (session->meta == NULL) { if (!(session->meta = shout_metadata_new())) { return csound->init_error("Could not allocate metadata"); } else { shout_set_metadata(session->shout, session->meta); } } STRINGDAT &key = args.str_data(1); STRINGDAT &value = args.str_data(2); int mres = shout_metadata_add(session->meta, key.data, value.data); return OK; } }; struct shoutsend : csnd::InPlug<4> { ARGO = ""; ARGI = "iaaj"; ShoutSession* session; lame_global_flags* lame; unsigned char* outbuffer; int* inbufferL; int* inbufferR; int inbufferPos; int inbufferSize; int outSize; int outMaxSize; int init() { if (!(session = getHandle(csound, args[0], handleName))) { return csound->init_error("Invalid shout session handle"); } lame = lame_init(); if (!lame) { return csound->init_error("Could not setup LAME"); } lame_set_num_channels(lame, 2); lame_set_in_samplerate(lame, csound->sr()); lame_set_quality(lame, (args[3] == FL(-1)) ? 4 : (int) args[3]); lame_set_out_samplerate(lame, csound->sr()); lame_init_params(lame); shout_set_audio_info(session->shout, SHOUT_AI_BITRATE, "128"); outSize = 0; inbufferPos = 0; inbufferSize = 2048; // ksmps derived? outMaxSize = inbufferSize; // * 2; //8192; outbuffer = (unsigned char*) csound->malloc(sizeof(unsigned char) * outMaxSize); inbufferL = (int*) csound->malloc(sizeof(int) * inbufferSize); inbufferR = (int*) csound->malloc(sizeof(int) * inbufferSize); return OK; } int aperf() { for (int i = 0; i < nsmps; i ++) { inbufferL[inbufferPos] = (int) (args(1)[i] * INT_MAX); inbufferR[inbufferPos ++] = (int) (args(2)[i] * INT_MAX); if (inbufferPos >= inbufferSize) { outSize += lame_encode_buffer_int(lame, inbufferL, inbufferR, inbufferSize, outbuffer, outMaxSize); //std::cout << shout_delay(session->shout) << "\n"; //shout_sync(session->shout); shout_send(session->shout, outbuffer, outSize); inbufferPos = 0; outSize = 0; } } return OK; } }; #include void csnd::on_load(csnd::Csound *csound) { csnd::plugin(csound, "shoutinit", csnd::thread::i); csnd::plugin(csound, "shoutopen", csnd::thread::i); csnd::plugin(csound, "shoutclose", csnd::thread::i); csnd::plugin(csound, "shoutsend", csnd::thread::ia); csnd::plugin(csound, "shoutsetname", csnd::thread::i); csnd::plugin(csound, "shoutseturl", csnd::thread::i); csnd::plugin(csound, "shoutsetgenre", csnd::thread::i); csnd::plugin(csound, "shoutsetdescription", csnd::thread::i); csnd::plugin(csound, "shoutsetpublic", csnd::thread::i); csnd::plugin(csound, "shoutsetmeta", csnd::thread::i); }