We all know how to set the audio volume in Windows: use the small mixer widget (for me on the right side of the bottom panel). My typical problem is: I play live music on a wind instrument (clarinet, oboe, bassoon, saxophone) having thus boths hands occupied. But how can I influence the audio volume on the fly, i.e. when a song is already started? I prefer foot switches to do that. Note that a standard foot switch normally acts like a poti (variable resistor). As I do not play with devices connected via normal audio cables this standard solution cannot be applied. My solution presented here can also be used with Bluetooth, i.e. without normal audio cables.
Note: this program is related to the PDF document under A foot switch for musicians. The foot switch mentioned there produces events sent via USB/Serial. These events can replace the key strokes used here so that the play process ist completely hands-free!
The fact that we use keyboard events (key strokes) in our sample is not a contradiction to the main problem as described above. If you replace the key strokes by foot switch events or events coming from another application then the structure of the program remains the same. Just replace the application logic in routine threadWorker1 by the reception of signals via TCP, USB etc.
Sone remarks on the sample program: this program uses PThreads - it could also use normal windows threads or any other type of thread (wxWidgets etc.) PThreads are useful as they can also be used under Linux/Unix.
In pure Windows GUI programs one single thread can perhaps be used. The audio player must then work in asynchronous mode (this is more difficult) and the polling of the control events can be done in a Timer callback routine. My experiences with such a single thread solution howewer failed so far.
This is a standard C program compiled with MingW gcc. This version is a CLI program - i.e. it has no real graphic interface and runs in a "black shell window". The command used to compile and link it can be found in the first comment lines of the source code.
You may download the source code here.
// module waveThreads.c // Uses portable threads (PThreads) under Windows // Audio result: works: thread2 influences the volume of // the wave player thread1! Evaluates the keys q, +, - // This version uses mutexes as signals to indicate the termination // of a thread!!! // Works under Windows 32 Bit // Written by Dr.E.Huckert 03-2009, 05-2016 // Windows 32 Bit: compile: gcc -o waveThreads.exe waveThreads.c -lpthread -lwinmm // Versions: // 03-2020 problem with printing thread-ID fixed, then OK #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> // This is a very simple thread administration...not // really used in this version #define NUM_THREADS 10 // max. 10 threads pthread_t thread_ids[NUM_THREADS]; pthread_mutex_t mutex1, mutex2; #ifdef _WIN32 #include <windows.h> #include <mmsystem.h> #include <conio.h> #define sleep(x) Sleep(1000 * x) #endif char waveFilename[128] = "test.wav"; // Note: these are values per channel! // They must be combined (shifted, masked) for stereo #define HIGH_VOLUME 0xdddd #define LOW_VOLUME 0x1111 #define MAX_VOLUME 0xffff #define MIN_VOLUME 0x0000 #define MEDIUM_VOLUME 0x7fff // --------------------------------------------------------- // Play a WAVE file whose name is given // Returns: < 0 upon error int playWaveFile(const char *filename) { int ret; // printf("playWaveFile() file=%s\n", filename); // EH: must be mode=SND_SYNC - if not only the first buffer is played and // the rest is skipped as the application terminates! //ret = ::sndPlaySound(filename, SND_FILENAME | SND_ASYNC); //ret = sndPlaySound(filename, SND_FILENAME | SND_SYNC); // deprecated ret = PlaySound(filename, NULL, SND_FILENAME | SND_SYNC); return ret; } // end playWaveFile() // ----------------------------------------------------------- // symmetric channel values assumed DWORD makeMono(DWORD stereoVol) // 32 bit, 16 bit per channel { return stereoVol & 0x0000ffff; // result=right channel } // end makeMono() // ----------------------------------------------------------- // symmetric channel values assumed DWORD makeStereo(DWORD monoVol) // right channel value, 16 Bit { return (monoVol << 16) | monoVol; } // end makeStereo() // ----------------------------------------------------------- // set the volume for a WAVE audio channel // Returns: < 0 upon error // else the last volume set (mono value, not shifted) DWORD getSetVolume(DWORD newVol) // mono value, not shifted { int ret = 0; HWND hwnd; MMRESULT mmRet; DWORD dword, retVol; // static unsigned entryCount = 0; static HWAVEOUT hWaveOut; static HWAVEIN hWaveIn; static WAVEFORMATEX waveform ; // printf("getSetVolume newVol=0x%lx\n", newVol); retVol = 0UL; if (entryCount == 0) { // Open a WaveOut device once (static!) hwnd = GetCurrentThread(); waveform.wFormatTag = WAVE_FORMAT_PCM ; waveform.nChannels = 1 ; waveform.nSamplesPerSec = 11025 ; waveform.nAvgBytesPerSec = 11025 ; waveform.nBlockAlign = 1 ; waveform.wBitsPerSample = 8 ; waveform.cbSize = 0 ; // waveOutReset(hWaveOut) ; mmRet = waveOutOpen(&hWaveOut, WAVE_MAPPER, &waveform, 0, 0, CALLBACK_NULL); if (mmRet != MMSYSERR_NOERROR) { char msg[128]; waveOutGetErrorText(mmRet, msg, sizeof(msg) - 1); printf("waveOutOpen() reports ERROR: %s\n", msg); ret = -1; goto zurueck; } } entryCount++; // mmRet = waveOutSetVolume(hWaveOut, makeStereo(newVol)); printf("volume set=%lu\n", newVol); printf("volume mmRet=%lu\n", (long)mmRet); // mmRet = waveOutGetVolume(hWaveOut, &retVol); retVol = makeMono(retVol); printf("volume get=0x%lx\n", retVol); // zurueck: printf("getSetVolume() retVol=0x%lx\n", retVol); return retVol; } // end getSetVolume() // ------------------------------------------------- // thread 1 worker routine: plays a local wave file // Assumes that mutex1 is locked at start time void *threadWorker1(void *pArg) { int n, rc; long *pLong; // pLong = (long *)pArg; //printf("thread 1 arg=%ld\n", *pLong); printf("thread 1: playing file %s\n", waveFilename); // pthread_testcancel(); // this is necessary!!! // rc = playWaveFile(waveFilename); // pthread_testcancel(); // this is necessary!!! // // send a termination signal: the termination signal is the mutex itself! rc = pthread_mutex_unlock(&mutex1); if (rc != 0) { printf("thread 1: unlock ERROR\n"); } else printf("thread 1: termination signal = mutex set\n"); // zurueck: printf("leaving thread 1\n"); pthread_exit(NULL); } // end threadWorker1() // ---------------------------------------------------- // thread worker routine: runs 15 secs // Tries to change the volume of the audio player thread 1 void *threadWorker2(void *pArg) { int n, rc; long *pLong; DWORD lastVol, newVol; int key; // //pLong = (long *)pArg; //printf("thread 2 arg=%ld\n", *pLong); lastVol = getSetVolume(MEDIUM_VOLUME); // // wait for key events: // q = quit // + = increase volume // - = decrease volume // Important: instead of polling for key strokes you can // poll here for events from foot switches, TCP, UDP etc... while (1) { key = _kbhit(); if (key == 0) goto go_on; key = _getch(); printf("thread 2 key=%d\n", key); if (key == 'q') break; // Note: still probelms here as the volum e values are unsigned, // .i.e. comparison < 0 may not work! if (key == '-') { newVol = lastVol - (lastVol / 5); if (newVol < MIN_VOLUME) newVol = MIN_VOLUME; lastVol = getSetVolume(newVol); } if (key == '+') { newVol = lastVol + (lastVol / 5); if (newVol > MAX_VOLUME) newVol = MAX_VOLUME; lastVol = getSetVolume(newVol); } go_on: Sleep(100L); } // end while (1) // // send a termination signal: the termination signal is the mutex itself! rc = pthread_mutex_unlock(&mutex2); if (rc != 0) { printf("thread 2: unlock ERROR\n"); } else printf("thread 2: termination signal = mutex set\n"); // zurueck: printf("leaving thread 2\n"); pthread_exit(NULL); } // end threadWorker2() // --------------------------------------------------------- // Create and start a thread // Side effect: fills array thread_ids[] // Returns 0 if OK, < 0 if error int createThread(int threadNo, // 0 .. (NUM_THREADS-1) void *(*worker)(void *), // address of worker routine void *pArg) { int ret = 0; printf("creating thread no. %d\n", threadNo); ret = pthread_create(&thread_ids[threadNo], NULL, worker, pArg); // argument passed to the worker routine if (ret != 0) { ret = -1; goto zurueck; } zurueck: printf("created thread no. %d with result=%d\n", threadNo, ret); return ret; } // end createThread() // ------------------------------------------------------ void usage() { printf("usage: waveThreads [waveFilename]\n"); printf(" If no file is given the test.wav is assumed\n"); printf(" Use the volume keys +,- and q (for quit)\n"); printf("Copyright Dr.E.Huckert 03-2020\n"); } // end usage() // ------------------------------------------------------ int main (int argc, char *argv[]) { int rc; int ret = 0; long arg1 = 101L; long arg2 = 102L; // usage(); if (argc > 1) strcpy(waveFilename, argv[1]); // rc = pthread_mutex_init(&mutex1, NULL); rc = pthread_mutex_init(&mutex2, NULL); // // create two threads and start them rc = createThread(0, threadWorker1, &arg1); if (rc != 0) { ret = -1; goto zurueck; } rc = pthread_mutex_lock(&mutex1); if (rc != 0) { ret = -2; goto zurueck; } printf("createThread() rc=%d\n", rc); // worked in year 2009 - now (2020) no longer! //printf("thread id=%08lx\n", (long)( thread_ids[0])); printf("thread id=0x%p\n", thread_ids[0].p); // rc = createThread(1, threadWorker2, &arg2); if (rc != 0) { ret = -3; goto zurueck; } rc = pthread_mutex_lock(&mutex2); if (rc != 0) { ret = -4; goto zurueck; } printf("createThread() rc=%d\n", rc); // worked in year 2009 - now (2020) no longer! //printf("thread id=%08lx\n", (long)( thread_ids[])); printf("thread id=0x%p\n", thread_ids[1].p); // // Wait until all threads terminate. // The termination signal is the change of a mutex associated with each thread sleep(1); // give the threads time to start int noThreads = 2; // while (noThreads > 0) { // Note: this can be replaced and simplified by a loop over an array of mutexes // try to lock mutex1 rc = pthread_mutex_trylock(&mutex1); if (rc != 0) // errror handling is not complete (is simplified) { printf("main(): cannot lock - waiting\n"); } else { printf("main(): lock 1 OK!!!\n"); noThreads--; if (noThreads <= 0) break; } // try to lock mutex2 rc = pthread_mutex_trylock(&mutex2); if (rc != 0) // errror handling is not complete (is simplified) { printf("main(): cannot lock - waiting\n"); } else { printf("main(): lock 2 OK!!!\n"); noThreads--; if (noThreads <= 0) break; } sleep(1); } // end while(noThreads > 0) // ret = 1; printf("main() all threads have terminated\n"); // zurueck: printf("main() return code=%d\n", ret); return ret; } // end main()
Copyright for all images, texts and software on this page: Dr. E. Huckert