ECS7012设计编程 写作、c/c++程序

” ECS7012设计编程 写作、c/c++程序ECS7012 Music and Audio Programming Spring 2021Assignment 2: Drum MachineHanded out: Wednesday, 3 March 2021Report and Code Due: 10:00, Friday, 26 March 2021Introduction:Your task is to create a digital audio system which plays sequenced drum loops with varioustempos and styles, depending on input from several sensors.Your drum machine will play drum samples which will be given to you in preloaded buffers.Each buffer contains a single drum sound. You will also be given a collection of patternsdescribing how the drum Samples should be ordered into loops. Your task will be to create theaudio code that plays the samples in the specified loop.The speed of the loops and which loop to play at any given time will be selectable by sensorsattached to the Bela board, including an accelerometer which measures the tilt of the board, apotentiometer and two buttons. Your code will handle reading the sensor data and using it tocontrol the parameters of the audio sequencer.Required Materials: Lab Kit (Bela Starter Kit and cables) 1 x 3-axis accelerometer board 1 x potentiometer 2 x buttons 1 x LED Resistors (10k for buttons, 470 for LED) Code template from QMplusSystem Description:You need to build a System with the following requirements. You can choose to follow thesteps in the next section or build using your own design. In either case you must documentyour design decisions. Play multiple drum samples simultaneously, up to 4 drum samples Play samples back according to a defined looping pattern of 16 events (beats) Store and recall 5 different drum patterns Start and stop playback with button press LED that flashes on each beat, like a metronome Change playback tempo with potentiometer Select drum pattern with orientation of accelerometer Turning the accelerometer upside down plays the drum samples of the current drum patternbackwards BONUS: detect when accelerometer tapped and play drum fill pattern onceInstructions:1 / 91. Overview of the taskYour task in this assignment is to create an audio sequencer system which is capable ofplaying sampled drum sounds at precise times. The sounds themselves are held in buffers(each buffer containing one single drum sound), and the patterns are specified in arrays. Asimple example pattern is shown below, which would play in a loop of 16 events:Your job will be to play the sounds at the right time, to manage the overall timing of thesequence (keeping track of where you are in the sequence and when the next event shouldarrive), and to select between different patterns depending on sensor input data.2. Playing a drum from a sample bufferIn this step, you should write Code so that pressing a button plays a drum sound.See the appendix for how to wire the button to the Bela. Your button-reading and audio codeshould go in render.cpp. The drum sample buffers are stored in the arraygDrumSampleBuffers (where each element of the array is a buffer holding a differentdrum sound). The lengths of each sample are stored in the arraygDrumSampleBufferLengths. For example, gDrumSampleBufferLengths[0] holdsthe length of the drum sample stored in gDrumSampleBuffers[0].To work with the button: Initialise the GPIO input on the correct pin (in setup()) Read the button value and check if it has changed from unpressed to pressed (inrender()). You will need to save the last value in a global variable. Hint: see the Week 4aexample where we looked at how to tell if the button was just now pressed.To trigger the sound, check if the button was just now pressed, using the code you just wrote.When this happens, you should start playing sound from the buffer (and reset the variable). Avariable gReadPointer has been declared for you, which you can use to play sound fromthe drum buffer. You need to do three things: Start the read pointer at 0 when the button is pressed Copy samples from the drum buffer to the output audio buffer in render() (rememberboth channels!), incrementing the read pointer after each sample. Stop when the read pointer reaches the end of the drum buffer.Hint: think about what the default value of gReadPointer should be before you press thebutton. Implement this in the top of the file.3. Multiple drums at onceKickSnareHihat2 / 9Playing real drum patterns will often require multiple sounds to be active at once. Here, wewill create a scheme for playing up to 16 sounds at once; these could be different drums oreven multiple copies of the same drum (e.g. if playing the same sound again before the firstone has finished).In this step, rather than having one read pointer, we will have an array of them. First replacegReadPointer with an array called gReadPointers, which contains 16 elements. Thencreate a second array of 16 elements, type int, which will hold which buffer is associatedwith each read pointer. You might call this gDrumBufferForReadPointer or similar.3a. Starting a drum soundFill in the function called startPlayingDrum(). This function should be called wheneveryou want to play a new drum sound. It takes as an argument the index of the drum to play(i.e. which buffer within gDrumSampleBuffers). Your code should do the following: Find the first read pointer that is not already being used to play a drum (Hint: use a for()loop to go through and check each element of the array. The break; statement lets youstop the loop once you find a Read pointer that isnt being used.) Use gDrumBufferForReadPointer to indicate which buffer should be played, and resetthe read pointer to 0 to make it start playing. If there are no read pointers free (all 16 in use; unlikely), you can return without playing thesound. (There are other options, such as voice stealing, but we dont need to do that here.)3b. Playing the drum soundsOnce you have created these two arrays, your code in render() should go through eachread pointer in the array (Hint: for() loop). Check whether the read pointer is actuallyassociated with a drum sound (Hint: use gDrumBufferForReadPointer; what kind ofvalue might you assign to indicate that the particular read pointer isnt used right now?).Also check whether the read pointer has reached the end of the buffer, and if so, changegDrumBufferForReadPointer to indicate that the pointer is no longer active.For every active read pointer, mix the drum sound from the buffer into your audio output.Hint: You may need to reduce the overall level of the output to avoid clipping with multipledrums.3c. Testing multiple drumsAttach two buttons to your BBB. Extend your code from Step 2 to play two different drumsounds. When one of the triggers is set, call startPlayingDrum() to initiate the sound.You should be able to play both at the same time without glitches or distortion. You shouldalso be able to press the button quickly to re-trigger a drum without cutting off the previoussample.4. Playing drums in a loopIn this step, you will play looping Sequences of drum sounds based on pre-stored patterns.The patterns are stored in arrays; which pattern to play and how fast to play it will ultimately3 / 9be selectable by sensors. Note: you no longer need the buttons to trigger drum sounds as theydid in step 3; that was only for testing.4a. Create a simple metronomeYou have been given a function named startNextEvent(). You will use this function totrigger the next event in the pattern. For this step, add code to startNextEvent() so that italways plays the same drum sound every time it is called (Hint: startPlayingDrum()).Next, add code to render() which counts the number of audio samples that have gone by(Hint: you will need a global variable to save the number of samples; refer to the Week 7aexercises). When it reaches the length of time specified in the global variablegEventIntervalMilliseconds (already given to you), call startNextEvent() andreset the counter. (Hint: how do you convert from the interval in milliseconds to the numberof samples needed?)When you have finished this step, you should hear a drum sound playing at a regular interval,like a metronome.4b. Start and stopMake one of the buttons start and stop the metronome. Read the button inside render() asbefore, and add code to set the global variable gIsPlaying to 1 or 0. Then, within yourrender() function, check the value of gIsPlaying to decide whether to trigger newevents. Note: when you stop the loop, the currently playing samples should finish playing, sodont use gIsPlaying to cut off all the audio! Just check it to decide whether or not to startfurther drum sounds.At the end of this step, you should be Able to start and stop your metronome by pressing thebutton.4c. Change tempo with the potentiometerWire up the potentiometer to one of the analog inputs on the cape (see appendix for circuitdiagram). Inside render(), you should also add code to read the potentiometer value(analogRead()). Remember that there are two audio samples for every analog input sample(i.e. the sample rate is 22.05kHz compared to 44.1kHz for audio).When you get the analog input, you need to map it to the tempo of the loop by changing thevalue of gEventIntervalMilliseconds. (Remember, smaller interval = faster tempo.)Make it so the full range of potentiometer values maps to an interval range of 50-1000ms.(Hint: use the map() function.)When you finish this step, you should have a metronome whose tempo you can change byturning the potentiometer.4d. Make the LED blink on each beatIn this step, you should make the LED blink at each beat. See the appendix for how to wirethe LED. You will also need to add code to setup() to initialise the GPIO pin to be anoutput. In render() you will need the LED to stay lit for more than 1 sample so you cansee it (Hint: see Week 7a slides.)4 / 9When you finish this step, the LED should blink with each successive beat.4e. Play a drum patternYou are given a two-dimensional array gPatterns which holds the sequence of drums toplay. The first dimension of the array is which pattern to use; the second dimension is theindex of the events within a pattern. For example, gPatterns[0] will hold the first drumpattern; gPatterns[0][0] would be the first event within the pattern, followed bygPatterns[0][1], etc. The length of each pattern is stored in the arraygPatternLengths.In this step, you should change the code inside startNextEvent(). Instead of alwaysplaying the same drum sound, read the next event in the pattern to decide which drum(s) toplay. You should do the following: You have been given two global Variables: gCurrentPattern andgCurrentIndexInPattern. These hold which pattern to play and where within thepattern is currently being played. Each time startNextEvent() is called, look up thecurrent event in gPatterns using these two indices. Figure out which drums this eventcontains and play them. You have been given a function int eventContainsDrum(intevent, int drum). This function returns 1 if the given event contains the given drumindex (since an event may contain more than 1 drum). Hint: use a for() loop to check foreach drum; if eventContainsDrum() returns 1, then play that sound. Increment the index within the current pattern. If you get to the end of the pattern (Hint:check gPatternLengths), reset the index to 0. Notice that your index within the patternworks very much like a circular buffer for audio samples.At the end of this step, you should hear a complete drum pattern with multiple drum sounds,playing in a loop. The tempo of the loop will be controllable with the potentiometer. Yourenearly there!5. Select patterns with the accelerometerYou have been given a 3-axis accelerometer. This device creates 3 analog signals whosevalues are proportional to the acceleration in that axis. The X and Y axes are parallel to thebreadboard. The Z axis is up/down assuming the breadboard is lying flat on a desk.See the appendix for how to wire up the accelerometer. You will use analogRead() insiderender() to read the values from each axis.Important Note 1: the accelerometer is designed to measure acceleration in either direction,but the voltage it produces is always positive. Therefore, zero acceleration (i.e. sitting still ormoving at constant velocity) will be somewhere roughly halfway between 0V and 3.3V. Butthe exact value will depend on the particular device and the resistors used, so you cantassume it is always 1.65V. Therefore, you may have to measure it experimentally and includethese values in your code (or read values at startup, during the first few samples ofrender(), and use these as a reference).5 / 9Important Note 2: gravity is an acceleration! When the accelerometer is plugged into thebreadboard and resting on the Desk, you should measure 0g (no acceleration) in the X and Yaxes, and 1g in the Z axis. Turn the board on its side (90), and now you will measure +/-1gin either the X or Y axis, depending on which way you have turned it (and 0g in the Z axis).Turn the board upside-down, and you will measure -1g in the Z axis. Hint: the full range ofoutput voltage on the accelerometer corresponds to -1.5g to 1.5g in each axis.5a. Detect board orientationBased on the acceleration in each axis, you should be able to determine which way the boardis facing. By default you will read around 1g in the Z axis and 0g in the others (again,remember, 0g does not mean 0V!). You need to design a system that recognises any of 5orientations (note: depending on how your accelerometer is plugged into the board, the signsand axes may be different, but you will always have these 5 orientations): Resting flat (X = 0, Y = 0, Z = positive) Turned vertically on left side (X = negative, Y = 0, Z = 0) Turned vertically on right side (X = positive, Y = 0, Z = 0) Turned vertically on front side (X = 0, Y = negative, Z = 0) Turned vertically on back side (X = 0, Y = positive, Z = 0)Write code within render() to read each axis and make a decision about what orientationthe board is in. You dont necessarily need to check the accelerometer every single sample:reading it 100 times per second or so would be sufficient. Better yet would be to put alowpass filter on each of the accelerometer signals to reduce noise. (Hint: use a block of if() /else if() statements, but remember that the values in each axis may vary by a bit. Youll needsome degree of tolerance so if, for example, X and Y are not exactly 0g, you still detect theright orientation. Hysteresis will also help make the changes cleaner.)Initially, use rt_printf() statements so you can tell if your detection is working correctly.(rt_printf() is like regular printf() but performs better in this real-time context. But if youprint every single sample it will surely overload the system, so use sparingly!)5b. Change pattern depending on orientationBased on the orientation information in the previous step, change the value ofgCurrentPattern so that a different pattern plays in each orientation of the board.Remember that the patterns may be of different lengths! Each time you changegCurrentPattern, use modulo arithmetic to make sure gCurrentPatternIndex stayswithin the length of the New pattern (Hint: check gPatternLengths).At the end of this step, your drum machine should play 5 different patterns depending onwhich way the board is turned.5c. Play samples backwards when the board is upside downThere is one further orientation we have not explored: the board upside down will producenegative Z-axis acceleration and 0 in the X and Y axes. Add code to render() to detect thisorientation. Dont change the current pattern in this case. Instead, set the global variablegPlaysBackwards (already defined for you). Then in your audio code, when this variable is6 / 9set, you should make every drum sample play backwards instead of forwards. Hint: make theread pointers count downward instead of upward. Now how do you check if youve reachedthe end of a drum sample?Now you should have a complete working drum machine! Tempo is adjustable bypotentiometer, start/stop by the button, and selecting patterns by the orientation of the board.See below for some more features you can add if youre interested.Bonus Step (up to 5% extra marks). Tap to add a fillIn the final step, we will temporarily play a different drum pattern (a fill) whenever we firmlytap the board. Drum fills are often used at the end of a section of several bars; they areusually played once rather than as a repeating pattern.You have been given a variable gShouldPlayFill. You should set this in render()whenever you recognise a tap on the accelerometer. In the audio code (in render()), youshould check the value of this variable. If it is set, you should start playing a new pattern.When you get to the end of this new pattern, youll go back to what you were playing before,so you should save the value of gCurrentPattern inside the global variablegPreviousPattern. Then, set the value of gCurrentPattern to the special valueFILL_PATTERN and reset gCurrentPatternIndex to 0. This will cause the fill to playnext. Hint: dont forget to reset gShouldPlayFill inside render().In startNextEvent(), when you get to the end of the current pattern, check whether thepattern was a fill (Hint: if(gCurrentPattern == FILL_PATTERN)). If so, setgCurrentPattern back to the value you stored in gPreviousPattern, which will causethe drum machine to go back to the pattern it was playing before.Tapping the accelerometer will Produce a temporary spike in acceleration. If youre tappingthe surface of the breadboard, the spike should be primarily in the Z axis, but tapping in otherdirections is possible. For this step, you will need to design code to check for a spike inacceleration, and set gShouldPlayFill to 1 whenever you detect one. Hint: tryimplementing a high-pass filter on the accelerometer data. This will pass the spikes whileblocking the constant acceleration due to gravity. You can implement a filter on theaccelerometer data exactly as you did on the audio data in Assignment 1: save the previousinputs and outputs and multiply by the correct filter coefficients. Remember that the samplingrate of the accelerometer, like All analog inputs on Bela, is half the audio rate (22.05kHz).Your submission should consist of the following:1. Your source code. All .cpp and h files that you edit or create for the project includingmain.cpp and render.cpp.7 / 9 Dont forget to comment your code! Uncommented or illegible code will receive areduced mark. Please submit a zip file containing your source code and your report via QMPlus.2. A 3 to 4-page report (PDF format). Include the following information (though yourreport does not have to be structured in this way): Describing your design process. Details of your implementation. State diagram explaining how your implementation works. Cite your sources! Any code or designs found to come from another source withoutattribution will be treated as a case of plagiarism, and may be referred to the universityfor further action. Use the Harvard Style for citations.3. Quick (2-3 min.) video demo of your drum machine in action. This can be recorded witha phone or laptop camera; production value is not important. It should show you startingand stopping the drum machine, changing the tempo with the potentiometer, and tiltingthe board in different directions to get different patterns (including upside down to playthe samples backwards). If you have implemented the bonus step then please show that aswell. If you have an external speaker that you can use with Bela, then use that to recordthe sound. If you dont have a speaker, you could try a large pair of headphones orholding the headphone directly up to the microphone. Sound quality is not especiallyimportant, but it should be possible to tell what the drum machine is playing.Marking Criteria:Theory Demonstrates an understanding of the theory being implemented. 30%Implementation Code functions correctly, is well-commented, and designdecisions are explained/justified in the report.30%Evaluation Evidence demonstrating the implementations success anddiscussion of any weaknesses.30%Report Professional presentation of a technical report; sources are cited;figures are properly referenced and of sufficient resolution to belegible.10%8 / 9Appendix: CircuitsNote: pin numbers in the schematics may be different in your implementationLEDAttaching an LED requires a resistor (range between220 and 470, depending on the brightness youwant). Put the LED and resistor in series, making surethe short lead of the LED goes to ground. 2 wires toBela required: one to the GPIO output pin, one toground. Note: your GPIO pin numbers could bedifferent than the ones listed here; remember to referto the pin diagram to tell you Which pin correspondsto which GPIO number.ButtonThe button requires a pull-up resistor (10k suggested; brown-blackorange).The resistor goes between +3.3V and one terminal of thebutton. The other terminal of the button attaches to ground. Neveruse 5V on Bela! 3 wires to Bela required: one to +3.3V, one to theGPIO input pin, one to ground.PotentiometerThe potentiometer is Attached with 3 wires. One end of thepotentiometer goes to +3.3V (which can be found on pin 3of P9), the other end goes to ground. The middle terminalof the potentiometer goes to the analog input on the Belacape.AccelerometerThe accelerometer is powered by +3.3V and connectsto three analog inputs on the cape. Five wires arerequired: 3 analog inputs, +3.3V and ground. Inaddition, you will need a wire between the +V inputand the SLP pin to Enable the accelerometer.请加QQ:99515681 或邮箱:99515681@qq.com WX:codehelp

添加老师微信回复‘’官网 辅导‘’获取专业老师帮助,或点击联系老师1对1在线指导